diff --git a/src/packaging/specifiers.py b/src/packaging/specifiers.py index b564e538b..1f4123f6c 100644 --- a/src/packaging/specifiers.py +++ b/src/packaging/specifiers.py @@ -52,15 +52,12 @@ def _public_version(version: Version) -> Version: return version.__replace__(local=None) -def _base_version(version: Version) -> Version: - if ( - version.pre is None - and version.post is None - and version.dev is None - and version.local is None - ): - return version - return version.__replace__(pre=None, post=None, dev=None, local=None) +def _post_base(version: Version) -> Version: + """The version that *version* is a post-release of. + + 1.0.post1 -> 1.0, 1.0a1.post0 -> 1.0a1, 1.0.post0.dev1 -> 1.0. + """ + return version.__replace__(post=None, dev=None, local=None) def _earliest_prerelease(version: Version) -> Version: @@ -594,14 +591,12 @@ def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool: if not prospective > spec: return False - # This special case is here so that, unless the specifier itself - # includes is a post-release version, that we do not accept - # post-release versions for the version mentioned in the specifier - # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + # The spec says: ">V MUST NOT allow a post-release of the specified + # version unless the specified version is itself a post-release." if ( not spec.is_postrelease and prospective.is_postrelease - and _base_version(prospective) == _base_version(spec) + and _post_base(prospective) == spec ): return False diff --git a/tests/test_specifiers.py b/tests/test_specifiers.py index 3a374e46a..5ae177a67 100644 --- a/tests/test_specifiers.py +++ b/tests/test_specifiers.py @@ -780,6 +780,43 @@ def test_specifier_prereleases_detection( ("<=2.0.dev1", "1.0a1", None, None, True), ("<2.0", "2.0a1", None, None, False), ("<2.0a2", "2.0a1", None, None, True), + # >V.devN: post-releases of V.devN itself are excluded + # (V.devN can't have post-releases in PEP 440, so nothing + # to exclude; these just confirm ordering still works) + (">1.0.dev1", "1.0.dev0", None, None, False), + (">1.0.dev1", "1.0.dev2", None, None, True), + # >V.devN: post-releases of the base release are NOT + # post-releases of V.devN, so they are accepted + (">1.0.dev1", "1.0.post0", None, None, True), + (">1.0.dev1", "1.0.post1", None, None, True), + (">1.0.dev0", "1.0.post0", None, None, True), + # >V.preN: post-releases of the base release are NOT + # post-releases of V.preN, so they are accepted + (">1.0a1", "1.0.post0", None, None, True), + (">1.0b1", "1.0.post0", None, None, True), + (">1.0rc1", "1.0.post0", None, None, True), + # >V.preN: post-releases of the pre-release itself + # ARE excluded + (">1.0a1", "1.0a1.post0", None, None, False), + (">1.0b2", "1.0b2.post0", None, None, False), + (">1.0rc1", "1.0rc1.post0", None, None, False), + # >V.preN: post of a different pre is not a post-release + # of V.preN either + (">1.0a1", "1.0a2.post0", None, None, True), + (">1.0b1", "1.0b2.post0", None, None, True), + # >V.devN: non-post-release versions above V.devN + (">1.0.dev1", "1.0", None, None, True), + (">1.0.dev1", "1.0a1", None, None, True), + (">1.0.dev1", "1.1", None, None, True), + # >V (final): post-releases of V are still excluded + (">1.0", "1.0.post0", None, None, False), + (">1.0", "1.0.post1", None, None, False), + # >V (final): post-releases of a different base are fine + (">1.0", "2.0.post0", None, None, True), + (">1.0", "0.9.post0", None, None, False), + # >V.devN: locals and different bases + (">1.0.dev1", "1.1.post0", None, None, True), + (">1.0.dev1", "0.9.post0", None, None, False), # =1!0,!=1!1.*,!=1!2.*,<1!3", True, "0!5.0", False), (">=1!0,!=1!1.*,!=1!2.*,<1!3", False, "1!0.5", True), (">=1!0,!=1!1.*,!=1!2.*,<1!3", False, "0!5.0", False), + # >V.devN combined with other specifiers: post-releases of + # the base release are accepted (they are not post-releases + # of V.devN). + (">1.0.dev1,==1.0.post0", None, "1.0.post0", True), + (">1.0.dev1,==1.0.post1", None, "1.0.post1", True), + (">1.0a1,==1.0.post0", None, "1.0.post0", True), + (">1.0.dev1,<=2.0", None, "1.0.post0", True), + (">1.0.dev1,<=2.0", None, "1.0", True), + # >V.preN: post of the pre-release itself is still excluded + (">1.0a1,<=2.0", True, "1.0a1.post0", False), + # With an upper bound that includes post-releases + (">1.0.dev1,<=1.0.post1", None, "1.0.post0", True), + (">1.0.dev1,<=1.0.post1", None, "1.0.post1", True), + (">1.0.dev1,<=1.0.post1", None, "1.0", True), + # != can remove some versions but post-releases still match + (">1.0.dev1,!=1.0,<=2.0", None, "1.0.post0", True), + (">1.0.dev1,!=1.0,!=1.0.post0,<=2.0", None, "1.0.post1", True), #