From 2a5f354b502fe0624239f8b2607cc624857027cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Fri, 15 Dec 2023 10:40:27 +0100 Subject: [PATCH 01/27] Bump default API version to 1.43 (Moby 24.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Gronowski --- Makefile | 4 ++-- docker/constants.py | 2 +- tests/Dockerfile-ssh-dind | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 79486e3ec..00ebca05c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -TEST_API_VERSION ?= 1.41 -TEST_ENGINE_VERSION ?= 20.10 +TEST_API_VERSION ?= 1.43 +TEST_ENGINE_VERSION ?= 24.0 ifeq ($(OS),Windows_NT) PLATFORM := Windows diff --git a/docker/constants.py b/docker/constants.py index ed341a902..71e543e53 100644 --- a/docker/constants.py +++ b/docker/constants.py @@ -1,7 +1,7 @@ import sys from .version import __version__ -DEFAULT_DOCKER_API_VERSION = '1.41' +DEFAULT_DOCKER_API_VERSION = '1.43' MINIMUM_DOCKER_API_VERSION = '1.21' DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 diff --git a/tests/Dockerfile-ssh-dind b/tests/Dockerfile-ssh-dind index 0da15aa40..2b7332b8b 100644 --- a/tests/Dockerfile-ssh-dind +++ b/tests/Dockerfile-ssh-dind @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 -ARG API_VERSION=1.41 -ARG ENGINE_VERSION=20.10 +ARG API_VERSION=1.43 +ARG ENGINE_VERSION=24.0 FROM docker:${ENGINE_VERSION}-dind From 0fad869cc62829b6e51898dc8e2ca0832aec6d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Tue, 19 Dec 2023 10:25:34 +0100 Subject: [PATCH 02/27] integration/commit: Don't check for deprecated fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Container related Image fields (`Container` and `ContainerConfig`) will be deprecated in API v1.44 and will be removed in v1.45. Signed-off-by: Paweł Gronowski --- tests/integration/api_image_test.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/integration/api_image_test.py b/tests/integration/api_image_test.py index 7081b53b8..5c3721958 100644 --- a/tests/integration/api_image_test.py +++ b/tests/integration/api_image_test.py @@ -85,13 +85,8 @@ def test_commit(self): img_id = res['Id'] self.tmp_imgs.append(img_id) img = self.client.inspect_image(img_id) - assert 'Container' in img - assert img['Container'].startswith(id) - assert 'ContainerConfig' in img - assert 'Image' in img['ContainerConfig'] - assert TEST_IMG == img['ContainerConfig']['Image'] - busybox_id = self.client.inspect_image(TEST_IMG)['Id'] assert 'Parent' in img + busybox_id = self.client.inspect_image(TEST_IMG)['Id'] assert img['Parent'] == busybox_id def test_commit_with_changes(self): @@ -103,8 +98,6 @@ def test_commit_with_changes(self): ) self.tmp_imgs.append(img_id) img = self.client.inspect_image(img_id) - assert 'Container' in img - assert img['Container'].startswith(cid['Id']) assert '8000/tcp' in img['Config']['ExposedPorts'] assert img['Config']['Cmd'] == ['bash'] From 1784cc2962529dac8ea9aa8f3ecc5798f600b6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Fri, 22 Dec 2023 10:20:12 +0100 Subject: [PATCH 03/27] utils: Fix datetime_to_timestamp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace usage of deprecated function `datetime.utcfromtimestamp` and make sure the input date is UTC before subtracting. Signed-off-by: Paweł Gronowski --- docker/utils/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/utils/utils.py b/docker/utils/utils.py index 759ddd2f1..dbd513030 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -5,7 +5,7 @@ import os.path import shlex import string -from datetime import datetime +from datetime import datetime, timezone from packaging.version import Version from .. import errors @@ -394,8 +394,8 @@ def convert_filters(filters): def datetime_to_timestamp(dt): - """Convert a UTC datetime to a Unix timestamp""" - delta = dt - datetime.utcfromtimestamp(0) + """Convert a datetime to a Unix timestamp""" + delta = dt.astimezone(timezone.utc) - datetime(1970, 1, 1, tzinfo=timezone.utc) return delta.seconds + delta.days * 24 * 3600 From 3ec5a6849a6cad4c5f5f3bafb5f74b5827fec14c Mon Sep 17 00:00:00 2001 From: Sven Date: Wed, 3 Jan 2024 16:48:45 +0100 Subject: [PATCH 04/27] fix(build): tag regex should allow ports (#3196) Update the regex and add test cases. (There are some xfails here for cases that the regex is not currently handling. It's too strict for IPv6 domains at the moment.) Closes: https://github.com/docker/docker-py/issues/3195 Related: https://github.com/opencontainers/distribution-spec/pull/498 Signed-off-by: Sven Kieske Signed-off-by: Milas Bowman Co-authored-by: Milas Bowman --- docker/utils/build.py | 5 ++-- tests/unit/utils_build_test.py | 53 ++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/docker/utils/build.py b/docker/utils/build.py index a5c4b0c2d..86a4423f0 100644 --- a/docker/utils/build.py +++ b/docker/utils/build.py @@ -10,8 +10,9 @@ _SEP = re.compile('/|\\\\') if IS_WINDOWS_PLATFORM else re.compile('/') _TAG = re.compile( - r"^[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*" \ - + "(:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127})?$" + r"^[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*" + r"(?::[0-9]+)?(/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*" + r"(:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127})?$" ) diff --git a/tests/unit/utils_build_test.py b/tests/unit/utils_build_test.py index fa7d833de..5f1bb1ec0 100644 --- a/tests/unit/utils_build_test.py +++ b/tests/unit/utils_build_test.py @@ -6,11 +6,10 @@ import tempfile import unittest +import pytest from docker.constants import IS_WINDOWS_PLATFORM -from docker.utils import exclude_paths, tar - -import pytest +from docker.utils import exclude_paths, tar, match_tag from ..helpers import make_tree @@ -489,3 +488,51 @@ def test_tar_directory_link(self): assert member in names assert 'a/c/b' in names assert 'a/c/b/utils.py' not in names + + +# selected test cases from https://github.com/distribution/reference/blob/8507c7fcf0da9f570540c958ea7b972c30eeaeca/reference_test.go#L13-L328 +@pytest.mark.parametrize("tag,expected", [ + ("test_com", True), + ("test.com:tag", True), + # N.B. this implicitly means "docker.io/library/test.com:5000" + # i.e. the `5000` is a tag, not a port here! + ("test.com:5000", True), + ("test.com/repo:tag", True), + ("test:5000/repo", True), + ("test:5000/repo:tag", True), + ("test:5000/repo", True), + ("", False), + (":justtag", False), + ("Uppercase:tag", False), + ("test:5000/Uppercase/lowercase:tag", False), + ("lowercase:Uppercase", True), + # length limits not enforced + pytest.param("a/"*128 + "a:tag", False, marks=pytest.mark.xfail), + ("a/"*127 + "a:tag-puts-this-over-max", True), + ("aa/asdf$$^/aa", False), + ("sub-dom1.foo.com/bar/baz/quux", True), + ("sub-dom1.foo.com/bar/baz/quux:some-long-tag", True), + ("b.gcr.io/test.example.com/my-app:test.example.com", True), + ("xn--n3h.com/myimage:xn--n3h.com", True), + ("foo_bar.com:8080", True), + ("foo/foo_bar.com:8080", True), + ("192.168.1.1", True), + ("192.168.1.1:tag", True), + ("192.168.1.1:5000", True), + ("192.168.1.1/repo", True), + ("192.168.1.1:5000/repo", True), + ("192.168.1.1:5000/repo:5050", True), + # regex does not properly handle ipv6 + pytest.param("[2001:db8::1]", False, marks=pytest.mark.xfail), + ("[2001:db8::1]:5000", False), + pytest.param("[2001:db8::1]/repo", True, marks=pytest.mark.xfail), + pytest.param("[2001:db8:1:2:3:4:5:6]/repo:tag", True, marks=pytest.mark.xfail), + pytest.param("[2001:db8::1]:5000/repo", True, marks=pytest.mark.xfail), + pytest.param("[2001:db8::1]:5000/repo:tag", True, marks=pytest.mark.xfail), + pytest.param("[2001:db8::]:5000/repo", True, marks=pytest.mark.xfail), + pytest.param("[::1]:5000/repo", True, marks=pytest.mark.xfail), + ("[fe80::1%eth0]:5000/repo", False), + ("[fe80::1%@invalidzone]:5000/repo", False), +]) +def test_match_tag(tag: str, expected: bool): + assert match_tag(tag) == expected From b8a6987cd5fc254a71db4505398479ceef2d15c8 Mon Sep 17 00:00:00 2001 From: Khushiyant Date: Thu, 4 Jan 2024 00:14:53 +0530 Subject: [PATCH 05/27] fix: keyerror when creating new config (#3200) Closes #3110. --------- Signed-off-by: Khushiyant --- docker/models/configs.py | 1 + tests/unit/fake_api.py | 9 +++++++++ tests/unit/fake_api_client.py | 1 + tests/unit/models_configs_test.py | 10 ++++++++++ 4 files changed, 21 insertions(+) create mode 100644 tests/unit/models_configs_test.py diff --git a/docker/models/configs.py b/docker/models/configs.py index 3588c8b5d..5ef137784 100644 --- a/docker/models/configs.py +++ b/docker/models/configs.py @@ -30,6 +30,7 @@ class ConfigCollection(Collection): def create(self, **kwargs): obj = self.client.api.create_config(**kwargs) + obj.setdefault("Spec", {})["Name"] = kwargs.get("name") return self.prepare_model(obj) create.__doc__ = APIClient.create_config.__doc__ diff --git a/tests/unit/fake_api.py b/tests/unit/fake_api.py index 0524becdc..03e53cc64 100644 --- a/tests/unit/fake_api.py +++ b/tests/unit/fake_api.py @@ -19,6 +19,8 @@ FAKE_NODE_ID = '24ifsmvkjbyhk' FAKE_SECRET_ID = 'epdyrw4tsi03xy3deu8g8ly6o' FAKE_SECRET_NAME = 'super_secret' +FAKE_CONFIG_ID = 'sekvs771242jfdjnvfuds8232' +FAKE_CONFIG_NAME = 'super_config' # Each method is prefixed with HTTP method (get, post...) # for clarity and readability @@ -512,6 +514,11 @@ def post_fake_secret(): response = {'ID': FAKE_SECRET_ID} return status_code, response +def post_fake_config(): + status_code = 200 + response = {'ID': FAKE_CONFIG_ID} + return status_code, response + # Maps real api url to fake response callback prefix = 'http+docker://localhost' @@ -630,4 +637,6 @@ def post_fake_secret(): post_fake_network_disconnect, f'{prefix}/{CURRENT_VERSION}/secrets/create': post_fake_secret, + f'{prefix}/{CURRENT_VERSION}/configs/create': + post_fake_config, } diff --git a/tests/unit/fake_api_client.py b/tests/unit/fake_api_client.py index 95cf63b49..797994216 100644 --- a/tests/unit/fake_api_client.py +++ b/tests/unit/fake_api_client.py @@ -37,6 +37,7 @@ def make_fake_api_client(overrides=None): 'create_host_config.side_effect': api_client.create_host_config, 'create_network.return_value': fake_api.post_fake_network()[1], 'create_secret.return_value': fake_api.post_fake_secret()[1], + 'create_config.return_value': fake_api.post_fake_config()[1], 'exec_create.return_value': fake_api.post_fake_exec_create()[1], 'exec_start.return_value': fake_api.post_fake_exec_start()[1], 'images.return_value': fake_api.get_fake_images()[1], diff --git a/tests/unit/models_configs_test.py b/tests/unit/models_configs_test.py new file mode 100644 index 000000000..6960397ff --- /dev/null +++ b/tests/unit/models_configs_test.py @@ -0,0 +1,10 @@ +import unittest + +from .fake_api_client import make_fake_client +from .fake_api import FAKE_CONFIG_NAME + +class CreateConfigsTest(unittest.TestCase): + def test_create_config(self): + client = make_fake_client() + config = client.configs.create(name="super_config", data="config") + assert config.__repr__() == "".format(FAKE_CONFIG_NAME) From 08956b5fbc1adc76681fea308526a9c77065c22b Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 3 Jan 2024 20:49:07 +0200 Subject: [PATCH 06/27] ci: update Ruff & fix some minor issues (#3206) Signed-off-by: Aarni Koskela --- .github/workflows/ci.yml | 2 +- docker/models/containers.py | 6 +++--- docker/utils/socket.py | 4 ++-- docker/utils/utils.py | 2 +- pyproject.toml | 4 +++- test-requirements.txt | 2 +- tests/integration/api_build_test.py | 4 +--- tests/ssh/api_build_test.py | 4 +--- tests/unit/api_test.py | 2 +- 9 files changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 127d5b682..628c53504 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/setup-python@v4 with: python-version: '3.x' - - run: pip install -U ruff==0.0.284 + - run: pip install -U ruff==0.1.8 - name: Run ruff run: ruff docker tests diff --git a/docker/models/containers.py b/docker/models/containers.py index 4725d6f6f..32676bb85 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -903,9 +903,9 @@ def run(self, image, command=None, stdout=True, stderr=False, container, exit_status, command, image, out ) - return out if stream or out is None else b''.join( - [line for line in out] - ) + if stream or out is None: + return out + return b''.join(out) def create(self, image, command=None, **kwargs): """ diff --git a/docker/utils/socket.py b/docker/utils/socket.py index 2306ed073..c7cb584d4 100644 --- a/docker/utils/socket.py +++ b/docker/utils/socket.py @@ -64,7 +64,7 @@ def read_exactly(socket, n): Reads exactly n bytes from socket Raises SocketError if there isn't enough data """ - data = bytes() + data = b"" while len(data) < n: next_data = read(socket, n - len(data)) if not next_data: @@ -152,7 +152,7 @@ def consume_socket_output(frames, demux=False): if demux is False: # If the streams are multiplexed, the generator returns strings, that # we just need to concatenate. - return bytes().join(frames) + return b"".join(frames) # If the streams are demultiplexed, the generator yields tuples # (stdout, stderr) diff --git a/docker/utils/utils.py b/docker/utils/utils.py index dbd513030..efe9f9a3c 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -152,7 +152,7 @@ def convert_volume_binds(binds): ] if 'propagation' in v and v['propagation'] in propagation_modes: if mode: - mode = ','.join([mode, v['propagation']]) + mode = f"{mode},{v['propagation']}" else: mode = v['propagation'] diff --git a/pyproject.toml b/pyproject.toml index 0a6727966..a64e120ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,14 +5,16 @@ requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"] write_to = 'docker/_version.py' [tool.ruff] -target-version = "py37" +target-version = "py38" extend-select = [ "B", "C", "F", + "UP", "W", ] ignore = [ + "UP012", # unnecessary `UTF-8` argument (we want to be explicit) "C901", # too complex (there's a whole bunch of these) ] diff --git a/test-requirements.txt b/test-requirements.txt index 031d0acf0..2c0e3622c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,6 @@ setuptools==65.5.1 coverage==7.2.7 -ruff==0.0.284 +ruff==0.1.8 pytest==7.4.2 pytest-cov==4.1.0 pytest-timeout==2.1.0 diff --git a/tests/integration/api_build_test.py b/tests/integration/api_build_test.py index 2add2d87a..540ef2b0f 100644 --- a/tests/integration/api_build_test.py +++ b/tests/integration/api_build_test.py @@ -389,9 +389,7 @@ def test_build_stderr_data(self): lines = [] for chunk in stream: lines.append(chunk.get('stream')) - expected = '{0}{2}\n{1}'.format( - control_chars[0], control_chars[1], snippet - ) + expected = f'{control_chars[0]}{snippet}\n{control_chars[1]}' assert any(line == expected for line in lines) def test_build_gzip_encoding(self): diff --git a/tests/ssh/api_build_test.py b/tests/ssh/api_build_test.py index d060f465f..3b542994f 100644 --- a/tests/ssh/api_build_test.py +++ b/tests/ssh/api_build_test.py @@ -380,9 +380,7 @@ def test_build_stderr_data(self): lines = [] for chunk in stream: lines.append(chunk.get('stream')) - expected = '{0}{2}\n{1}'.format( - control_chars[0], control_chars[1], snippet - ) + expected = f'{control_chars[0]}{snippet}\n{control_chars[1]}' assert any(line == expected for line in lines) def test_build_gzip_encoding(self): diff --git a/tests/unit/api_test.py b/tests/unit/api_test.py index 7bc2ea8cd..0ca9bbb95 100644 --- a/tests/unit/api_test.py +++ b/tests/unit/api_test.py @@ -82,7 +82,7 @@ def fake_delete(self, url, *args, **kwargs): def fake_read_from_socket(self, response, stream, tty=False, demux=False): - return bytes() + return b'' url_base = f'{fake_api.prefix}/' From eeb9ea1937ba5e67087551bdabed858fab5e0ef3 Mon Sep 17 00:00:00 2001 From: Khushiyant Date: Thu, 4 Jan 2024 00:26:10 +0530 Subject: [PATCH 07/27] docs: change image.history() return type to list (#3202) Fixes #3076. Signed-off-by: Khushiyant --- docker/api/image.py | 2 +- docker/models/images.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/api/image.py b/docker/api/image.py index 5e1466ec3..85109473b 100644 --- a/docker/api/image.py +++ b/docker/api/image.py @@ -47,7 +47,7 @@ def history(self, image): image (str): The image to show history for Returns: - (str): The history of the image + (list): The history of the image Raises: :py:class:`docker.errors.APIError` diff --git a/docker/models/images.py b/docker/models/images.py index b4777d8da..4f058d24d 100644 --- a/docker/models/images.py +++ b/docker/models/images.py @@ -51,7 +51,7 @@ def history(self): Show the history of an image. Returns: - (str): The history of the image. + (list): The history of the image. Raises: :py:class:`docker.errors.APIError` From 694d9792e6c5f89b9fc6fd4d32a80216d5129ef7 Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Wed, 3 Jan 2024 14:01:42 -0500 Subject: [PATCH 08/27] lint: fix string formatting (#3211) Merged a linter upgrade along with an older PR, so this was immediately in violation Signed-off-by: Milas Bowman --- tests/unit/models_configs_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/models_configs_test.py b/tests/unit/models_configs_test.py index 6960397ff..5d52daf76 100644 --- a/tests/unit/models_configs_test.py +++ b/tests/unit/models_configs_test.py @@ -7,4 +7,4 @@ class CreateConfigsTest(unittest.TestCase): def test_create_config(self): client = make_fake_client() config = client.configs.create(name="super_config", data="config") - assert config.__repr__() == "".format(FAKE_CONFIG_NAME) + assert config.__repr__() == f"" From 249654d4d9c260c84350deeca3e77c7c76669663 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 21 Dec 2023 10:20:32 +0200 Subject: [PATCH 09/27] Drop `packaging` dependency Compare versions like Moby (api/types/versions/compare.go) Signed-off-by: Aarni Koskela --- docker/utils/utils.py | 24 ++++++++++++++++-------- setup.py | 1 - tests/unit/utils_test.py | 27 ++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/docker/utils/utils.py b/docker/utils/utils.py index efe9f9a3c..e1e064385 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -6,7 +6,8 @@ import shlex import string from datetime import datetime, timezone -from packaging.version import Version +from functools import lru_cache +from itertools import zip_longest from .. import errors from ..constants import DEFAULT_HTTP_HOST @@ -43,6 +44,7 @@ def decode_json_header(header): return json.loads(data) +@lru_cache(maxsize=None) def compare_version(v1, v2): """Compare docker versions @@ -55,14 +57,20 @@ def compare_version(v1, v2): >>> compare_version(v2, v2) 0 """ - s1 = Version(v1) - s2 = Version(v2) - if s1 == s2: + if v1 == v2: return 0 - elif s1 > s2: - return -1 - else: - return 1 + # Split into `sys.version_info` like tuples. + s1 = tuple(int(p) for p in v1.split('.')) + s2 = tuple(int(p) for p in v2.split('.')) + # Compare each component, padding with 0 if necessary. + for c1, c2 in zip_longest(s1, s2, fillvalue=0): + if c1 == c2: + continue + elif c1 > c2: + return -1 + else: + return 1 + return 0 def version_lt(v1, v2): diff --git a/setup.py b/setup.py index b6a024f81..3d3313924 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,6 @@ SOURCE_DIR = os.path.join(ROOT_DIR) requirements = [ - 'packaging >= 14.0', 'requests >= 2.26.0', 'urllib3 >= 1.26.0', ] diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py index de79e3037..c9434e11b 100644 --- a/tests/unit/utils_test.py +++ b/tests/unit/utils_test.py @@ -10,12 +10,13 @@ from docker.api.client import APIClient from docker.constants import IS_WINDOWS_PLATFORM, DEFAULT_DOCKER_API_VERSION from docker.errors import DockerException -from docker.utils import (convert_filters, convert_volume_binds, - decode_json_header, kwargs_from_env, parse_bytes, - parse_devices, parse_env_file, parse_host, - parse_repository_tag, split_command, update_headers) +from docker.utils import ( + compare_version, convert_filters, convert_volume_binds, decode_json_header, + format_environment, kwargs_from_env, parse_bytes, parse_devices, + parse_env_file, parse_host, parse_repository_tag, split_command, + update_headers, version_gte, version_lt +) from docker.utils.ports import build_port_bindings, split_port -from docker.utils.utils import format_environment TEST_CERT_DIR = os.path.join( os.path.dirname(__file__), @@ -629,3 +630,19 @@ def test_format_env_no_value(self): 'BAR': '', } assert sorted(format_environment(env_dict)) == ['BAR=', 'FOO'] + + +def test_compare_versions(): + assert compare_version('1.0', '1.1') == 1 + assert compare_version('1.10', '1.1') == -1 + assert compare_version('1.10', '1.10') == 0 + assert compare_version('1.10.0', '1.10.1') == 1 + assert compare_version('1.9', '1.10') == 1 + assert compare_version('1.9.1', '1.10') == 1 + # Test comparison helpers + assert version_lt('1.0', '1.27') + assert version_gte('1.27', '1.20') + # Test zero-padding + assert compare_version('1', '1.0') == 0 + assert compare_version('1.10', '1.10.1') == 1 + assert compare_version('1.10.0', '1.10') == 0 From f128956034b2df1f8741e4c0246f79e1e2598146 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 21 Dec 2023 10:35:28 +0200 Subject: [PATCH 10/27] Use `build` instead of calling setup.py Signed-off-by: Aarni Koskela --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 721020ac3..6987fce0d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,10 +26,10 @@ jobs: with: python-version: '3.x' - - name: Generate Pacakge + - name: Generate Package run: | - pip3 install setuptools wheel - python setup.py sdist bdist_wheel + pip3 install build + python -m build . env: SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DOCKER: ${{ inputs.tag }} From ae45d477c44b880cdc7155f8829e0cdea84d0033 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 21 Dec 2023 10:37:23 +0200 Subject: [PATCH 11/27] Use `hatch` for packaging Signed-off-by: Aarni Koskela --- .github/workflows/release.yml | 2 + pyproject.toml | 64 +++++++++++++++++++++++++-- setup.cfg | 3 -- setup.py | 82 ----------------------------------- 4 files changed, 63 insertions(+), 88 deletions(-) delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6987fce0d..953b59bf7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,8 @@ jobs: pip3 install build python -m build . env: + # This is also supported by Hatch; see + # https://github.com/ofek/hatch-vcs#version-source-environment-variables SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DOCKER: ${{ inputs.tag }} - name: Publish to PyPI diff --git a/pyproject.toml b/pyproject.toml index a64e120ee..83d22b801 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,66 @@ [build-system] -requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" -[tool.setuptools_scm] -write_to = 'docker/_version.py' +[project] +name = "docker" +dynamic = ["version"] +description = "A Python library for the Docker Engine API." +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.8" +maintainers = [ + { name = "Docker Inc.", email = "no-reply@docker.com" }, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Other Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "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", + "Topic :: Software Development", + "Topic :: Utilities", +] + +dependencies = [ + "requests >= 2.26.0", + "urllib3 >= 1.26.0", + "pywin32>=304; sys_platform == \"win32\"", +] + +[project.optional-dependencies] +ssh = [ + "paramiko>=2.4.3", +] +tls = [] # kept for backwards compatibility +websockets = [ + "websocket-client >= 1.3.0", +] + +[project.urls] +Changelog = "https://docker-py.readthedocs.io/en/stable/change-log.html" +Documentation = "https://docker-py.readthedocs.io" +Homepage = "https://github.com/docker/docker-py" +Source = "https://github.com/docker/docker-py" +Tracker = "https://github.com/docker/docker-py/issues" + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.hooks.vcs] +version-file = "docker/_version.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/docker", +] [tool.ruff] target-version = "py38" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index a37e5521d..000000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[metadata] -description_file = README.rst -license = Apache License 2.0 diff --git a/setup.py b/setup.py deleted file mode 100644 index 3d3313924..000000000 --- a/setup.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python - -import codecs -import os - -from setuptools import find_packages -from setuptools import setup - -ROOT_DIR = os.path.dirname(__file__) -SOURCE_DIR = os.path.join(ROOT_DIR) - -requirements = [ - 'requests >= 2.26.0', - 'urllib3 >= 1.26.0', -] - -extras_require = { - # win32 APIs if on Windows (required for npipe support) - ':sys_platform == "win32"': 'pywin32>=304', - - # This is now a no-op, as similarly the requests[security] extra is - # a no-op as of requests 2.26.0, this is always available/by default now - # see https://github.com/psf/requests/pull/5867 - 'tls': [], - - # Only required when connecting using the ssh:// protocol - 'ssh': ['paramiko>=2.4.3'], - - # Only required when using websockets - 'websockets': ['websocket-client >= 1.3.0'], -} - -with open('./test-requirements.txt') as test_reqs_txt: - test_requirements = list(test_reqs_txt) - - -long_description = '' -with codecs.open('./README.md', encoding='utf-8') as readme_md: - long_description = readme_md.read() - -setup( - name="docker", - use_scm_version={ - 'write_to': 'docker/_version.py' - }, - description="A Python library for the Docker Engine API.", - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/docker/docker-py', - project_urls={ - 'Documentation': 'https://docker-py.readthedocs.io', - 'Changelog': 'https://docker-py.readthedocs.io/en/stable/change-log.html', - 'Source': 'https://github.com/docker/docker-py', - 'Tracker': 'https://github.com/docker/docker-py/issues', - }, - packages=find_packages(exclude=["tests.*", "tests"]), - setup_requires=['setuptools_scm'], - install_requires=requirements, - tests_require=test_requirements, - extras_require=extras_require, - python_requires='>=3.8', - zip_safe=False, - test_suite='tests', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Other Environment', - 'Intended Audience :: Developers', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - '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', - 'Topic :: Software Development', - 'Topic :: Utilities', - 'License :: OSI Approved :: Apache Software License', - ], - maintainer='Docker, Inc.', - maintainer_email='no-reply@docker.com', -) From 047df6b0d31250d5d15ce25d1252ea7a04c7fcd4 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 21 Dec 2023 10:51:05 +0200 Subject: [PATCH 12/27] Build wheel in CI, upload artifact for perusal Signed-off-by: Aarni Koskela --- .github/workflows/ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 628c53504..9eb450a6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,19 @@ jobs: - name: Run ruff run: ruff docker tests + build: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + - run: pip3 install build && python -m build . + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist + unit-tests: runs-on: ubuntu-latest strategy: From d50cc429c2adc61c0af5275972bd740f846d55fe Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 3 Jan 2024 21:23:04 +0200 Subject: [PATCH 13/27] Enable Ruff I (import sort), autofix Signed-off-by: Aarni Koskela --- docker/__init__.py | 3 +- docker/api/build.py | 6 +--- docker/api/client.py | 22 +++++++++++---- docker/api/container.py | 15 +++++----- docker/api/exec_api.py | 3 +- docker/api/network.py | 5 ++-- docker/api/secret.py | 3 +- docker/api/swarm.py | 7 ++--- docker/api/volume.py | 3 +- docker/auth.py | 3 +- docker/client.py | 2 +- docker/constants.py | 1 + docker/context/__init__.py | 2 +- docker/context/api.py | 10 ++++--- docker/context/config.py | 7 ++--- docker/context/context.py | 15 ++++++---- docker/credentials/__init__.py | 4 +-- docker/credentials/store.py | 3 +- docker/models/configs.py | 2 +- docker/models/containers.py | 11 +++++--- docker/models/networks.py | 2 +- docker/models/nodes.py | 2 +- docker/models/secrets.py | 2 +- docker/models/services.py | 8 ++++-- docker/models/swarm.py | 1 + docker/models/volumes.py | 2 +- docker/transport/__init__.py | 1 + docker/transport/npipeconn.py | 7 +++-- docker/transport/npipesocket.py | 8 +++--- docker/transport/sshconn.py | 15 +++++----- docker/transport/unixconn.py | 8 +++--- docker/types/__init__.py | 27 ++++++++++++------ docker/types/containers.py | 14 ++++++++-- docker/types/services.py | 8 ++++-- docker/utils/__init__.py | 31 +++++++++++++++------ docker/utils/build.py | 3 +- docker/utils/json_stream.py | 1 - docker/utils/utils.py | 13 +++++---- docker/version.py | 2 +- docs/conf.py | 1 + pyproject.toml | 1 + setup.py | 3 +- tests/helpers.py | 3 +- tests/integration/api_build_test.py | 6 ++-- tests/integration/api_config_test.py | 3 +- tests/integration/api_container_test.py | 18 ++++++------ tests/integration/api_exec_test.py | 15 +++++----- tests/integration/api_healthcheck_test.py | 2 +- tests/integration/api_image_test.py | 7 ++--- tests/integration/api_network_test.py | 5 ++-- tests/integration/api_plugin_test.py | 5 ++-- tests/integration/api_secret_test.py | 3 +- tests/integration/api_service_test.py | 9 +++--- tests/integration/api_swarm_test.py | 4 ++- tests/integration/api_volume_test.py | 3 +- tests/integration/base.py | 3 +- tests/integration/client_test.py | 3 +- tests/integration/conftest.py | 3 +- tests/integration/context_api_test.py | 3 ++ tests/integration/credentials/store_test.py | 7 +++-- tests/integration/credentials/utils_test.py | 2 +- tests/integration/errors_test.py | 6 ++-- tests/integration/models_containers_test.py | 7 ++--- tests/integration/models_images_test.py | 5 ++-- tests/integration/models_networks_test.py | 3 +- tests/integration/models_resources_test.py | 3 +- tests/integration/models_services_test.py | 7 +++-- tests/integration/models_swarm_test.py | 3 +- tests/integration/models_volumes_test.py | 3 +- tests/integration/regression_test.py | 5 ++-- tests/ssh/api_build_test.py | 6 ++-- tests/ssh/base.py | 3 +- tests/ssh/connect_test.py | 4 ++- tests/unit/api_container_test.py | 15 ++++++---- tests/unit/api_exec_test.py | 5 +++- tests/unit/api_image_test.py | 15 ++++++---- tests/unit/api_network_test.py | 5 ++-- tests/unit/api_test.py | 10 +++---- tests/unit/api_volume_test.py | 2 +- tests/unit/auth_test.py | 5 ++-- tests/unit/client_test.py | 11 +++++--- tests/unit/context_test.py | 10 +++---- tests/unit/dockertypes_test.py | 13 +++++++-- tests/unit/errors_test.py | 11 ++++++-- tests/unit/fake_api_client.py | 3 +- tests/unit/models_configs_test.py | 3 +- tests/unit/models_containers_test.py | 6 ++-- tests/unit/models_networks_test.py | 2 +- tests/unit/models_secrets_test.py | 2 +- tests/unit/models_services_test.py | 1 + tests/unit/sshadapter_test.py | 1 + tests/unit/swarm_test.py | 4 +-- tests/unit/utils_build_test.py | 2 +- tests/unit/utils_config_test.py | 8 +++--- tests/unit/utils_json_stream_test.py | 2 +- tests/unit/utils_test.py | 22 +++++++++++---- 96 files changed, 364 insertions(+), 240 deletions(-) diff --git a/docker/__init__.py b/docker/__init__.py index c1c518c56..fb7a5e921 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -1,7 +1,6 @@ from .api import APIClient from .client import DockerClient, from_env -from .context import Context -from .context import ContextAPI +from .context import Context, ContextAPI from .tls import TLSConfig from .version import __version__ diff --git a/docker/api/build.py b/docker/api/build.py index abd5ab52a..47216a58f 100644 --- a/docker/api/build.py +++ b/docker/api/build.py @@ -3,11 +3,7 @@ import os import random -from .. import auth -from .. import constants -from .. import errors -from .. import utils - +from .. import auth, constants, errors, utils log = logging.getLogger(__name__) diff --git a/docker/api/client.py b/docker/api/client.py index 499a7c785..394ceb1f5 100644 --- a/docker/api/client.py +++ b/docker/api/client.py @@ -8,12 +8,22 @@ import requests.exceptions from .. import auth -from ..constants import (DEFAULT_NUM_POOLS, DEFAULT_NUM_POOLS_SSH, - DEFAULT_MAX_POOL_SIZE, DEFAULT_TIMEOUT_SECONDS, - DEFAULT_USER_AGENT, IS_WINDOWS_PLATFORM, - MINIMUM_DOCKER_API_VERSION, STREAM_HEADER_SIZE_BYTES) -from ..errors import (DockerException, InvalidVersion, TLSParameterError, - create_api_error_from_http_exception) +from ..constants import ( + DEFAULT_MAX_POOL_SIZE, + DEFAULT_NUM_POOLS, + DEFAULT_NUM_POOLS_SSH, + DEFAULT_TIMEOUT_SECONDS, + DEFAULT_USER_AGENT, + IS_WINDOWS_PLATFORM, + MINIMUM_DOCKER_API_VERSION, + STREAM_HEADER_SIZE_BYTES, +) +from ..errors import ( + DockerException, + InvalidVersion, + TLSParameterError, + create_api_error_from_http_exception, +) from ..tls import TLSConfig from ..transport import UnixHTTPAdapter from ..utils import check_resource, config, update_headers, utils diff --git a/docker/api/container.py b/docker/api/container.py index 5a267d13f..1f153eeb7 100644 --- a/docker/api/container.py +++ b/docker/api/container.py @@ -1,13 +1,14 @@ from datetime import datetime -from .. import errors -from .. import utils +from .. import errors, utils from ..constants import DEFAULT_DATA_CHUNK_SIZE -from ..types import CancellableStream -from ..types import ContainerConfig -from ..types import EndpointConfig -from ..types import HostConfig -from ..types import NetworkingConfig +from ..types import ( + CancellableStream, + ContainerConfig, + EndpointConfig, + HostConfig, + NetworkingConfig, +) class ContainerApiMixin: diff --git a/docker/api/exec_api.py b/docker/api/exec_api.py index 63df9e6c6..d8fc50dd3 100644 --- a/docker/api/exec_api.py +++ b/docker/api/exec_api.py @@ -1,5 +1,4 @@ -from .. import errors -from .. import utils +from .. import errors, utils from ..types import CancellableStream diff --git a/docker/api/network.py b/docker/api/network.py index dd4e3761a..2b1925710 100644 --- a/docker/api/network.py +++ b/docker/api/network.py @@ -1,7 +1,6 @@ -from ..errors import InvalidVersion -from ..utils import check_resource, minimum_version -from ..utils import version_lt from .. import utils +from ..errors import InvalidVersion +from ..utils import check_resource, minimum_version, version_lt class NetworkApiMixin: diff --git a/docker/api/secret.py b/docker/api/secret.py index cd440b95f..db1701bdc 100644 --- a/docker/api/secret.py +++ b/docker/api/secret.py @@ -1,7 +1,6 @@ import base64 -from .. import errors -from .. import utils +from .. import errors, utils class SecretApiMixin: diff --git a/docker/api/swarm.py b/docker/api/swarm.py index d09dd087b..d60d18b61 100644 --- a/docker/api/swarm.py +++ b/docker/api/swarm.py @@ -1,9 +1,8 @@ -import logging import http.client as http_client +import logging + +from .. import errors, types, utils from ..constants import DEFAULT_SWARM_ADDR_POOL, DEFAULT_SWARM_SUBNET_SIZE -from .. import errors -from .. import types -from .. import utils log = logging.getLogger(__name__) diff --git a/docker/api/volume.py b/docker/api/volume.py index 98b42a124..c6c036fad 100644 --- a/docker/api/volume.py +++ b/docker/api/volume.py @@ -1,5 +1,4 @@ -from .. import errors -from .. import utils +from .. import errors, utils class VolumeApiMixin: diff --git a/docker/auth.py b/docker/auth.py index 7a301ba40..96a6e3a65 100644 --- a/docker/auth.py +++ b/docker/auth.py @@ -2,8 +2,7 @@ import json import logging -from . import credentials -from . import errors +from . import credentials, errors from .utils import config INDEX_NAME = 'docker.io' diff --git a/docker/client.py b/docker/client.py index 2910c1259..9012d24c9 100644 --- a/docker/client.py +++ b/docker/client.py @@ -1,5 +1,5 @@ from .api.client import APIClient -from .constants import (DEFAULT_TIMEOUT_SECONDS, DEFAULT_MAX_POOL_SIZE) +from .constants import DEFAULT_MAX_POOL_SIZE, DEFAULT_TIMEOUT_SECONDS from .models.configs import ConfigCollection from .models.containers import ContainerCollection from .models.images import ImageCollection diff --git a/docker/constants.py b/docker/constants.py index 71e543e53..433e6c4e2 100644 --- a/docker/constants.py +++ b/docker/constants.py @@ -1,4 +1,5 @@ import sys + from .version import __version__ DEFAULT_DOCKER_API_VERSION = '1.43' diff --git a/docker/context/__init__.py b/docker/context/__init__.py index dbf172fda..46d462b0c 100644 --- a/docker/context/__init__.py +++ b/docker/context/__init__.py @@ -1,2 +1,2 @@ -from .context import Context from .api import ContextAPI +from .context import Context diff --git a/docker/context/api.py b/docker/context/api.py index 493f470e5..ae5d67bb2 100644 --- a/docker/context/api.py +++ b/docker/context/api.py @@ -2,11 +2,13 @@ import os from docker import errors -from docker.context.config import get_meta_dir -from docker.context.config import METAFILE -from docker.context.config import get_current_context_name -from docker.context.config import write_context_name_to_docker_config from docker.context import Context +from docker.context.config import ( + METAFILE, + get_current_context_name, + get_meta_dir, + write_context_name_to_docker_config, +) class ContextAPI: diff --git a/docker/context/config.py b/docker/context/config.py index 8c3fe2500..5a6373aa4 100644 --- a/docker/context/config.py +++ b/docker/context/config.py @@ -1,10 +1,9 @@ -import os -import json import hashlib +import json +import os from docker import utils -from docker.constants import IS_WINDOWS_PLATFORM -from docker.constants import DEFAULT_UNIX_SOCKET +from docker.constants import DEFAULT_UNIX_SOCKET, IS_WINDOWS_PLATFORM from docker.utils.config import find_config_file METAFILE = "meta.json" diff --git a/docker/context/context.py b/docker/context/context.py index 4faf8e701..317bcf61d 100644 --- a/docker/context/context.py +++ b/docker/context/context.py @@ -1,12 +1,15 @@ -import os import json +import os from shutil import copyfile, rmtree -from docker.tls import TLSConfig + +from docker.context.config import ( + get_context_host, + get_meta_dir, + get_meta_file, + get_tls_dir, +) from docker.errors import ContextException -from docker.context.config import get_meta_dir -from docker.context.config import get_meta_file -from docker.context.config import get_tls_dir -from docker.context.config import get_context_host +from docker.tls import TLSConfig class Context: diff --git a/docker/credentials/__init__.py b/docker/credentials/__init__.py index a1247700d..80d19e798 100644 --- a/docker/credentials/__init__.py +++ b/docker/credentials/__init__.py @@ -1,8 +1,8 @@ -from .store import Store -from .errors import StoreError, CredentialsNotFound from .constants import ( DEFAULT_LINUX_STORE, DEFAULT_OSX_STORE, DEFAULT_WIN32_STORE, PROGRAM_PREFIX, ) +from .errors import CredentialsNotFound, StoreError +from .store import Store diff --git a/docker/credentials/store.py b/docker/credentials/store.py index 4e63a5ba6..00d693a4b 100644 --- a/docker/credentials/store.py +++ b/docker/credentials/store.py @@ -4,8 +4,7 @@ import subprocess import warnings -from . import constants -from . import errors +from . import constants, errors from .utils import create_environment_dict diff --git a/docker/models/configs.py b/docker/models/configs.py index 5ef137784..4eba87f4e 100644 --- a/docker/models/configs.py +++ b/docker/models/configs.py @@ -1,5 +1,5 @@ from ..api import APIClient -from .resource import Model, Collection +from .resource import Collection, Model class Config(Model): diff --git a/docker/models/containers.py b/docker/models/containers.py index 32676bb85..35d8b3141 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -2,16 +2,19 @@ import ntpath from collections import namedtuple -from .images import Image -from .resource import Collection, Model from ..api import APIClient from ..constants import DEFAULT_DATA_CHUNK_SIZE from ..errors import ( - ContainerError, DockerException, ImageNotFound, - NotFound, create_unexpected_kwargs_error + ContainerError, + DockerException, + ImageNotFound, + NotFound, + create_unexpected_kwargs_error, ) from ..types import HostConfig, NetworkingConfig from ..utils import version_gte +from .images import Image +from .resource import Collection, Model class Container(Model): diff --git a/docker/models/networks.py b/docker/models/networks.py index f50287907..9b3ed7829 100644 --- a/docker/models/networks.py +++ b/docker/models/networks.py @@ -1,7 +1,7 @@ from ..api import APIClient from ..utils import version_gte from .containers import Container -from .resource import Model, Collection +from .resource import Collection, Model class Network(Model): diff --git a/docker/models/nodes.py b/docker/models/nodes.py index 8dd9350c0..2fa480c54 100644 --- a/docker/models/nodes.py +++ b/docker/models/nodes.py @@ -1,4 +1,4 @@ -from .resource import Model, Collection +from .resource import Collection, Model class Node(Model): diff --git a/docker/models/secrets.py b/docker/models/secrets.py index da01d44c8..38c48dc7e 100644 --- a/docker/models/secrets.py +++ b/docker/models/secrets.py @@ -1,5 +1,5 @@ from ..api import APIClient -from .resource import Model, Collection +from .resource import Collection, Model class Secret(Model): diff --git a/docker/models/services.py b/docker/models/services.py index 70037041a..09502633e 100644 --- a/docker/models/services.py +++ b/docker/models/services.py @@ -1,7 +1,9 @@ import copy -from docker.errors import create_unexpected_kwargs_error, InvalidArgument -from docker.types import TaskTemplate, ContainerSpec, Placement, ServiceMode -from .resource import Model, Collection + +from docker.errors import InvalidArgument, create_unexpected_kwargs_error +from docker.types import ContainerSpec, Placement, ServiceMode, TaskTemplate + +from .resource import Collection, Model class Service(Model): diff --git a/docker/models/swarm.py b/docker/models/swarm.py index 1e39f3fd2..271cc5dcb 100644 --- a/docker/models/swarm.py +++ b/docker/models/swarm.py @@ -1,5 +1,6 @@ from docker.api import APIClient from docker.errors import APIError + from .resource import Model diff --git a/docker/models/volumes.py b/docker/models/volumes.py index 3c2e83780..12c9f14b2 100644 --- a/docker/models/volumes.py +++ b/docker/models/volumes.py @@ -1,5 +1,5 @@ from ..api import APIClient -from .resource import Model, Collection +from .resource import Collection, Model class Volume(Model): diff --git a/docker/transport/__init__.py b/docker/transport/__init__.py index 07bc7fd58..8c68b1f6e 100644 --- a/docker/transport/__init__.py +++ b/docker/transport/__init__.py @@ -1,4 +1,5 @@ from .unixconn import UnixHTTPAdapter + try: from .npipeconn import NpipeHTTPAdapter from .npipesocket import NpipeSocket diff --git a/docker/transport/npipeconn.py b/docker/transport/npipeconn.py index d335d8718..fe740a5f8 100644 --- a/docker/transport/npipeconn.py +++ b/docker/transport/npipeconn.py @@ -1,13 +1,14 @@ import queue + import requests.adapters +import urllib3 +import urllib3.connection from docker.transport.basehttpadapter import BaseHTTPAdapter + from .. import constants from .npipesocket import NpipeSocket -import urllib3 -import urllib3.connection - RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer diff --git a/docker/transport/npipesocket.py b/docker/transport/npipesocket.py index 9cbe40cc7..d91938e76 100644 --- a/docker/transport/npipesocket.py +++ b/docker/transport/npipesocket.py @@ -1,12 +1,12 @@ import functools -import time import io +import time -import win32file -import win32pipe import pywintypes -import win32event import win32api +import win32event +import win32file +import win32pipe cERROR_PIPE_BUSY = 0xe7 cSECURITY_SQOS_PRESENT = 0x100000 diff --git a/docker/transport/sshconn.py b/docker/transport/sshconn.py index 6e1d0ee72..91671e920 100644 --- a/docker/transport/sshconn.py +++ b/docker/transport/sshconn.py @@ -1,19 +1,20 @@ -import paramiko -import queue -import urllib.parse -import requests.adapters import logging import os +import queue import signal import socket import subprocess +import urllib.parse -from docker.transport.basehttpadapter import BaseHTTPAdapter -from .. import constants - +import paramiko +import requests.adapters import urllib3 import urllib3.connection +from docker.transport.basehttpadapter import BaseHTTPAdapter + +from .. import constants + RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer diff --git a/docker/transport/unixconn.py b/docker/transport/unixconn.py index 09d373dd6..f88d29ebf 100644 --- a/docker/transport/unixconn.py +++ b/docker/transport/unixconn.py @@ -1,12 +1,12 @@ -import requests.adapters import socket -from docker.transport.basehttpadapter import BaseHTTPAdapter -from .. import constants - +import requests.adapters import urllib3 import urllib3.connection +from docker.transport.basehttpadapter import BaseHTTPAdapter + +from .. import constants RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer diff --git a/docker/types/__init__.py b/docker/types/__init__.py index 89f223893..fbe247210 100644 --- a/docker/types/__init__.py +++ b/docker/types/__init__.py @@ -1,13 +1,24 @@ -from .containers import ( - ContainerConfig, HostConfig, LogConfig, Ulimit, DeviceRequest -) +from .containers import ContainerConfig, DeviceRequest, HostConfig, LogConfig, Ulimit from .daemon import CancellableStream from .healthcheck import Healthcheck from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig from .services import ( - ConfigReference, ContainerSpec, DNSConfig, DriverConfig, EndpointSpec, - Mount, Placement, PlacementPreference, Privileges, Resources, - RestartPolicy, RollbackConfig, SecretReference, ServiceMode, TaskTemplate, - UpdateConfig, NetworkAttachmentConfig + ConfigReference, + ContainerSpec, + DNSConfig, + DriverConfig, + EndpointSpec, + Mount, + NetworkAttachmentConfig, + Placement, + PlacementPreference, + Privileges, + Resources, + RestartPolicy, + RollbackConfig, + SecretReference, + ServiceMode, + TaskTemplate, + UpdateConfig, ) -from .swarm import SwarmSpec, SwarmExternalCA +from .swarm import SwarmExternalCA, SwarmSpec diff --git a/docker/types/containers.py b/docker/types/containers.py index a28061383..598188a25 100644 --- a/docker/types/containers.py +++ b/docker/types/containers.py @@ -1,8 +1,16 @@ from .. import errors from ..utils.utils import ( - convert_port_bindings, convert_tmpfs_mounts, convert_volume_binds, - format_environment, format_extra_hosts, normalize_links, parse_bytes, - parse_devices, split_command, version_gte, version_lt, + convert_port_bindings, + convert_tmpfs_mounts, + convert_volume_binds, + format_environment, + format_extra_hosts, + normalize_links, + parse_bytes, + parse_devices, + split_command, + version_gte, + version_lt, ) from .base import DictType from .healthcheck import Healthcheck diff --git a/docker/types/services.py b/docker/types/services.py index 0b07c350e..821115411 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -1,8 +1,12 @@ from .. import errors from ..constants import IS_WINDOWS_PLATFORM from ..utils import ( - check_resource, format_environment, format_extra_hosts, parse_bytes, - split_command, convert_service_networks, + check_resource, + convert_service_networks, + format_environment, + format_extra_hosts, + parse_bytes, + split_command, ) diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py index b4bef7d47..c086a9f07 100644 --- a/docker/utils/__init__.py +++ b/docker/utils/__init__.py @@ -1,13 +1,28 @@ -from .build import match_tag, create_archive, exclude_paths, mkbuildcontext, tar +from .build import create_archive, exclude_paths, match_tag, mkbuildcontext, tar from .decorators import check_resource, minimum_version, update_headers from .utils import ( - compare_version, convert_port_bindings, convert_volume_binds, - parse_repository_tag, parse_host, - kwargs_from_env, convert_filters, datetime_to_timestamp, - create_host_config, parse_bytes, parse_env_file, version_lt, - version_gte, decode_json_header, split_command, create_ipam_config, - create_ipam_pool, parse_devices, normalize_links, convert_service_networks, - format_environment, format_extra_hosts + compare_version, + convert_filters, + convert_port_bindings, + convert_service_networks, + convert_volume_binds, + create_host_config, + create_ipam_config, + create_ipam_pool, + datetime_to_timestamp, + decode_json_header, + format_environment, + format_extra_hosts, + kwargs_from_env, + normalize_links, + parse_bytes, + parse_devices, + parse_env_file, + parse_host, + parse_repository_tag, + split_command, + version_gte, + version_lt, ) diff --git a/docker/utils/build.py b/docker/utils/build.py index 86a4423f0..b84139104 100644 --- a/docker/utils/build.py +++ b/docker/utils/build.py @@ -4,9 +4,8 @@ import tarfile import tempfile -from .fnmatch import fnmatch from ..constants import IS_WINDOWS_PLATFORM - +from .fnmatch import fnmatch _SEP = re.compile('/|\\\\') if IS_WINDOWS_PLATFORM else re.compile('/') _TAG = re.compile( diff --git a/docker/utils/json_stream.py b/docker/utils/json_stream.py index 266193e56..41d25920c 100644 --- a/docker/utils/json_stream.py +++ b/docker/utils/json_stream.py @@ -3,7 +3,6 @@ from ..errors import StreamParseError - json_decoder = json.JSONDecoder() diff --git a/docker/utils/utils.py b/docker/utils/utils.py index e1e064385..f36a3afb8 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -8,16 +8,17 @@ from datetime import datetime, timezone from functools import lru_cache from itertools import zip_longest +from urllib.parse import urlparse, urlunparse from .. import errors -from ..constants import DEFAULT_HTTP_HOST -from ..constants import DEFAULT_UNIX_SOCKET -from ..constants import DEFAULT_NPIPE -from ..constants import BYTE_UNITS +from ..constants import ( + BYTE_UNITS, + DEFAULT_HTTP_HOST, + DEFAULT_NPIPE, + DEFAULT_UNIX_SOCKET, +) from ..tls import TLSConfig -from urllib.parse import urlparse, urlunparse - URLComponents = collections.namedtuple( 'URLComponents', 'scheme netloc url params query fragment', diff --git a/docker/version.py b/docker/version.py index dca45bf04..72b12b84d 100644 --- a/docker/version.py +++ b/docker/version.py @@ -1,7 +1,7 @@ try: from ._version import __version__ except ImportError: - from importlib.metadata import version, PackageNotFoundError + from importlib.metadata import PackageNotFoundError, version try: __version__ = version('docker') except PackageNotFoundError: diff --git a/docs/conf.py b/docs/conf.py index a529f8be8..02694d3cd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,6 +19,7 @@ import os import sys from importlib.metadata import version + sys.path.insert(0, os.path.abspath('..')) diff --git a/pyproject.toml b/pyproject.toml index a64e120ee..96fb27228 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ extend-select = [ "B", "C", "F", + "I", "UP", "W", ] diff --git a/setup.py b/setup.py index 3d3313924..b939ec58d 100644 --- a/setup.py +++ b/setup.py @@ -3,8 +3,7 @@ import codecs import os -from setuptools import find_packages -from setuptools import setup +from setuptools import find_packages, setup ROOT_DIR = os.path.dirname(__file__) SOURCE_DIR = os.path.join(ROOT_DIR) diff --git a/tests/helpers.py b/tests/helpers.py index 748ee70a7..3d60a3faf 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -8,10 +8,11 @@ import tempfile import time -import docker import paramiko import pytest +import docker + def make_tree(dirs, files): base = tempfile.mkdtemp() diff --git a/tests/integration/api_build_test.py b/tests/integration/api_build_test.py index 540ef2b0f..62e93a738 100644 --- a/tests/integration/api_build_test.py +++ b/tests/integration/api_build_test.py @@ -3,13 +3,13 @@ import shutil import tempfile +import pytest + from docker import errors from docker.utils.proxy import ProxyConfig -import pytest - -from .base import BaseAPIIntegrationTest, TEST_IMG from ..helpers import random_name, requires_api_version, requires_experimental +from .base import TEST_IMG, BaseAPIIntegrationTest class BuildTest(BaseAPIIntegrationTest): diff --git a/tests/integration/api_config_test.py b/tests/integration/api_config_test.py index 982ec468a..4261599d8 100644 --- a/tests/integration/api_config_test.py +++ b/tests/integration/api_config_test.py @@ -1,6 +1,7 @@ -import docker import pytest +import docker + from ..helpers import force_leave_swarm, requires_api_version from .base import BaseAPIIntegrationTest diff --git a/tests/integration/api_container_test.py b/tests/integration/api_container_test.py index aa27fbfd7..0215e14c2 100644 --- a/tests/integration/api_container_test.py +++ b/tests/integration/api_container_test.py @@ -9,15 +9,17 @@ import requests import docker -from .. import helpers -from ..helpers import assert_cat_socket_detached_with_keys -from ..helpers import ctrl_with -from ..helpers import requires_api_version, skip_if_desktop -from .base import BaseAPIIntegrationTest -from .base import TEST_IMG from docker.constants import IS_WINDOWS_PLATFORM -from docker.utils.socket import next_frame_header -from docker.utils.socket import read_exactly +from docker.utils.socket import next_frame_header, read_exactly + +from .. import helpers +from ..helpers import ( + assert_cat_socket_detached_with_keys, + ctrl_with, + requires_api_version, + skip_if_desktop, +) +from .base import TEST_IMG, BaseAPIIntegrationTest class ListContainersTest(BaseAPIIntegrationTest): diff --git a/tests/integration/api_exec_test.py b/tests/integration/api_exec_test.py index 4d7748f5e..5b829e287 100644 --- a/tests/integration/api_exec_test.py +++ b/tests/integration/api_exec_test.py @@ -1,11 +1,12 @@ -from ..helpers import assert_cat_socket_detached_with_keys -from ..helpers import ctrl_with -from ..helpers import requires_api_version -from .base import BaseAPIIntegrationTest -from .base import TEST_IMG from docker.utils.proxy import ProxyConfig -from docker.utils.socket import next_frame_header -from docker.utils.socket import read_exactly +from docker.utils.socket import next_frame_header, read_exactly + +from ..helpers import ( + assert_cat_socket_detached_with_keys, + ctrl_with, + requires_api_version, +) +from .base import TEST_IMG, BaseAPIIntegrationTest class ExecTest(BaseAPIIntegrationTest): diff --git a/tests/integration/api_healthcheck_test.py b/tests/integration/api_healthcheck_test.py index 9ecdcd86a..f00d804b4 100644 --- a/tests/integration/api_healthcheck_test.py +++ b/tests/integration/api_healthcheck_test.py @@ -1,5 +1,5 @@ -from .base import BaseAPIIntegrationTest, TEST_IMG from .. import helpers +from .base import TEST_IMG, BaseAPIIntegrationTest SECOND = 1000000000 diff --git a/tests/integration/api_image_test.py b/tests/integration/api_image_test.py index 5c3721958..d3915c9b5 100644 --- a/tests/integration/api_image_test.py +++ b/tests/integration/api_image_test.py @@ -2,19 +2,18 @@ import json import shutil import socket +import socketserver import tarfile import tempfile import threading - -import pytest from http.server import SimpleHTTPRequestHandler -import socketserver +import pytest import docker from ..helpers import requires_api_version, requires_experimental -from .base import BaseAPIIntegrationTest, TEST_IMG +from .base import TEST_IMG, BaseAPIIntegrationTest class ListImagesTest(BaseAPIIntegrationTest): diff --git a/tests/integration/api_network_test.py b/tests/integration/api_network_test.py index 6689044b6..ce2e8ea4c 100644 --- a/tests/integration/api_network_test.py +++ b/tests/integration/api_network_test.py @@ -1,9 +1,10 @@ +import pytest + import docker from docker.types import IPAMConfig, IPAMPool -import pytest from ..helpers import random_name, requires_api_version -from .base import BaseAPIIntegrationTest, TEST_IMG +from .base import TEST_IMG, BaseAPIIntegrationTest class TestNetworks(BaseAPIIntegrationTest): diff --git a/tests/integration/api_plugin_test.py b/tests/integration/api_plugin_test.py index 3f1633900..168c81b23 100644 --- a/tests/integration/api_plugin_test.py +++ b/tests/integration/api_plugin_test.py @@ -1,10 +1,11 @@ import os -import docker import pytest -from .base import BaseAPIIntegrationTest +import docker + from ..helpers import requires_api_version +from .base import BaseAPIIntegrationTest SSHFS = 'vieux/sshfs:latest' diff --git a/tests/integration/api_secret_test.py b/tests/integration/api_secret_test.py index fd9854341..588aaeb99 100644 --- a/tests/integration/api_secret_test.py +++ b/tests/integration/api_secret_test.py @@ -1,6 +1,7 @@ -import docker import pytest +import docker + from ..helpers import force_leave_swarm, requires_api_version from .base import BaseAPIIntegrationTest diff --git a/tests/integration/api_service_test.py b/tests/integration/api_service_test.py index dec3fa007..7a7ae4ea4 100644 --- a/tests/integration/api_service_test.py +++ b/tests/integration/api_service_test.py @@ -1,13 +1,12 @@ import random import time -import docker import pytest -from ..helpers import ( - force_leave_swarm, requires_api_version, requires_experimental -) -from .base import BaseAPIIntegrationTest, TEST_IMG +import docker + +from ..helpers import force_leave_swarm, requires_api_version, requires_experimental +from .base import TEST_IMG, BaseAPIIntegrationTest class ServiceTest(BaseAPIIntegrationTest): diff --git a/tests/integration/api_swarm_test.py b/tests/integration/api_swarm_test.py index b4125d24d..00477e103 100644 --- a/tests/integration/api_swarm_test.py +++ b/tests/integration/api_swarm_test.py @@ -1,7 +1,9 @@ import copy -import docker + import pytest +import docker + from ..helpers import force_leave_swarm, requires_api_version from .base import BaseAPIIntegrationTest diff --git a/tests/integration/api_volume_test.py b/tests/integration/api_volume_test.py index 2085e8311..ecd19da2d 100644 --- a/tests/integration/api_volume_test.py +++ b/tests/integration/api_volume_test.py @@ -1,6 +1,7 @@ -import docker import pytest +import docker + from ..helpers import requires_api_version from .base import BaseAPIIntegrationTest diff --git a/tests/integration/base.py b/tests/integration/base.py index e4073757e..51ee05daa 100644 --- a/tests/integration/base.py +++ b/tests/integration/base.py @@ -3,9 +3,10 @@ import unittest import docker -from .. import helpers from docker.utils import kwargs_from_env +from .. import helpers + TEST_IMG = 'alpine:3.10' TEST_API_VERSION = os.environ.get('DOCKER_TEST_API_VERSION') diff --git a/tests/integration/client_test.py b/tests/integration/client_test.py index 7df172c88..1d1be077e 100644 --- a/tests/integration/client_test.py +++ b/tests/integration/client_test.py @@ -1,10 +1,9 @@ import threading import unittest +from datetime import datetime, timedelta import docker -from datetime import datetime, timedelta - from ..helpers import requires_api_version from .base import TEST_API_VERSION diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index ae9459558..443c5b795 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,9 +1,10 @@ import sys import warnings +import pytest + import docker.errors from docker.utils import kwargs_from_env -import pytest from .base import TEST_IMG diff --git a/tests/integration/context_api_test.py b/tests/integration/context_api_test.py index 1a13f2817..2131ebe74 100644 --- a/tests/integration/context_api_test.py +++ b/tests/integration/context_api_test.py @@ -1,9 +1,12 @@ import os import tempfile + import pytest + from docker import errors from docker.context import ContextAPI from docker.tls import TLSConfig + from .base import BaseAPIIntegrationTest diff --git a/tests/integration/credentials/store_test.py b/tests/integration/credentials/store_test.py index 82ea84741..e1eba33a1 100644 --- a/tests/integration/credentials/store_test.py +++ b/tests/integration/credentials/store_test.py @@ -6,8 +6,11 @@ import pytest from docker.credentials import ( - CredentialsNotFound, Store, StoreError, DEFAULT_LINUX_STORE, - DEFAULT_OSX_STORE + DEFAULT_LINUX_STORE, + DEFAULT_OSX_STORE, + CredentialsNotFound, + Store, + StoreError, ) diff --git a/tests/integration/credentials/utils_test.py b/tests/integration/credentials/utils_test.py index 464403979..75bdea100 100644 --- a/tests/integration/credentials/utils_test.py +++ b/tests/integration/credentials/utils_test.py @@ -1,7 +1,7 @@ import os +from unittest import mock from docker.credentials.utils import create_environment_dict -from unittest import mock @mock.patch.dict(os.environ) diff --git a/tests/integration/errors_test.py b/tests/integration/errors_test.py index e2fce48b0..438caacbc 100644 --- a/tests/integration/errors_test.py +++ b/tests/integration/errors_test.py @@ -1,7 +1,9 @@ -from docker.errors import APIError -from .base import BaseAPIIntegrationTest, TEST_IMG import pytest +from docker.errors import APIError + +from .base import TEST_IMG, BaseAPIIntegrationTest + class ErrorsTest(BaseAPIIntegrationTest): def test_api_error_parses_json(self): diff --git a/tests/integration/models_containers_test.py b/tests/integration/models_containers_test.py index 219b9a4cb..f2813e74e 100644 --- a/tests/integration/models_containers_test.py +++ b/tests/integration/models_containers_test.py @@ -5,10 +5,9 @@ import pytest import docker -from .base import BaseIntegrationTest -from .base import TEST_API_VERSION -from ..helpers import random_name -from ..helpers import requires_api_version + +from ..helpers import random_name, requires_api_version +from .base import TEST_API_VERSION, BaseIntegrationTest class ContainerCollectionTest(BaseIntegrationTest): diff --git a/tests/integration/models_images_test.py b/tests/integration/models_images_test.py index d335da4a7..9d42cc48f 100644 --- a/tests/integration/models_images_test.py +++ b/tests/integration/models_images_test.py @@ -1,11 +1,12 @@ import io import tempfile -import docker import pytest -from .base import BaseIntegrationTest, TEST_IMG, TEST_API_VERSION +import docker + from ..helpers import random_name +from .base import TEST_API_VERSION, TEST_IMG, BaseIntegrationTest class ImageCollectionTest(BaseIntegrationTest): diff --git a/tests/integration/models_networks_test.py b/tests/integration/models_networks_test.py index f4052e4ba..f5e6fcf57 100644 --- a/tests/integration/models_networks_test.py +++ b/tests/integration/models_networks_test.py @@ -1,6 +1,7 @@ import docker + from .. import helpers -from .base import BaseIntegrationTest, TEST_API_VERSION +from .base import TEST_API_VERSION, BaseIntegrationTest class NetworkCollectionTest(BaseIntegrationTest): diff --git a/tests/integration/models_resources_test.py b/tests/integration/models_resources_test.py index 4aafe0cc7..7d9762702 100644 --- a/tests/integration/models_resources_test.py +++ b/tests/integration/models_resources_test.py @@ -1,5 +1,6 @@ import docker -from .base import BaseIntegrationTest, TEST_API_VERSION + +from .base import TEST_API_VERSION, BaseIntegrationTest class ModelTest(BaseIntegrationTest): diff --git a/tests/integration/models_services_test.py b/tests/integration/models_services_test.py index f1439a418..947ba46d2 100644 --- a/tests/integration/models_services_test.py +++ b/tests/integration/models_services_test.py @@ -1,13 +1,14 @@ import unittest -import docker import pytest -from .. import helpers -from .base import TEST_API_VERSION +import docker from docker.errors import InvalidArgument from docker.types.services import ServiceMode +from .. import helpers +from .base import TEST_API_VERSION + class ServiceTest(unittest.TestCase): @classmethod diff --git a/tests/integration/models_swarm_test.py b/tests/integration/models_swarm_test.py index 6c1836dc6..f43824c75 100644 --- a/tests/integration/models_swarm_test.py +++ b/tests/integration/models_swarm_test.py @@ -1,10 +1,11 @@ import unittest +import pytest + import docker from .. import helpers from .base import TEST_API_VERSION -import pytest class SwarmTest(unittest.TestCase): diff --git a/tests/integration/models_volumes_test.py b/tests/integration/models_volumes_test.py index 47b4a4550..7d3ffda99 100644 --- a/tests/integration/models_volumes_test.py +++ b/tests/integration/models_volumes_test.py @@ -1,5 +1,6 @@ import docker -from .base import BaseIntegrationTest, TEST_API_VERSION + +from .base import TEST_API_VERSION, BaseIntegrationTest class VolumesTest(BaseIntegrationTest): diff --git a/tests/integration/regression_test.py b/tests/integration/regression_test.py index 7d2b228cc..5df9d3121 100644 --- a/tests/integration/regression_test.py +++ b/tests/integration/regression_test.py @@ -1,10 +1,11 @@ import io import random +import pytest + import docker -from .base import BaseAPIIntegrationTest, TEST_IMG -import pytest +from .base import TEST_IMG, BaseAPIIntegrationTest class TestRegressions(BaseAPIIntegrationTest): diff --git a/tests/ssh/api_build_test.py b/tests/ssh/api_build_test.py index 3b542994f..20476fc74 100644 --- a/tests/ssh/api_build_test.py +++ b/tests/ssh/api_build_test.py @@ -3,13 +3,13 @@ import shutil import tempfile +import pytest + from docker import errors from docker.utils.proxy import ProxyConfig -import pytest - -from .base import BaseAPIIntegrationTest, TEST_IMG from ..helpers import random_name, requires_api_version, requires_experimental +from .base import TEST_IMG, BaseAPIIntegrationTest class BuildTest(BaseAPIIntegrationTest): diff --git a/tests/ssh/base.py b/tests/ssh/base.py index d6ff130a1..bf3c11d7a 100644 --- a/tests/ssh/base.py +++ b/tests/ssh/base.py @@ -5,9 +5,10 @@ import pytest import docker -from .. import helpers from docker.utils import kwargs_from_env +from .. import helpers + TEST_IMG = 'alpine:3.10' TEST_API_VERSION = os.environ.get('DOCKER_TEST_API_VERSION') diff --git a/tests/ssh/connect_test.py b/tests/ssh/connect_test.py index 3d33a96db..8780e3b8b 100644 --- a/tests/ssh/connect_test.py +++ b/tests/ssh/connect_test.py @@ -1,9 +1,11 @@ import os import unittest -import docker import paramiko.ssh_exception import pytest + +import docker + from .base import TEST_API_VERSION diff --git a/tests/unit/api_container_test.py b/tests/unit/api_container_test.py index c4e2250be..b2e5237a2 100644 --- a/tests/unit/api_container_test.py +++ b/tests/unit/api_container_test.py @@ -1,17 +1,22 @@ import datetime import json import signal +from unittest import mock + +import pytest import docker from docker.api import APIClient -from unittest import mock -import pytest -from . import fake_api from ..helpers import requires_api_version +from . import fake_api from .api_test import ( - BaseAPIClientTest, url_prefix, fake_request, DEFAULT_TIMEOUT_SECONDS, - fake_inspect_container, url_base + DEFAULT_TIMEOUT_SECONDS, + BaseAPIClientTest, + fake_inspect_container, + fake_request, + url_base, + url_prefix, ) diff --git a/tests/unit/api_exec_test.py b/tests/unit/api_exec_test.py index 1760239fd..9d789723a 100644 --- a/tests/unit/api_exec_test.py +++ b/tests/unit/api_exec_test.py @@ -2,7 +2,10 @@ from . import fake_api from .api_test import ( - BaseAPIClientTest, url_prefix, fake_request, DEFAULT_TIMEOUT_SECONDS, + DEFAULT_TIMEOUT_SECONDS, + BaseAPIClientTest, + fake_request, + url_prefix, ) diff --git a/tests/unit/api_image_test.py b/tests/unit/api_image_test.py index 22b27fe0d..148109d37 100644 --- a/tests/unit/api_image_test.py +++ b/tests/unit/api_image_test.py @@ -1,12 +1,17 @@ -import docker +from unittest import mock + import pytest -from . import fake_api +import docker from docker import auth -from unittest import mock + +from . import fake_api from .api_test import ( - BaseAPIClientTest, fake_request, DEFAULT_TIMEOUT_SECONDS, url_prefix, - fake_resolve_authconfig + DEFAULT_TIMEOUT_SECONDS, + BaseAPIClientTest, + fake_request, + fake_resolve_authconfig, + url_prefix, ) diff --git a/tests/unit/api_network_test.py b/tests/unit/api_network_test.py index d3daa44c4..1f9e59665 100644 --- a/tests/unit/api_network_test.py +++ b/tests/unit/api_network_test.py @@ -1,8 +1,9 @@ import json +from unittest import mock -from .api_test import BaseAPIClientTest, url_prefix, response from docker.types import IPAMConfig, IPAMPool -from unittest import mock + +from .api_test import BaseAPIClientTest, response, url_prefix class NetworkTest(BaseAPIClientTest): diff --git a/tests/unit/api_test.py b/tests/unit/api_test.py index 0ca9bbb95..3ce127b34 100644 --- a/tests/unit/api_test.py +++ b/tests/unit/api_test.py @@ -1,29 +1,29 @@ import datetime +import http.server import io import json import os import re import shutil import socket +import socketserver import struct import tempfile import threading import time import unittest -import socketserver -import http.server +from unittest import mock -import docker import pytest import requests import urllib3 + +import docker from docker.api import APIClient from docker.constants import DEFAULT_DOCKER_API_VERSION -from unittest import mock from . import fake_api - DEFAULT_TIMEOUT_SECONDS = docker.constants.DEFAULT_TIMEOUT_SECONDS diff --git a/tests/unit/api_volume_test.py b/tests/unit/api_volume_test.py index 0a97ca515..fd3206396 100644 --- a/tests/unit/api_volume_test.py +++ b/tests/unit/api_volume_test.py @@ -3,7 +3,7 @@ import pytest from ..helpers import requires_api_version -from .api_test import BaseAPIClientTest, url_prefix, fake_request +from .api_test import BaseAPIClientTest, fake_request, url_prefix class VolumeTest(BaseAPIClientTest): diff --git a/tests/unit/auth_test.py b/tests/unit/auth_test.py index 0ed890fdf..b2fedb32e 100644 --- a/tests/unit/auth_test.py +++ b/tests/unit/auth_test.py @@ -6,11 +6,12 @@ import shutil import tempfile import unittest - -from docker import auth, credentials, errors from unittest import mock + import pytest +from docker import auth, credentials, errors + class RegressionTest(unittest.TestCase): def test_803_urlsafe_encode(self): diff --git a/tests/unit/client_test.py b/tests/unit/client_test.py index 7012b2123..60a6d5c0f 100644 --- a/tests/unit/client_test.py +++ b/tests/unit/client_test.py @@ -1,15 +1,18 @@ import datetime import os import unittest +from unittest import mock -import docker import pytest + +import docker from docker.constants import ( - DEFAULT_DOCKER_API_VERSION, DEFAULT_TIMEOUT_SECONDS, - DEFAULT_MAX_POOL_SIZE, IS_WINDOWS_PLATFORM + DEFAULT_DOCKER_API_VERSION, + DEFAULT_MAX_POOL_SIZE, + DEFAULT_TIMEOUT_SECONDS, + IS_WINDOWS_PLATFORM, ) from docker.utils import kwargs_from_env -from unittest import mock from . import fake_api diff --git a/tests/unit/context_test.py b/tests/unit/context_test.py index 25f0d8c6b..9e9fc9ba1 100644 --- a/tests/unit/context_test.py +++ b/tests/unit/context_test.py @@ -1,10 +1,10 @@ import unittest -import docker + import pytest -from docker.constants import DEFAULT_UNIX_SOCKET -from docker.constants import DEFAULT_NPIPE -from docker.constants import IS_WINDOWS_PLATFORM -from docker.context import ContextAPI, Context + +import docker +from docker.constants import DEFAULT_NPIPE, DEFAULT_UNIX_SOCKET, IS_WINDOWS_PLATFORM +from docker.context import Context, ContextAPI class BaseContextTest(unittest.TestCase): diff --git a/tests/unit/dockertypes_test.py b/tests/unit/dockertypes_test.py index f3d562e10..03e7d2eda 100644 --- a/tests/unit/dockertypes_test.py +++ b/tests/unit/dockertypes_test.py @@ -1,15 +1,22 @@ import unittest +from unittest import mock import pytest from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import InvalidArgument, InvalidVersion from docker.types import ( - ContainerSpec, EndpointConfig, HostConfig, IPAMConfig, - IPAMPool, LogConfig, Mount, ServiceMode, Ulimit, + ContainerSpec, + EndpointConfig, + HostConfig, + IPAMConfig, + IPAMPool, + LogConfig, + Mount, + ServiceMode, + Ulimit, ) from docker.types.services import convert_service_ports -from unittest import mock def create_host_config(*args, **kwargs): diff --git a/tests/unit/errors_test.py b/tests/unit/errors_test.py index f8c3a6663..5ccc40474 100644 --- a/tests/unit/errors_test.py +++ b/tests/unit/errors_test.py @@ -2,9 +2,14 @@ import requests -from docker.errors import (APIError, ContainerError, DockerException, - create_unexpected_kwargs_error, - create_api_error_from_http_exception) +from docker.errors import ( + APIError, + ContainerError, + DockerException, + create_api_error_from_http_exception, + create_unexpected_kwargs_error, +) + from .fake_api import FAKE_CONTAINER_ID, FAKE_IMAGE_ID from .fake_api_client import make_fake_client diff --git a/tests/unit/fake_api_client.py b/tests/unit/fake_api_client.py index 797994216..017e99d0c 100644 --- a/tests/unit/fake_api_client.py +++ b/tests/unit/fake_api_client.py @@ -1,8 +1,9 @@ import copy +from unittest import mock import docker from docker.constants import DEFAULT_DOCKER_API_VERSION -from unittest import mock + from . import fake_api diff --git a/tests/unit/models_configs_test.py b/tests/unit/models_configs_test.py index 5d52daf76..9f1083068 100644 --- a/tests/unit/models_configs_test.py +++ b/tests/unit/models_configs_test.py @@ -1,7 +1,8 @@ import unittest -from .fake_api_client import make_fake_client from .fake_api import FAKE_CONFIG_NAME +from .fake_api_client import make_fake_client + class CreateConfigsTest(unittest.TestCase): def test_create_config(self): diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index 05005815f..0e2ae341a 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -3,12 +3,12 @@ import pytest import docker -from docker.constants import DEFAULT_DATA_CHUNK_SIZE, \ - DEFAULT_DOCKER_API_VERSION +from docker.constants import DEFAULT_DATA_CHUNK_SIZE, DEFAULT_DOCKER_API_VERSION from docker.models.containers import Container, _create_container_args from docker.models.images import Image from docker.types import EndpointConfig -from .fake_api import FAKE_CONTAINER_ID, FAKE_IMAGE_ID, FAKE_EXEC_ID + +from .fake_api import FAKE_CONTAINER_ID, FAKE_EXEC_ID, FAKE_IMAGE_ID from .fake_api_client import make_fake_client diff --git a/tests/unit/models_networks_test.py b/tests/unit/models_networks_test.py index f10e1e3e3..099fb2193 100644 --- a/tests/unit/models_networks_test.py +++ b/tests/unit/models_networks_test.py @@ -1,6 +1,6 @@ import unittest -from .fake_api import FAKE_NETWORK_ID, FAKE_CONTAINER_ID +from .fake_api import FAKE_CONTAINER_ID, FAKE_NETWORK_ID from .fake_api_client import make_fake_client diff --git a/tests/unit/models_secrets_test.py b/tests/unit/models_secrets_test.py index 1c261a871..1f5aaace2 100644 --- a/tests/unit/models_secrets_test.py +++ b/tests/unit/models_secrets_test.py @@ -1,7 +1,7 @@ import unittest -from .fake_api_client import make_fake_client from .fake_api import FAKE_SECRET_NAME +from .fake_api_client import make_fake_client class CreateServiceTest(unittest.TestCase): diff --git a/tests/unit/models_services_test.py b/tests/unit/models_services_test.py index 45c63ac9e..027756343 100644 --- a/tests/unit/models_services_test.py +++ b/tests/unit/models_services_test.py @@ -1,4 +1,5 @@ import unittest + from docker.models.services import _get_create_service_kwargs diff --git a/tests/unit/sshadapter_test.py b/tests/unit/sshadapter_test.py index 874239ac8..873666210 100644 --- a/tests/unit/sshadapter_test.py +++ b/tests/unit/sshadapter_test.py @@ -1,4 +1,5 @@ import unittest + import docker from docker.transport.sshconn import SSHSocket diff --git a/tests/unit/swarm_test.py b/tests/unit/swarm_test.py index 3fc7c68cd..4c0f2fd00 100644 --- a/tests/unit/swarm_test.py +++ b/tests/unit/swarm_test.py @@ -1,8 +1,8 @@ import json -from . import fake_api from ..helpers import requires_api_version -from .api_test import BaseAPIClientTest, url_prefix, fake_request +from . import fake_api +from .api_test import BaseAPIClientTest, fake_request, url_prefix class SwarmTest(BaseAPIClientTest): diff --git a/tests/unit/utils_build_test.py b/tests/unit/utils_build_test.py index 5f1bb1ec0..2089afb49 100644 --- a/tests/unit/utils_build_test.py +++ b/tests/unit/utils_build_test.py @@ -9,7 +9,7 @@ import pytest from docker.constants import IS_WINDOWS_PLATFORM -from docker.utils import exclude_paths, tar, match_tag +from docker.utils import exclude_paths, match_tag, tar from ..helpers import make_tree diff --git a/tests/unit/utils_config_test.py b/tests/unit/utils_config_test.py index 27d5a7cd4..c87231a99 100644 --- a/tests/unit/utils_config_test.py +++ b/tests/unit/utils_config_test.py @@ -1,12 +1,12 @@ +import json import os -import unittest import shutil import tempfile -import json - -from pytest import mark, fixture +import unittest from unittest import mock +from pytest import fixture, mark + from docker.utils import config diff --git a/tests/unit/utils_json_stream_test.py b/tests/unit/utils_json_stream_test.py index 821ebe42d..5a8310cb5 100644 --- a/tests/unit/utils_json_stream_test.py +++ b/tests/unit/utils_json_stream_test.py @@ -1,4 +1,4 @@ -from docker.utils.json_stream import json_splitter, stream_as_text, json_stream +from docker.utils.json_stream import json_splitter, json_stream, stream_as_text class TestJsonSplitter: diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py index c9434e11b..21da0b58e 100644 --- a/tests/unit/utils_test.py +++ b/tests/unit/utils_test.py @@ -7,14 +7,26 @@ import unittest import pytest + from docker.api.client import APIClient -from docker.constants import IS_WINDOWS_PLATFORM, DEFAULT_DOCKER_API_VERSION +from docker.constants import DEFAULT_DOCKER_API_VERSION, IS_WINDOWS_PLATFORM from docker.errors import DockerException from docker.utils import ( - compare_version, convert_filters, convert_volume_binds, decode_json_header, - format_environment, kwargs_from_env, parse_bytes, parse_devices, - parse_env_file, parse_host, parse_repository_tag, split_command, - update_headers, version_gte, version_lt + compare_version, + convert_filters, + convert_volume_binds, + decode_json_header, + format_environment, + kwargs_from_env, + parse_bytes, + parse_devices, + parse_env_file, + parse_host, + parse_repository_tag, + split_command, + update_headers, + version_gte, + version_lt, ) from docker.utils.ports import build_port_bindings, split_port From 1818712b8c16c3eaefef41a5e35bc049cc6fbf4f Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 3 Jan 2024 21:38:53 +0200 Subject: [PATCH 14/27] Untangle circular imports Signed-off-by: Aarni Koskela --- docker/context/api.py | 5 +++-- docker/context/context.py | 7 ++++--- docker/transport/npipeconn.py | 3 +-- docker/transport/sshconn.py | 3 +-- docker/transport/unixconn.py | 3 +-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docker/context/api.py b/docker/context/api.py index ae5d67bb2..9ac4ff470 100644 --- a/docker/context/api.py +++ b/docker/context/api.py @@ -2,13 +2,14 @@ import os from docker import errors -from docker.context import Context -from docker.context.config import ( + +from .config import ( METAFILE, get_current_context_name, get_meta_dir, write_context_name_to_docker_config, ) +from .context import Context class ContextAPI: diff --git a/docker/context/context.py b/docker/context/context.py index 317bcf61d..da17d9478 100644 --- a/docker/context/context.py +++ b/docker/context/context.py @@ -2,14 +2,15 @@ import os from shutil import copyfile, rmtree -from docker.context.config import ( +from docker.errors import ContextException +from docker.tls import TLSConfig + +from .config import ( get_context_host, get_meta_dir, get_meta_file, get_tls_dir, ) -from docker.errors import ContextException -from docker.tls import TLSConfig class Context: diff --git a/docker/transport/npipeconn.py b/docker/transport/npipeconn.py index fe740a5f8..44d6921c2 100644 --- a/docker/transport/npipeconn.py +++ b/docker/transport/npipeconn.py @@ -4,9 +4,8 @@ import urllib3 import urllib3.connection -from docker.transport.basehttpadapter import BaseHTTPAdapter - from .. import constants +from .basehttpadapter import BaseHTTPAdapter from .npipesocket import NpipeSocket RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer diff --git a/docker/transport/sshconn.py b/docker/transport/sshconn.py index 91671e920..187066801 100644 --- a/docker/transport/sshconn.py +++ b/docker/transport/sshconn.py @@ -11,9 +11,8 @@ import urllib3 import urllib3.connection -from docker.transport.basehttpadapter import BaseHTTPAdapter - from .. import constants +from .basehttpadapter import BaseHTTPAdapter RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer diff --git a/docker/transport/unixconn.py b/docker/transport/unixconn.py index f88d29ebf..d571833f0 100644 --- a/docker/transport/unixconn.py +++ b/docker/transport/unixconn.py @@ -4,9 +4,8 @@ import urllib3 import urllib3.connection -from docker.transport.basehttpadapter import BaseHTTPAdapter - from .. import constants +from .basehttpadapter import BaseHTTPAdapter RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer From cb21af7f69fc44572c9f9a326c84275ae2be9c63 Mon Sep 17 00:00:00 2001 From: Rob Murray Date: Wed, 13 Mar 2024 14:54:25 +0000 Subject: [PATCH 15/27] Fix tests that look at 'Aliases' Inspect output for 'NetworkSettings.Networks..Aliases' includes the container's short-id (although it will be removed in API v1.45, in moby 26.0). Signed-off-by: Rob Murray --- tests/integration/models_containers_test.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/integration/models_containers_test.py b/tests/integration/models_containers_test.py index 219b9a4cb..87ba89b1d 100644 --- a/tests/integration/models_containers_test.py +++ b/tests/integration/models_containers_test.py @@ -110,12 +110,12 @@ def test_run_with_networking_config(self): client.networks.create(net_name) self.tmp_networks.append(net_name) - test_aliases = ['hello'] + test_alias = 'hello' test_driver_opt = {'key1': 'a'} networking_config = { net_name: client.api.create_endpoint_config( - aliases=test_aliases, + aliases=[test_alias], driver_opt=test_driver_opt ) } @@ -132,8 +132,10 @@ def test_run_with_networking_config(self): assert 'NetworkSettings' in attrs assert 'Networks' in attrs['NetworkSettings'] assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] - assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] == \ - test_aliases + # Expect Aliases to list 'test_alias' and the container's short-id. + # In API version 1.45, the short-id will be removed. + assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] \ + == [test_alias, attrs['Id'][:12]] assert attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] \ == test_driver_opt @@ -190,7 +192,9 @@ def test_run_with_networking_config_only_undeclared_network(self): assert 'NetworkSettings' in attrs assert 'Networks' in attrs['NetworkSettings'] assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] - assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] is None + # Aliases should include the container's short-id (but it will be removed + # in API v1.45). + assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] == [attrs["Id"][:12]] assert (attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] is None) From e91b280074784026135573ab1726a565c090cd82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Thu, 7 Mar 2024 13:12:32 +0100 Subject: [PATCH 16/27] Bump default API version to 1.44 (Moby 25.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Gronowski --- Makefile | 4 ++-- docker/constants.py | 2 +- tests/Dockerfile-ssh-dind | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 00ebca05c..25a83205b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -TEST_API_VERSION ?= 1.43 -TEST_ENGINE_VERSION ?= 24.0 +TEST_API_VERSION ?= 1.44 +TEST_ENGINE_VERSION ?= 25.0 ifeq ($(OS),Windows_NT) PLATFORM := Windows diff --git a/docker/constants.py b/docker/constants.py index 71e543e53..8e7350d3d 100644 --- a/docker/constants.py +++ b/docker/constants.py @@ -1,7 +1,7 @@ import sys from .version import __version__ -DEFAULT_DOCKER_API_VERSION = '1.43' +DEFAULT_DOCKER_API_VERSION = '1.44' MINIMUM_DOCKER_API_VERSION = '1.21' DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 diff --git a/tests/Dockerfile-ssh-dind b/tests/Dockerfile-ssh-dind index 2b7332b8b..250c20f2c 100644 --- a/tests/Dockerfile-ssh-dind +++ b/tests/Dockerfile-ssh-dind @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 -ARG API_VERSION=1.43 -ARG ENGINE_VERSION=24.0 +ARG API_VERSION=1.44 +ARG ENGINE_VERSION=25.0 FROM docker:${ENGINE_VERSION}-dind From dd82f9ae8e601d3c82ef8d4f2dbab0c16109152f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Thu, 7 Mar 2024 13:12:45 +0100 Subject: [PATCH 17/27] Bump minimum API version to 1.24 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 25.0 raised the minimum supported API verison: https://github.com/moby/moby/pull/46887 Signed-off-by: Paweł Gronowski --- docker/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/constants.py b/docker/constants.py index 8e7350d3d..213ff61ed 100644 --- a/docker/constants.py +++ b/docker/constants.py @@ -2,7 +2,7 @@ from .version import __version__ DEFAULT_DOCKER_API_VERSION = '1.44' -MINIMUM_DOCKER_API_VERSION = '1.21' +MINIMUM_DOCKER_API_VERSION = '1.24' DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 CONTAINER_LIMITS_KEYS = [ From 9ad4bddc9ee23f3646f256280a21ef86274e39bc Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Wed, 3 Apr 2024 08:44:29 -0400 Subject: [PATCH 18/27] chore(ci): fix-ups across Make / Docker / GitHub Actions (#3241) --- .github/workflows/ci.yml | 2 +- .readthedocs.yml | 6 ++-- Dockerfile | 16 ++++------- Dockerfile-docs | 9 ++++-- MANIFEST.in | 9 ------ Makefile | 60 +++++++++++++++++++++++++--------------- README.md | 2 +- docs-requirements.txt | 2 -- pyproject.toml | 23 ++++++++++++++- requirements.txt | 6 ---- test-requirements.txt | 6 ---- tests/Dockerfile | 19 ++++--------- tox.ini | 7 ++--- 13 files changed, 86 insertions(+), 81 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 docs-requirements.txt delete mode 100644 requirements.txt delete mode 100644 test-requirements.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eb450a6a..2cac5b132 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Install dependencies run: | python3 -m pip install --upgrade pip - pip3 install -r test-requirements.txt -r requirements.txt + pip3 install '.[ssh,dev]' - name: Run unit tests run: | docker logout diff --git a/.readthedocs.yml b/.readthedocs.yml index 80000ee7f..907454ea9 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,14 +4,14 @@ sphinx: configuration: docs/conf.py build: - os: ubuntu-20.04 + os: ubuntu-22.04 tools: - python: '3.10' + python: '3.12' python: install: - - requirements: docs-requirements.txt - method: pip path: . extra_requirements: - ssh + - docs diff --git a/Dockerfile b/Dockerfile index 293888d72..0189f46c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,13 @@ # syntax=docker/dockerfile:1 ARG PYTHON_VERSION=3.12 - FROM python:${PYTHON_VERSION} WORKDIR /src - -COPY requirements.txt /src/requirements.txt -RUN pip install --no-cache-dir -r requirements.txt - -COPY test-requirements.txt /src/test-requirements.txt -RUN pip install --no-cache-dir -r test-requirements.txt - COPY . . -ARG SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER -RUN pip install --no-cache-dir . + +ARG VERSION +RUN --mount=type=cache,target=/cache/pip \ + PIP_CACHE_DIR=/cache/pip \ + SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} \ + pip install .[ssh] diff --git a/Dockerfile-docs b/Dockerfile-docs index 266b2099e..14d615c43 100644 --- a/Dockerfile-docs +++ b/Dockerfile-docs @@ -11,7 +11,12 @@ RUN addgroup --gid $gid sphinx \ && useradd --uid $uid --gid $gid -M sphinx WORKDIR /src -COPY requirements.txt docs-requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt -r docs-requirements.txt +COPY . . + +ARG VERSION +RUN --mount=type=cache,target=/cache/pip \ + PIP_CACHE_DIR=/cache/pip \ + SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} \ + pip install .[ssh,docs] USER sphinx diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 2ba6e0274..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,9 +0,0 @@ -include test-requirements.txt -include requirements.txt -include README.md -include README.rst -include LICENSE -recursive-include tests *.py -recursive-include tests/unit/testdata * -recursive-include tests/integration/testdata * -recursive-include tests/gpg-keys * diff --git a/Makefile b/Makefile index 25a83205b..13a00f5e2 100644 --- a/Makefile +++ b/Makefile @@ -11,12 +11,17 @@ ifeq ($(PLATFORM),Linux) uid_args := "--build-arg uid=$(shell id -u) --build-arg gid=$(shell id -g)" endif +SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER ?= $(shell git describe --match '[0-9]*' --dirty='.m' --always --tags 2>/dev/null | sed -r 's/-([0-9]+)/.dev\1/' | sed 's/-/+/') +ifeq ($(SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER),) + SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER = "dev" +endif + .PHONY: all all: test .PHONY: clean clean: - -docker rm -f dpy-dind-py3 dpy-dind-certs dpy-dind-ssl + -docker rm -f dpy-dind dpy-dind-certs dpy-dind-ssl find -name "__pycache__" | xargs rm -rf .PHONY: build-dind-ssh @@ -25,35 +30,46 @@ build-dind-ssh: --pull \ -t docker-dind-ssh \ -f tests/Dockerfile-ssh-dind \ + --build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \ --build-arg ENGINE_VERSION=${TEST_ENGINE_VERSION} \ --build-arg API_VERSION=${TEST_API_VERSION} \ --build-arg APT_MIRROR . -.PHONY: build-py3 -build-py3: +.PHONY: build +build: docker build \ --pull \ -t docker-sdk-python3 \ -f tests/Dockerfile \ + --build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \ --build-arg APT_MIRROR . .PHONY: build-docs build-docs: - docker build -t docker-sdk-python-docs -f Dockerfile-docs $(uid_args) . + docker build \ + -t docker-sdk-python-docs \ + -f Dockerfile-docs \ + --build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \ + $(uid_args) \ + . .PHONY: build-dind-certs build-dind-certs: - docker build -t dpy-dind-certs -f tests/Dockerfile-dind-certs . + docker build \ + -t dpy-dind-certs \ + -f tests/Dockerfile-dind-certs \ + --build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \ + . .PHONY: test -test: ruff unit-test-py3 integration-dind integration-dind-ssl +test: ruff unit-test integration-dind integration-dind-ssl -.PHONY: unit-test-py3 -unit-test-py3: build-py3 +.PHONY: unit-test +unit-test: build docker run -t --rm docker-sdk-python3 py.test tests/unit -.PHONY: integration-test-py3 -integration-test-py3: build-py3 +.PHONY: integration-test +integration-test: build docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock docker-sdk-python3 py.test -v tests/integration/${file} .PHONY: setup-network @@ -61,15 +77,15 @@ setup-network: docker network inspect dpy-tests || docker network create dpy-tests .PHONY: integration-dind -integration-dind: integration-dind-py3 +integration-dind: integration-dind -.PHONY: integration-dind-py3 -integration-dind-py3: build-py3 setup-network - docker rm -vf dpy-dind-py3 || : +.PHONY: integration-dind +integration-dind: build setup-network + docker rm -vf dpy-dind || : docker run \ --detach \ - --name dpy-dind-py3 \ + --name dpy-dind \ --network dpy-tests \ --pull=always \ --privileged \ @@ -82,10 +98,10 @@ integration-dind-py3: build-py3 setup-network --rm \ --tty \ busybox \ - sh -c 'while ! nc -z dpy-dind-py3 2375; do sleep 1; done' + sh -c 'while ! nc -z dpy-dind 2375; do sleep 1; done' docker run \ - --env="DOCKER_HOST=tcp://dpy-dind-py3:2375" \ + --env="DOCKER_HOST=tcp://dpy-dind:2375" \ --env="DOCKER_TEST_API_VERSION=${TEST_API_VERSION}" \ --network dpy-tests \ --rm \ @@ -93,11 +109,11 @@ integration-dind-py3: build-py3 setup-network docker-sdk-python3 \ py.test tests/integration/${file} - docker rm -vf dpy-dind-py3 + docker rm -vf dpy-dind .PHONY: integration-dind-ssh -integration-dind-ssh: build-dind-ssh build-py3 setup-network +integration-dind-ssh: build-dind-ssh build setup-network docker rm -vf dpy-dind-ssh || : docker run -d --network dpy-tests --name dpy-dind-ssh --privileged \ docker-dind-ssh dockerd --experimental @@ -116,7 +132,7 @@ integration-dind-ssh: build-dind-ssh build-py3 setup-network .PHONY: integration-dind-ssl -integration-dind-ssl: build-dind-certs build-py3 setup-network +integration-dind-ssl: build-dind-certs build setup-network docker rm -vf dpy-dind-certs dpy-dind-ssl || : docker run -d --name dpy-dind-certs dpy-dind-certs @@ -164,7 +180,7 @@ integration-dind-ssl: build-dind-certs build-py3 setup-network docker rm -vf dpy-dind-ssl dpy-dind-certs .PHONY: ruff -ruff: build-py3 +ruff: build docker run -t --rm docker-sdk-python3 ruff docker tests .PHONY: docs @@ -172,5 +188,5 @@ docs: build-docs docker run --rm -t -v `pwd`:/src docker-sdk-python-docs sphinx-build docs docs/_build .PHONY: shell -shell: build-py3 +shell: build docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker-sdk-python3 python diff --git a/README.md b/README.md index 921ffbcb8..a6e06a229 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A Python library for the Docker Engine API. It lets you do anything the `docker` ## Installation -The latest stable version [is available on PyPI](https://pypi.python.org/pypi/docker/). Either add `docker` to your `requirements.txt` file or install with pip: +The latest stable version [is available on PyPI](https://pypi.python.org/pypi/docker/). Install with pip: pip install docker diff --git a/docs-requirements.txt b/docs-requirements.txt deleted file mode 100644 index 04d1aff26..000000000 --- a/docs-requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -myst-parser==0.18.0 -Sphinx==5.1.1 diff --git a/pyproject.toml b/pyproject.toml index 73f5ddad6..525a9b81a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,13 +36,34 @@ dependencies = [ ] [project.optional-dependencies] +# ssh feature allows DOCKER_HOST=ssh://... style connections ssh = [ "paramiko>=2.4.3", ] -tls = [] # kept for backwards compatibility +# tls is always supported, the feature is a no-op for backwards compatibility +tls = [] +# websockets can be used as an alternate container attach mechanism but +# by default docker-py hijacks the TCP connection and does not use Websockets +# unless attach_socket(container, ws=True) is called websockets = [ "websocket-client >= 1.3.0", ] +# docs are dependencies required to build the ReadTheDocs site +# this is only needed for CI / working on the docs! +docs = [ + "myst-parser==0.18.0", + "Sphinx==5.1.1", + +] +# dev are dependencies required to test & lint this project +# this is only needed if you are making code changes to docker-py! +dev = [ + "coverage==7.2.7", + "pytest==7.4.2", + "pytest-cov==4.1.0", + "pytest-timeout==2.1.0", + "ruff==0.1.8", +] [project.urls] Changelog = "https://docker-py.readthedocs.io/en/stable/change-log.html" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 6d932eb37..000000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -packaging==21.3 -paramiko==2.11.0 -pywin32==304; sys_platform == 'win32' -requests==2.31.0 -urllib3==1.26.18 -websocket-client==1.3.3 diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 2c0e3622c..000000000 --- a/test-requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -setuptools==65.5.1 -coverage==7.2.7 -ruff==0.1.8 -pytest==7.4.2 -pytest-cov==4.1.0 -pytest-timeout==2.1.0 diff --git a/tests/Dockerfile b/tests/Dockerfile index d7c14b6cc..c0af8e85f 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -1,7 +1,6 @@ # syntax=docker/dockerfile:1 ARG PYTHON_VERSION=3.12 - FROM python:${PYTHON_VERSION} RUN apt-get update && apt-get -y install --no-install-recommends \ @@ -27,16 +26,10 @@ RUN curl -sSL -o /opt/docker-credential-pass.tar.gz \ chmod +x /usr/local/bin/docker-credential-pass WORKDIR /src +COPY . . -COPY requirements.txt /src/requirements.txt -RUN --mount=type=cache,target=/root/.cache/pip \ - pip install -r requirements.txt - -COPY test-requirements.txt /src/test-requirements.txt -RUN --mount=type=cache,target=/root/.cache/pip \ - pip install -r test-requirements.txt - -COPY . /src -ARG SETUPTOOLS_SCM_PRETEND_VERSION=99.0.0+docker -RUN --mount=type=cache,target=/root/.cache/pip \ - pip install -e . +ARG VERSION +RUN --mount=type=cache,target=/cache/pip \ + PIP_CACHE_DIR=/cache/pip \ + SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} \ + pip install .[dev,ssh,websockets] diff --git a/tox.ini b/tox.ini index 03467aea2..19689b964 100644 --- a/tox.ini +++ b/tox.ini @@ -6,11 +6,8 @@ skipsdist=True usedevelop=True commands = py.test -v --cov=docker {posargs:tests/unit} -deps = - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt +extras = dev [testenv:ruff] commands = ruff docker tests setup.py -deps = - -r{toxinidir}/test-requirements.txt +extras = dev From b6464dbed92b14b2c61d5ee49805fce041a3e083 Mon Sep 17 00:00:00 2001 From: Bob Du Date: Wed, 10 Apr 2024 04:13:21 +0800 Subject: [PATCH 19/27] chore: fix return type docs for `container.logs()` (#2240) --- docker/api/container.py | 2 +- docker/models/containers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/api/container.py b/docker/api/container.py index 1f153eeb7..d1b870f9c 100644 --- a/docker/api/container.py +++ b/docker/api/container.py @@ -844,7 +844,7 @@ def logs(self, container, stdout=True, stderr=True, stream=False, float (in fractional seconds) Returns: - (generator or str) + (generator of bytes or bytes) Raises: :py:class:`docker.errors.APIError` diff --git a/docker/models/containers.py b/docker/models/containers.py index 35d8b3141..4795523a1 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -313,7 +313,7 @@ def logs(self, **kwargs): float (in nanoseconds) Returns: - (generator or str): Logs from the container. + (generator of bytes or bytes): Logs from the container. Raises: :py:class:`docker.errors.APIError` From 205d2f2bd00640e83c917f07e50c684173d2e2f6 Mon Sep 17 00:00:00 2001 From: Christopher Petito <47751006+krissetto@users.noreply.github.com> Date: Wed, 22 May 2024 10:58:13 +0000 Subject: [PATCH 20/27] Fix to get our CI working again since we rely on parsing tags. See https://github.com/docker/docker-py/pull/3259 attempts for more details Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2cac5b132..e31b2bd5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,9 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true - name: make ${{ matrix.variant }} run: | docker logout From e34bcf20d9d0dc7156aaba72c635a4aa3bb0658e Mon Sep 17 00:00:00 2001 From: Christopher Petito <47751006+krissetto@users.noreply.github.com> Date: Wed, 22 May 2024 11:10:22 +0000 Subject: [PATCH 21/27] Update setup-python gh action Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e31b2bd5c..9b43a27bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - run: pip install -U ruff==0.1.8 @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - run: pip3 install build && python -m build . @@ -40,7 +40,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 953b59bf7..f4d0919ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' From e33e0a437ecd895158c8cb4322a0cdad79312636 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 20 May 2024 21:13:41 +0200 Subject: [PATCH 22/27] Hotfix for requests 2.32.0. Signed-off-by: Felix Fontein --- docker/transport/basehttpadapter.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker/transport/basehttpadapter.py b/docker/transport/basehttpadapter.py index dfbb193b9..281897a27 100644 --- a/docker/transport/basehttpadapter.py +++ b/docker/transport/basehttpadapter.py @@ -6,3 +6,10 @@ def close(self): super().close() if hasattr(self, 'pools'): self.pools.clear() + + # Hotfix for requests 2.32.0: its commit + # https://github.com/psf/requests/commit/c0813a2d910ea6b4f8438b91d315b8d181302356 + # changes requests.adapters.HTTPAdapter to no longer call get_connection() from + # send(), but instead call _get_connection(). + def _get_connection(self, request, *args, proxies=None, **kwargs): + return self.get_connection(request.url, proxies) From 2a059a9f19c7b37c6c71c233754c6845e325d1ec Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 21 May 2024 18:44:08 +0200 Subject: [PATCH 23/27] Extend fix to requests 2.32.2+. Signed-off-by: Felix Fontein --- docker/transport/basehttpadapter.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker/transport/basehttpadapter.py b/docker/transport/basehttpadapter.py index 281897a27..c5996bb3e 100644 --- a/docker/transport/basehttpadapter.py +++ b/docker/transport/basehttpadapter.py @@ -7,9 +7,14 @@ def close(self): if hasattr(self, 'pools'): self.pools.clear() - # Hotfix for requests 2.32.0: its commit + # Hotfix for requests 2.32.0 and 2.32.1: its commit # https://github.com/psf/requests/commit/c0813a2d910ea6b4f8438b91d315b8d181302356 # changes requests.adapters.HTTPAdapter to no longer call get_connection() from # send(), but instead call _get_connection(). def _get_connection(self, request, *args, proxies=None, **kwargs): return self.get_connection(request.url, proxies) + + # Fix for requests 2.32.2+: + # https://github.com/psf/requests/commit/c98e4d133ef29c46a9b68cd783087218a8075e05 + def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None): + return self.get_connection(request.url, proxies) From d8e9bcb2780607faf388f8832bff3865eb24dce0 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 21 May 2024 21:05:36 +0200 Subject: [PATCH 24/27] requests 2.32.0 and 2.32.1 have been yanked. Signed-off-by: Felix Fontein --- docker/transport/basehttpadapter.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docker/transport/basehttpadapter.py b/docker/transport/basehttpadapter.py index c5996bb3e..2301b6b07 100644 --- a/docker/transport/basehttpadapter.py +++ b/docker/transport/basehttpadapter.py @@ -7,13 +7,6 @@ def close(self): if hasattr(self, 'pools'): self.pools.clear() - # Hotfix for requests 2.32.0 and 2.32.1: its commit - # https://github.com/psf/requests/commit/c0813a2d910ea6b4f8438b91d315b8d181302356 - # changes requests.adapters.HTTPAdapter to no longer call get_connection() from - # send(), but instead call _get_connection(). - def _get_connection(self, request, *args, proxies=None, **kwargs): - return self.get_connection(request.url, proxies) - # Fix for requests 2.32.2+: # https://github.com/psf/requests/commit/c98e4d133ef29c46a9b68cd783087218a8075e05 def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None): From 4f2a26d21e81774cbb8e025b01de001a3ac3a545 Mon Sep 17 00:00:00 2001 From: Christopher Petito <47751006+krissetto@users.noreply.github.com> Date: Thu, 23 May 2024 09:27:07 +0000 Subject: [PATCH 25/27] Added 7.1.0 changelog Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com> --- docs/change-log.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/change-log.md b/docs/change-log.md index faf868ff8..ebbdb7130 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,6 +1,27 @@ Changelog ========== +7.1.0 +----- +### Upgrade Notes +- Bumped minimum engine API version to 1.24 +- Bumped default engine API version to 1.44 (Moby 25.0) + +### Bugfixes +- Fixed issue with tag parsing when the registry address includes ports that resulted in `invalid tag format` errors +- Fixed issue preventing creating new configs (`ConfigCollection`), which failed with a `KeyError` due to the `name` field +- Fixed an issue due to an update in the [requests](https://github.com/psf/requests) package breaking `docker-py` by applying the [suggested fix](https://github.com/psf/requests/pull/6710) + +### Miscellaneous +- Documentation improvements +- Updated Ruff (linter) and fixed minor linting issues +- Packaging/CI updates + - Started using hatch for packaging (https://github.com/pypa/hatch) + - Updated `setup-python` github action +- Updated tests + - Stopped checking for deprecated container and image related fields (`Container` and `ContainerConfig`) + - Updated tests that check `NetworkSettings.Networks..Aliases` due to engine changes + 7.0.0 ----- ### Upgrade Notes From 45488acfc1851c5b5358ec7d8030a754c5f23783 Mon Sep 17 00:00:00 2001 From: Christopher Petito <47751006+krissetto@users.noreply.github.com> Date: Thu, 23 May 2024 10:14:18 +0000 Subject: [PATCH 26/27] Fix env var name in release pipeline Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f4d0919ac..3717e096b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: env: # This is also supported by Hatch; see # https://github.com/ofek/hatch-vcs#version-source-environment-variables - SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DOCKER: ${{ inputs.tag }} + SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER: ${{ inputs.tag }} - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 From 1ab40c8e926c0b892b3ef47ae8acc274fc13f250 Mon Sep 17 00:00:00 2001 From: Christopher Petito <47751006+krissetto@users.noreply.github.com> Date: Thu, 23 May 2024 10:49:23 +0000 Subject: [PATCH 27/27] Fix env var name in release pipeline to match hatch expectations Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3717e096b..17be00163 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: env: # This is also supported by Hatch; see # https://github.com/ofek/hatch-vcs#version-source-environment-variables - SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER: ${{ inputs.tag }} + SETUPTOOLS_SCM_PRETEND_VERSION: ${{ inputs.tag }} - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1