From 249654d4d9c260c84350deeca3e77c7c76669663 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 21 Dec 2023 10:20:32 +0200 Subject: [PATCH] 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