diff --git a/.gitignore b/.gitignore
index fe63595..028b49a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
.gitignore
-pyproject.toml
.idea/
__pycache__/
dist/
easier_docker.egg-info/
+example.egg-info/
venv/
build/
-.coverage
\ No newline at end of file
+.coverage
+htmlcov
+easierdocker/easier_docker.egg-info/
diff --git a/Makefile b/Makefile
index 6a137c0..03c3e19 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,31 @@
-.PHONY: test clean all
+.PHONY: test clean build upload all
-all: test clean
+TWINE_UPLOAD := twine upload --repository pypi --username __token__ --password $(TWINE_API_TOKEN)
+
+all: clean build
test:
coverage run -m unittest discover
coverage report
+ coverage html
+ google-chrome htmlcov/index.html
+
clean:
find . -name '__pycache__' -type d -exec rm -rf {} +
- rm -rf build/*
- rm -rf dist/*
- rm -rf dist/*
- rm -rf easier_docker.egg-info/*
+ find . -name 'easier_docker.egg-info' -type d -exec rm -rf {} +
+ rm -rf build
+ rm -rf dist
rm -rf .coverage
+ rm -rf htmlcov
+
+build:
+ python -m build
+
+upload:
+ @echo "Uploading the package..."
+ @if [ -z "$(TWINE_API_TOKEN)" ]; then \
+ echo "Error: TWINE_API_TOKEN is not set. Please export it as an environment variable."; \
+ exit 1; \
+ fi
+ $(TWINE_UPLOAD) dist/*
diff --git a/README.md b/README.md
index 3934b92..fc08079 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,12 @@
-
easier-docker
+# easier-docker
-
-
-
-
-
-
+
+
+
+
+
+
+[](https://github.com/touero/easier-docker/actions/workflows/python-publish.yml)
## Repository Introduction
@@ -23,13 +24,13 @@ Please check [wiki](https://github.com/touero/easier-docker/wiki).
## Related
### Repository
-- [docker-py](https://github.com/docker/docker-py) — A Python library for the Docker Engine API.
+[docker-py](https://github.com/docker/docker-py) — A Python library for the Docker Engine API.
### Materials
-- [Docker SDK for Python](https://docker-py.readthedocs.io/en/stable/)
+[Docker SDK for Python](https://docker-py.readthedocs.io/en/stable/)
-### Repository Used
-- [opsariichthys-bidens](https://github.com/weiensong/opsariichthys-bidens) — About
+### Repository Used Example
+[opsariichthys-bidens](https://github.com/weiensong/opsariichthys-bidens) — About
Building a Basic Information API for Chinese National Universities in the Handheld College Entrance Examination Based on Fastapi.
@@ -38,7 +39,8 @@ Building a Basic Information API for Chinese National Universities in the Handhe
## Contributing
-[Open an issue](https://github.com/weiensong/easier_docker/issues) or submit PRs.
+[Open an issue](https://github.com/weiensong/easier_docker/issues) or submit PRs to git branch `develop`.
+
Standard Python follows the [Python PEP-8](https://peps.python.org/pep-0008/) Code of Conduct.
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..733006e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,37 @@
+[build-system]
+requires = ["setuptools>=68.2.0", "wheel>=0.42.0"]
+build-backend = "setuptools.build_meta"
+
+
+[tool.setuptools.packages]
+find = { where = ["easierdocker"] }
+
+
+[project]
+name = "easier-docker"
+version = "2.2.4"
+description = "Configure your container image information more easily in python, allowing the container in docker to execute the configured program you want to execute."
+readme = "README.md"
+requires-python = ">=3.8"
+license = {text = "Apache License 2.0"}
+authors = [
+ {name = "EnSong Wei", email = "touer0018@gmail.com"}
+]
+
+
+[project.urls]
+Homepage = "https://github.com/touero/easier-docker"
+"Bug Reports" = "https://github.com/touero/easier-docker/issues"
+Source = "https://github.com/touero/easier-docker"
+
+
+keywords = "easy, docker, docker sdk, python docker"
+
+
+classifiers = "License :: OSI Approved :: Apache Software License, Programming Language :: Python :: 3.8, Programming Language :: Python :: 3.9, Programming Language :: Python :: 3.10, Programming Language :: Python :: 3.11, Programming Language :: Python :: 3.12"
+
+
+dependencies = "docker~=7.1.0, setuptools~=68.2.0, PyYAML~=6.0.1, wheel~=0.42.0, twine~=4.0.2, coverage==7.4.4"
+
+[project.scripts]
+easier-docker = "easierdocker.__main__:main"
diff --git a/requirements.txt b/requirements.txt
index e4469ad..a70da8f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,5 @@ PyYAML~=6.0.1
wheel~=0.42.0
twine~=4.0.2
coverage==7.4.4
+build
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 353f93a..0000000
--- a/setup.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from setuptools import setup, find_packages
-
-setup(
- name='easier-docker',
- version='2.2.4',
- author='EnSong Wei',
- author_email='touer0018@gmail.com',
- description='Configure your container image information more easily in python, allowing the container in docker '
- 'to execute the configured program you want to execute.',
- long_description=open('README.md', encoding='utf-8').read(),
- long_description_content_type='text/markdown',
- url='https://github.com/touero/easier-docker',
- packages=find_packages(),
- license='Apache License 2.0',
- install_requires=[
- 'docker~=7.1.0',
- 'setuptools~=68.2.0',
- 'PyYAML~=6.0.1',
- 'wheel~=0.42.0',
- 'twine~=4.0.2',
- 'coverage==7.4.4'
- ],
- entry_points={
- 'console_scripts': [
- 'easier-docker=easierdocker.__main__:main',
- ],
- },
- classifiers=[
- 'License :: OSI Approved :: Apache Software License',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9',
- 'Programming Language :: Python :: 3.10',
- 'Programming Language :: Python :: 3.11',
- 'Programming Language :: Python :: 3.12',
- ],
- keywords='easy, docker, docker sdk, python docker',
- project_urls={
- 'Bug Reports': 'https://github.com/touero/easier-docker/issues',
- 'Source': 'https://github.com/touero/easier-docker',
- },
-)
diff --git a/tests/test_easier_docker.py b/tests/test_easier_docker.py
index e439cf3..70c2fff 100644
--- a/tests/test_easier_docker.py
+++ b/tests/test_easier_docker.py
@@ -1,98 +1,102 @@
-import os
-
import unittest
from unittest.mock import patch, MagicMock
+from docker.errors import ImageNotFound, APIError, NotFound, DockerException
from easierdocker import EasierDocker
+from easierdocker.exceptions import DockerConnectionError, NotFoundImageInDockerHub
class TestEasierDocker(unittest.TestCase):
- @patch('docker.from_env')
- def test_init(self, mock_from_env):
- parent_dir = os.path.dirname(os.getcwd())
- host_script = os.path.join(parent_dir, 'example')
- container_script = '/path/to/container'
- container_config = {
- 'image': 'python:3.9',
- 'name': 'python_test',
- 'volumes': {
- f'{host_script}': {'bind': container_script, 'mode': 'rw'}
- },
- 'detach': True,
- 'command': ["sh", "-c", f'cd {container_script} &&'
- 'python docker_example.py'],
+ def setUp(self):
+ self.container_config = {
+ "image": "test_image",
+ "name": "test_container",
+ "detach": True
}
+ self.network_config = {"name": "test_network"}
+ self.easier_docker = EasierDocker(self.container_config, self.network_config)
- network_config = {
- 'name': 'bridge',
- 'driver': 'bridge',
- }
+ @patch("docker.from_env")
+ def test_docker_connection_error(self, mock_from_env):
+ mock_from_env.side_effect = DockerException("Docker connection failed")
+ with self.assertRaises(DockerConnectionError):
+ EasierDocker(container_config={}, network_config={})
- mock_client = MagicMock()
- mock_from_env.return_value = mock_client
- easier_docker = EasierDocker(container_config=container_config, network_config={})
-
- self.assertEqual(easier_docker._container_config, container_config)
- self.assertEqual(easier_docker._network_config, {})
-
- mock_from_env.assert_called_once()
- self.assertEqual(easier_docker._client, mock_client)
-
- easier_docker = EasierDocker(container_config=container_config, network_config=network_config)
- self.assertEqual(easier_docker._container_config, container_config)
- self.assertEqual(easier_docker._network_config, network_config)
-
- @patch('docker.from_env')
- def test_properties(self, mock_from_env):
- parent_dir = os.path.dirname(os.getcwd())
- host_script = os.path.join(parent_dir, 'example')
- container_script = '/path/to/container'
- container_config = {
- 'image': 'python:3.9',
- 'name': 'python_test',
- 'volumes': {
- f'{host_script}': {'bind': container_script, 'mode': 'rw'}
- },
- 'detach': True,
- 'command': ["sh", "-c", f'cd {container_script} &&'
- 'python docker_example.py'],
- }
+ @patch("docker.from_env")
+ def test_init_success(self, mock_from_env):
+ client_mock = MagicMock()
+ mock_from_env.return_value = client_mock
+ docker_instance = EasierDocker(self.container_config)
+ self.assertIsNotNone(docker_instance.client)
- network_config = {
- 'name': 'bridge',
- 'driver': 'bridge',
- }
- mock_client = MagicMock()
- mock_from_env.return_value = mock_client
- easier_docker = EasierDocker(container_config=container_config, network_config=network_config)
- self.assertEqual(easier_docker.container_config, container_config)
- self.assertEqual(easier_docker.network_config, network_config)
- self.assertEqual(easier_docker.client, mock_client)
- self.assertEqual(easier_docker.image_name, container_config['image'])
- self.assertEqual(easier_docker.container_name, container_config['name'])
-
- @patch('docker.from_env')
- def test_get_images(self, mock_from_env):
- parent_dir = os.path.dirname(os.getcwd())
- host_script = os.path.join(parent_dir, 'example')
- container_script = '/path/to/container'
- container_config = {
- 'image': 'python:3.9',
- 'name': 'python_test',
- 'volumes': {
- f'{host_script}': {'bind': container_script, 'mode': 'rw'}
- },
- 'detach': True,
- 'command': ["sh", "-c", f'cd {container_script} &&'
- 'python docker_example.py'],
- }
+ @patch("docker.from_env", side_effect=DockerConnectionError("Docker connection failed"))
+ def test_init_failure(self, mock_from_env):
+ with self.assertRaises(DockerConnectionError):
+ EasierDocker(self.container_config)
- network_config = {
- 'name': 'bridge',
- 'driver': 'bridge',
- }
- mock_client = MagicMock()
- mock_from_env.return_value = mock_client
- easier_docker = EasierDocker(container_config=container_config, network_config=network_config)
- easier_docker._EasierDocker__get_image()
- mock_client.images.get.assert_called_once_with(easier_docker.image_name)
+ @patch("docker.models.images.ImageCollection.get")
+ def test_get_image_found_locally(self, mock_image_get):
+ self.easier_docker._EasierDocker__get_image()
+ mock_image_get.assert_called_once_with("test_image")
+
+ @patch("docker.models.images.ImageCollection.get", side_effect=ImageNotFound("Image not found"))
+ @patch("docker.api.APIClient.pull", return_value=[
+ '{"status": "Pulling", "progress": "50%"}'.encode("utf-8"),
+ '{"status": "Complete"}'.encode("utf-8")
+ ])
+ def test_get_image_pull_success(self, mock_pull, mock_image_get):
+ self.easier_docker._EasierDocker__get_image()
+ mock_pull.assert_called_once_with("test_image", stream=True)
+
+ @patch("docker.models.images.ImageCollection.get", side_effect=ImageNotFound("Image not found"))
+ @patch("docker.api.APIClient.pull", side_effect=NotFound("Image not in Docker Hub"))
+ def test_get_image_pull_failure(self, mock_pull, mock_image_get):
+ with self.assertRaises(NotFoundImageInDockerHub):
+ self.easier_docker._EasierDocker__get_image()
+
+ @patch("docker.models.containers.ContainerCollection.list", return_value=[])
+ @patch("docker.models.containers.ContainerCollection.run")
+ def test_run_container_success(self, mock_run, mock_list):
+ container_mock = MagicMock()
+ container_mock.attrs = {"NetworkSettings": {"IPAddress": "127.0.0.1"}, "Created": "now"}
+ container_mock.name = "test_container"
+ container_mock.short_id = "12345"
+ mock_run.return_value = container_mock
+
+ self.easier_docker._EasierDocker__run_container()
+ mock_run.assert_called_once_with(**self.container_config)
+
+ @patch("docker.models.containers.ContainerCollection.list", return_value=[])
+ @patch("docker.models.containers.ContainerCollection.run", side_effect=APIError("API Error"))
+ def test_run_container_failure(self, mock_run, mock_list):
+ with self.assertRaises(APIError):
+ self.easier_docker._EasierDocker__run_container()
+
+ @patch("docker.models.containers.ContainerCollection.list", return_value=[MagicMock()])
+ def test_get_container_found(self, mock_list):
+ container_mock = mock_list.return_value[0]
+ container_mock.name = "test_container"
+ container_mock.attrs = {"NetworkSettings": {"IPAddress": "127.0.0.1"}, "Created": "now"}
+ container_mock.start = MagicMock()
+
+ container = self.easier_docker._EasierDocker__get_container()
+ self.assertEqual(container_mock, container)
+ container_mock.start.assert_called_once()
+
+ @patch("docker.models.containers.ContainerCollection.list", return_value=[])
+ def test_get_container_not_found(self, mock_list):
+ container = self.easier_docker._EasierDocker__get_container()
+ self.assertIsNone(container)
+
+ @patch("docker.models.networks.NetworkCollection.list", return_value=[])
+ @patch("docker.models.networks.NetworkCollection.create")
+ def test_create_network(self, mock_create, mock_list):
+ self.easier_docker._EasierDocker__create_network()
+ mock_create.assert_called_once_with(**self.network_config)
+
+ @patch("docker.client.DockerClient.networks", new_callable=MagicMock)
+ def test_create_network_exists(self, mock_networks):
+ mock_network = MagicMock(name="test_network", short_id="short_id_1")
+ mock_networks.list.return_value = [mock_network]
+ self.easier_docker._EasierDocker__create_network()
+ mock_networks.list.assert_called_once()