diff --git a/dep_freshness/tests/test_versions.py b/dep_freshness/tests/test_versions.py index d988267..3c73a4e 100644 --- a/dep_freshness/tests/test_versions.py +++ b/dep_freshness/tests/test_versions.py @@ -45,12 +45,19 @@ def test_newest_stable_ignores_unparseable_entries(): # PyPI stub packages date-stamp a fourth component. Capping the regex at # three reported this exact pin as unpinned *against its own value*. ("==2.33.0.20260712", "2.33.0.20260712"), + # PEP 440 post/dev/pre releases are exact pins too (pytest-asyncio). + ("1.4.1.post1", "1.4.1.post1"), ("==1.4.1.post1", "1.4.1.post1"), + ("2.0.post3", "2.0.post3"), ("1.0.dev2", "1.0.dev2"), + ("==2.0rc1", "2.0rc1"), ("1.2.3a1", "1.2.3a1"), ("1.2b4", "1.2b4"), + ("1.0.post1.dev2", "1.0.post1.dev2"), ]) def test_exact_pins_are_recognised(raw, expected): assert exact_pin(raw) == expected -@pytest.mark.parametrize("raw", ["^1.6.0", ">=1.6.0", "any", "*", "", ">=1,<2", "~1.2"]) +@pytest.mark.parametrize("raw", ["^1.6.0", ">=1.6.0", "any", "*", "", ">=1,<2", "~1.2", + ">=1.4.1.post1", "~=1.4", "1.4.post", "1.4.postx", "1.4.1.post1,<2", +]) def test_ranges_are_not_exact_pins(raw): assert exact_pin(raw) is None @@ -61,6 +68,13 @@ def test_behind_compares_versions(): assert not behind("1.8.0", "1.7.0") +def test_a_post_release_is_newer_than_its_base_release(): + assert behind("1.4.1", "1.4.1.post1") + assert not behind("1.4.1.post1", "1.4.1") + assert not is_prerelease("1.4.1.post1") + assert newest_stable(["1.4.1", "1.4.1.post1", "1.4.0"]) == "1.4.1.post1" + + def test_behind_never_fires_on_unparseable_input(): """An unpinned constraint is UNPINNED, never dressed up as staleness.""" assert not behind("^1.6.0", "1.7.0") diff --git a/dep_freshness/versions.py b/dep_freshness/versions.py index 3d19abe..5fa79a2 100644 --- a/dep_freshness/versions.py +++ b/dep_freshness/versions.py @@ -160,10 +160,12 @@ def behind(pinned: str, latest: str) -> bool: # date-stamp a fourth (`types-requests==2.33.0.20260712`), and capping at # three reported an exact pin as unpinned against itself. A project-name # prefix (`natural-comparator-1.1`) is part of the pin, since it is what the -# registry's version list spells. +# registry's version list spells. PEP 440 `(a|b|rc)N`, `.postN` and `.devN` +# follow the release (`pytest-asyncio==1.4.1.post1`), before the semver suffix. EXACT = re.compile( r"^\s*(?:==\s*)?(?:[A-Za-z][A-Za-z_.-]*?-(?=v?\d))?" - r"v?\d+(?:\.\d+)*(?:[-+][0-9A-Za-z.-]+)?\s*$" + r"v?\d+(?:\.\d+)*(?:(?:a|b|rc)\d+)?(?:\.post\d+)?(?:\.dev\d+)?" + r"(?:[-+][0-9A-Za-z.-]+)?\s*$" )