From 64cf32a9fa8ceb8ab99bb74cd8e204e4b80e1b5c Mon Sep 17 00:00:00 2001 From: Kai Mueller <15907922+kasium@users.noreply.github.com> Date: Mon, 13 Dec 2021 16:15:37 +0000 Subject: [PATCH 1/7] Fix Compatible version specifier incorrectly strip trailing '0' components Closes #421 --- packaging/specifiers.py | 5 ++++- packaging/utils.py | 12 +++++++++--- tests/test_specifiers.py | 3 +++ tests/test_utils.py | 5 +++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packaging/specifiers.py b/packaging/specifiers.py index 0e218a6f..566ca6d2 100644 --- a/packaging/specifiers.py +++ b/packaging/specifiers.py @@ -119,7 +119,10 @@ def __str__(self) -> str: @property def _canonical_spec(self) -> Tuple[str, str]: - return self._spec[0], canonicalize_version(self._spec[1]) + strip_zeros = self._spec[0] != "~=" + return self._spec[0], canonicalize_version( + self._spec[1], strip_trailing_zero=strip_zeros + ) def __hash__(self) -> int: return hash(self._canonical_spec) diff --git a/packaging/utils.py b/packaging/utils.py index bab11b80..82cb806e 100644 --- a/packaging/utils.py +++ b/packaging/utils.py @@ -35,7 +35,9 @@ def canonicalize_name(name: str) -> NormalizedName: return cast(NormalizedName, value) -def canonicalize_version(version: Union[Version, str]) -> str: +def canonicalize_version( + version: Union[Version, str], strip_trailing_zero: bool = True +) -> str: """ This is very similar to Version.__str__, but has one subtle difference with the way it handles the release segment. @@ -56,8 +58,12 @@ def canonicalize_version(version: Union[Version, str]) -> str: parts.append(f"{parsed.epoch}!") # Release segment - # NB: This strips trailing '.0's to normalize - parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release))) + release_segment = ".".join(str(x) for x in parsed.release) + if strip_trailing_zero: + # NB: This strips trailing '.0's to normalize + parts.append(re.sub(r"(\.0)+$", "", release_segment)) + else: + parts.append(release_segment) # Pre-release if parsed.pre is not None: diff --git a/tests/test_specifiers.py b/tests/test_specifiers.py index ca21fa1d..aa7ef9e5 100644 --- a/tests/test_specifiers.py +++ b/tests/test_specifiers.py @@ -630,6 +630,9 @@ def test_iteration(self, spec, expected_items): items = {str(item) for item in spec} assert items == set(expected_items) + def test_specifier_equal_for_compatible_operator(self): + assert Specifier("~=1.18.0") != Specifier("~=1.18") + class TestLegacySpecifier: def test_legacy_specifier_is_deprecated(self): diff --git a/tests/test_utils.py b/tests/test_utils.py index be52d670..84a8b38b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -56,6 +56,11 @@ def test_canonicalize_version(version, expected): assert canonicalize_version(version) == expected +@pytest.mark.parametrize(("version"), ["1.4.0", "1.0"]) +def test_canonicalize_version_no_strip_trailing_zero(version): + assert canonicalize_version(version, strip_trailing_zero=False) == version + + @pytest.mark.parametrize( ("filename", "name", "version", "build", "tags"), [ From 80a01cdeba2bd7a37eb4ea087efe6796413aaaab Mon Sep 17 00:00:00 2001 From: kasium <15907922+kasium@users.noreply.github.com> Date: Mon, 13 Dec 2021 17:42:40 +0100 Subject: [PATCH 2/7] Update packaging/specifiers.py Co-authored-by: Tzu-ping Chung --- packaging/specifiers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packaging/specifiers.py b/packaging/specifiers.py index 566ca6d2..362f32ab 100644 --- a/packaging/specifiers.py +++ b/packaging/specifiers.py @@ -120,9 +120,11 @@ def __str__(self) -> str: @property def _canonical_spec(self) -> Tuple[str, str]: strip_zeros = self._spec[0] != "~=" - return self._spec[0], canonicalize_version( - self._spec[1], strip_trailing_zero=strip_zeros + canonical_version = canonicalize_version( + self._spec[1], + strip_trailing_zero=(self._spec[0] != "~="), ) + return self._spec[0], canonical_version def __hash__(self) -> int: return hash(self._canonical_spec) From 08127b02c38254e1046652411df52fdbb2ae3821 Mon Sep 17 00:00:00 2001 From: Kai Mueller <15907922+kasium@users.noreply.github.com> Date: Tue, 14 Dec 2021 06:56:59 +0000 Subject: [PATCH 3/7] Fix flake8 --- packaging/specifiers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packaging/specifiers.py b/packaging/specifiers.py index 362f32ab..6e5d4d59 100644 --- a/packaging/specifiers.py +++ b/packaging/specifiers.py @@ -119,7 +119,6 @@ def __str__(self) -> str: @property def _canonical_spec(self) -> Tuple[str, str]: - strip_zeros = self._spec[0] != "~=" canonical_version = canonicalize_version( self._spec[1], strip_trailing_zero=(self._spec[0] != "~="), From 69ae171dec9ab89bd79d187c94a87b25fb19adb5 Mon Sep 17 00:00:00 2001 From: kasium <15907922+kasium@users.noreply.github.com> Date: Tue, 14 Dec 2021 10:29:02 +0100 Subject: [PATCH 4/7] Update packaging/utils.py Co-authored-by: Pradyun Gedam --- packaging/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/utils.py b/packaging/utils.py index 82cb806e..ce342a69 100644 --- a/packaging/utils.py +++ b/packaging/utils.py @@ -36,7 +36,7 @@ def canonicalize_name(name: str) -> NormalizedName: def canonicalize_version( - version: Union[Version, str], strip_trailing_zero: bool = True + version: Union[Version, str], *, strip_trailing_zero: bool = True ) -> str: """ This is very similar to Version.__str__, but has one subtle difference From 2c87ba464102947a8ab415e831bcf5a8411e448a Mon Sep 17 00:00:00 2001 From: Kai Mueller <15907922+kasium@users.noreply.github.com> Date: Tue, 14 Dec 2021 14:38:05 +0000 Subject: [PATCH 5/7] Add test --- tests/test_specifiers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_specifiers.py b/tests/test_specifiers.py index aa7ef9e5..ae5e110b 100644 --- a/tests/test_specifiers.py +++ b/tests/test_specifiers.py @@ -633,6 +633,9 @@ def test_iteration(self, spec, expected_items): def test_specifier_equal_for_compatible_operator(self): assert Specifier("~=1.18.0") != Specifier("~=1.18") + def test_specifier_hash_for_compatible_operator(self): + assert hash(Specifier("~=1.18.0")) != hash(Specifier("~=1.18")) + class TestLegacySpecifier: def test_legacy_specifier_is_deprecated(self): From f8ded877f7fc318b5835a31ded991a49197111f3 Mon Sep 17 00:00:00 2001 From: Kai Mueller <15907922+kasium@users.noreply.github.com> Date: Tue, 14 Dec 2021 15:06:57 +0000 Subject: [PATCH 6/7] Add test --- tests/test_specifiers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_specifiers.py b/tests/test_specifiers.py index ae5e110b..41a3f829 100644 --- a/tests/test_specifiers.py +++ b/tests/test_specifiers.py @@ -1002,3 +1002,8 @@ def test_comparison_non_specifier(self): ) def test_comparison_ignores_local(self, version, specifier, expected): assert (Version(version) in SpecifierSet(specifier)) == expected + + def test_contains_with_compatible_operator(self): + combination = SpecifierSet("~=1.18.0") & SpecifierSet("~=1.18") + assert "1.19.5" not in combination + assert "1.18.0" in combination From 40b047911b8a628ec456846a237c5caaa2d30345 Mon Sep 17 00:00:00 2001 From: kasium <15907922+kasium@users.noreply.github.com> Date: Wed, 22 Dec 2021 23:46:03 +0100 Subject: [PATCH 7/7] Update packaging/utils.py Co-authored-by: Brett Cannon --- packaging/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packaging/utils.py b/packaging/utils.py index ce342a69..33c613b7 100644 --- a/packaging/utils.py +++ b/packaging/utils.py @@ -61,9 +61,8 @@ def canonicalize_version( release_segment = ".".join(str(x) for x in parsed.release) if strip_trailing_zero: # NB: This strips trailing '.0's to normalize - parts.append(re.sub(r"(\.0)+$", "", release_segment)) - else: - parts.append(release_segment) + release_segment = re.sub(r"(\.0)+$", "", release_segment) + parts.append(release_segment) # Pre-release if parsed.pre is not None: