diff --git a/src/univers/versions.py b/src/univers/versions.py index 5d6101ac..56e121de 100644 --- a/src/univers/versions.py +++ b/src/univers/versions.py @@ -217,18 +217,52 @@ def build_value(cls, string): """ Return a packaging.version.LegacyVersion or packaging.version.Version """ - return packaging_version.Version(string) + return cls._coerce_pep440(string) @classmethod def is_valid(cls, string): try: # Note: we consider only modern pep440 versions as valid. legacy # will fail validation for now. - cls.build_value(string) + cls._coerce_pep440(string) return True except packaging_version.InvalidVersion: return False + @classmethod + def _coerce_pep440(cls, string): + """ + Return a packaging.version.Version, coercing a limited set of + legacy PyPI version forms that use '-' for a local version segment. + """ + try: + return packaging_version.Version(string) + except packaging_version.InvalidVersion: + normalized = cls._normalize_legacy_local(string) + if normalized: + return packaging_version.Version(normalized) + raise + + @classmethod + def _normalize_legacy_local(cls, string): + """ + Normalize legacy local versions like "2.0.1rc2-git" to + PEP 440-compatible "2.0.1rc2+git" when safe. + """ + if not string or "+" in string or "-" not in string: + return None + + base, local = string.rsplit("-", 1) + if not local or not local[0].isalpha(): + return None + + candidate = f"{base}+{local}" + try: + packaging_version.Version(candidate) + except packaging_version.InvalidVersion: + return None + return candidate + class EnhancedSemanticVersion(semantic_version.Version): @property diff --git a/tests/test_pypi_version.py b/tests/test_pypi_version.py index 7b6325d7..bb9113bc 100644 --- a/tests/test_pypi_version.py +++ b/tests/test_pypi_version.py @@ -21,6 +21,10 @@ def test_constructor(self): assert pypi_version.value == packaging_version.Version("2.4.5") self.assertRaises(InvalidVersion, PypiVersion, "2.//////") + def test_constructor_accepts_legacy_local(self): + pypi_version = PypiVersion("2.0.1rc2-git") + assert str(pypi_version) == "2.0.1rc2+git" + def test_compare(self): pypi_version = PypiVersion("2.4.5") assert pypi_version == PypiVersion("2.4.5") diff --git a/tests/test_versions.py b/tests/test_versions.py index ab3007d3..ef2bba9b 100644 --- a/tests/test_versions.py +++ b/tests/test_versions.py @@ -39,6 +39,7 @@ def test_pypi_version(): assert PypiVersion("1.2.3") != PypiVersion("1.2.4") assert PypiVersion("1") == PypiVersion("1.0") assert PypiVersion.is_valid("1.2.3") + assert PypiVersion.is_valid("2.0.1rc2-git") assert not PypiVersion.is_valid("1.2.3a-1-a") assert PypiVersion.normalize("v1.2.3") == "1.2.3" assert PypiVersion("1").satisfies(