Skip to content

Commit 7740476

Browse files
authored
Narrow <V.postN pre-release exclusion (#1140)
1 parent c901ded commit 7740476

2 files changed

Lines changed: 60 additions & 5 deletions

File tree

src/packaging/specifiers.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ def _base_version(version: Version) -> Version:
6363
return version.__replace__(pre=None, post=None, dev=None, local=None)
6464

6565

66+
def _earliest_prerelease(version: Version) -> Version:
67+
"""Earliest pre-release of *version*.
68+
69+
1.2 -> 1.2.dev0, 1.2.post1 -> 1.2.post1.dev0.
70+
"""
71+
return version.__replace__(dev=0, local=None)
72+
73+
6674
class InvalidSpecifier(ValueError):
6775
"""
6876
Raised when attempting to create a :class:`Specifier` with a specifier
@@ -561,14 +569,12 @@ def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
561569
if not prospective < spec:
562570
return False
563571

564-
# This special case is here so that, unless the specifier itself
565-
# includes is a pre-release version, that we do not accept pre-release
566-
# versions for the version mentioned in the specifier (e.g. <3.1 should
567-
# not match 3.1.dev0, but should match 3.0.dev0).
572+
# The spec says: "<V MUST NOT allow a pre-release of the specified
573+
# version unless the specified version is itself a pre-release."
568574
if (
569575
not spec.is_prerelease
570576
and prospective.is_prerelease
571-
and _base_version(prospective) == _base_version(spec)
577+
and prospective >= _earliest_prerelease(spec)
572578
):
573579
return False
574580

tests/test_specifiers.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,35 @@ def test_specifier_prereleases_detection(
780780
("<=2.0.dev1", "1.0a1", None, None, True),
781781
("<2.0", "2.0a1", None, None, False),
782782
("<2.0a2", "2.0a1", None, None, True),
783+
# <V.postN: pre-releases of V.postN itself are excluded
784+
("<1.0.post1", "1.0.post1.dev0", None, None, False),
785+
("<1.0.post0", "1.0.post0.dev0", None, None, False),
786+
# <V.postN: pre-releases of the base release are NOT
787+
# pre-releases of V.postN, so they are accepted
788+
("<1.0.post1", "1.0.dev0", None, None, True),
789+
("<1.0.post1", "1.0a1", None, None, True),
790+
("<1.0.post1", "1.0rc1", None, None, True),
791+
("<1.0.post0", "1.0.dev0", None, None, True),
792+
("<1.0.post0", "1.0a1", None, None, True),
793+
("<1.0.post0", "1.0b1", None, None, True),
794+
("<1.0.post0", "1.0rc2", None, None, True),
795+
# <V.postN: dev of a different post is not a pre-release
796+
# of V.postN either
797+
("<1.0.post1", "1.0.post0.dev0", None, None, True),
798+
("<1.0.post2", "1.0.post1.dev0", None, None, True),
799+
# <V.postN: non-pre-release versions below V.postN
800+
("<1.0.post1", "1.0", None, None, True),
801+
("<1.0.post1", "1.0.post0", None, None, True),
802+
("<1.0.post1", "0.9", None, None, True),
803+
("<1.0.post0", "1.0", None, None, True),
804+
# <V.postN: higher post numbers
805+
("<1.0.post10", "1.0.dev0", None, None, True),
806+
("<1.0.post10", "1.0.post9.dev0", None, None, True),
807+
("<1.0.post10", "1.0.post9", None, None, True),
808+
# <V.postN: locals and different bases
809+
("<1.0.post1", "1.0+local", None, None, True),
810+
("<1.0.post1", "1.0.post0+local", None, None, True),
811+
("<1.0.post1", "0.9.dev0", None, None, True),
783812
("<=2.0", "1.0.dev1", False, None, False),
784813
("<=2.0a1", "1.0.dev1", False, None, False),
785814
("<=2.0", "1.0.dev1", None, False, False),
@@ -1933,6 +1962,26 @@ def test_filter_exclusionary_bridges(
19331962
(">=1!0,!=1!1.*,!=1!2.*,<1!3", True, "0!5.0", False),
19341963
(">=1!0,!=1!1.*,!=1!2.*,<1!3", False, "1!0.5", True),
19351964
(">=1!0,!=1!1.*,!=1!2.*,<1!3", False, "0!5.0", False),
1965+
# <V.postN combined with other specifiers: pre-releases of
1966+
# the base release are accepted (they are not pre-releases
1967+
# of V.postN).
1968+
("==1.0.dev0,<1.0.post1", None, "1.0.dev0", True),
1969+
("==1.0a1,<1.0.post0", None, "1.0a1", True),
1970+
("==1.0.post0.dev0,<1.0.post1", None, "1.0.post0.dev0", True),
1971+
(">=1.0,<1.0.post1", None, "1.0", True),
1972+
(">=1.0,<1.0.post1", None, "1.0.post0", True),
1973+
# 1.0.dev0 < 1.0, so it fails >=1.0 regardless of <
1974+
(">=1.0,<1.0.post1", True, "1.0.dev0", False),
1975+
# With a lower bound that includes pre-releases
1976+
(">=1.0.dev0,<1.0.post1", True, "1.0.dev0", True),
1977+
(">=1.0.dev0,<1.0.post1", True, "1.0.a1", True),
1978+
(">=1.0.dev0,<1.0.post1", True, "1.0.post0.dev0", True),
1979+
# != can remove non-pre-releases but pre-releases still match
1980+
(">=1.0.dev0,<1.0.post1,!=1.0,!=1.0.post0", True, "1.0.dev0", True),
1981+
(">=1.0.dev0,<1.0.post1,!=1.0,!=1.0.post0", True, "1.0.post0.dev0", True),
1982+
# Post-release survivors still match
1983+
(">=1.0.dev0,<1.0.post2,!=1.0,!=1.0.post0", True, "1.0.post1", True),
1984+
(">=1.0.dev0,<1.0.post2,!=1.0", True, "1.0.post0", True),
19361985
],
19371986
)
19381987
def test_contains_exclusionary_bridges(

0 commit comments

Comments
 (0)