Skip to content

Commit

Permalink
Merge pull request #22 from touero/develop
Browse files Browse the repository at this point in the history
Update some docs and from setup.py turn to pyproject.toml
  • Loading branch information
touero authored Nov 23, 2024
2 parents c2e49a6 + 1080c99 commit 0b78ca8
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 147 deletions.
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.gitignore
pyproject.toml
.idea/
__pycache__/
dist/
easier_docker.egg-info/
example.egg-info/
venv/
build/
.coverage
.coverage
htmlcov
easierdocker/easier_docker.egg-info/
28 changes: 22 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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/*
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<h1 align="center">easier-docker</h1>
# easier-docker

<p align="center">
<a href="https://www.python.org/" ><img src="https://img.shields.io/badge/python_-%3E%3D3.8-blue" alt=""></a>
<a href="https://opensource.org/license/mit/" ><img src="https://img.shields.io/badge/license_-MIT-blue" alt=""></a>
<a href="https://www.python.org/" ><img src="https://img.shields.io/badge/-python-grey?style=plastic&logo=python" alt=""/></a>
<a href="https://www.docker.com/"><img src="https://img.shields.io/badge/-docker-grey?style=plastic&logo=docker" alt=""/></a>
</p>
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/easier-docker)
![Static Badge](https://img.shields.io/badge/-docker-grey?logo=docker)
![GitHub License](https://img.shields.io/github/license/touero/easier-docker)
![PyPI - License](https://img.shields.io/pypi/l/easier-docker)
![PyPI - Downloads](https://img.shields.io/pypi/dm/easier-docker)
![GitHub last commit](https://img.shields.io/github/last-commit/touero/easier-docker)
[![Upload Package To PyPI](https://github.com/touero/easier-docker/actions/workflows/python-publish.yml/badge.svg?branch=master)](https://github.com/touero/easier-docker/actions/workflows/python-publish.yml)


## Repository Introduction
Expand All @@ -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.


Expand All @@ -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.


Expand Down
37 changes: 37 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]"}
]


[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"
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ PyYAML~=6.0.1
wheel~=0.42.0
twine~=4.0.2
coverage==7.4.4
build

41 changes: 0 additions & 41 deletions setup.py

This file was deleted.

176 changes: 90 additions & 86 deletions tests/test_easier_docker.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 0b78ca8

Please sign in to comment.