Skip to content

Commit 37dc67f

Browse files
authored
šŸ› fix(discovery): match prerelease versions against major.minor specs (#48)
1 parent 3b88f54 commit 37dc67f

File tree

2 files changed

+39
-23
lines changed

2 files changed

+39
-23
lines changed

ā€Žsrc/python_discovery/_py_info.pyā€Ž

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -469,12 +469,24 @@ def _satisfies_version_specifier(self, spec: PythonSpec) -> bool:
469469
if spec.version_specifier is None: # pragma: no cover
470470
return True
471471
version_info = self.version_info
472-
release = f"{version_info.major}.{version_info.minor}.{version_info.micro}"
473-
if version_info.releaselevel != "final":
474-
suffix = {"alpha": "a", "beta": "b", "candidate": "rc"}.get(version_info.releaselevel)
475-
if suffix is not None: # pragma: no branch # releaselevel is always alpha/beta/candidate here
472+
for specifier in spec.version_specifier:
473+
assert specifier.version is not None # noqa: S101
474+
numeric_version = specifier.version_str
475+
for prefix in ("rc", "b", "a"):
476+
if prefix in numeric_version:
477+
numeric_version = numeric_version.split(prefix)[0]
478+
break
479+
precision = numeric_version.count(".") + 1
480+
release = ".".join(str(c) for c in [version_info.major, version_info.minor, version_info.micro][:precision])
481+
if (
482+
version_info.releaselevel != "final"
483+
and (precision == 3 or specifier.version.pre_type is not None) # noqa: PLR2004
484+
and (suffix := {"alpha": "a", "beta": "b", "candidate": "rc"}.get(version_info.releaselevel))
485+
):
476486
release = f"{release}{suffix}{version_info.serial}"
477-
return spec.version_specifier.contains(release)
487+
if not specifier.contains(release):
488+
return False
489+
return True
478490

479491
_current_system = None
480492
_current = None

ā€Žtests/test_py_info_extra.pyā€Ž

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -276,25 +276,29 @@ def test_satisfies_version_specifier_fails() -> None:
276276
assert CURRENT.satisfies(spec, impl_must_match=False) is False
277277

278278

279-
def test_satisfies_prerelease_version() -> None:
279+
@pytest.mark.parametrize(
280+
("version_info", "spec_str", "expected"),
281+
[
282+
pytest.param(VersionInfo(3, 14, 0, "alpha", 1), ">=3.14.0a1", True, id="alpha_match_exact"),
283+
pytest.param(VersionInfo(3, 14, 0, "beta", 1), ">=3.14.0b1", True, id="beta_match_exact"),
284+
pytest.param(VersionInfo(3, 14, 0, "candidate", 1), ">=3.14.0rc1", True, id="rc_match_exact"),
285+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), ">=3.15", True, id="prerelease_match_major_minor"),
286+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), ">=3.15.0", False, id="prerelease_not_match_full_precision"),
287+
pytest.param(VersionInfo(3, 15, 0, "alpha", 5), "<3.15.0a6", True, id="earlier_prerelease_less_than"),
288+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), "<3.15.0a6", False, id="prerelease_not_less_than_itself"),
289+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), ">=3.15.0a6", True, id="prerelease_match_itself"),
290+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), ">=3.15.0a7", False, id="prerelease_not_match_later"),
291+
pytest.param(VersionInfo(3, 15, 0, "final", 0), ">=3.15.0a6", True, id="final_greater_than_prerelease"),
292+
pytest.param(VersionInfo(3, 15, 0, "final", 0), "<3.15.0a6", False, id="final_not_less_than_prerelease"),
293+
pytest.param(VersionInfo(3, 15, 0, "final", 0), ">=3.15", True, id="final_match_major_minor"),
294+
pytest.param(VersionInfo(3, 15, 1, "alpha", 1), ">=3.15.0", True, id="later_micro_prerelease_match"),
295+
],
296+
)
297+
def test_satisfies_version_specifier_prerelease(version_info: VersionInfo, spec_str: str, expected: bool) -> None:
280298
info = copy.deepcopy(CURRENT)
281-
info.version_info = VersionInfo(3, 14, 0, "alpha", 1)
282-
spec = PythonSpec.from_string_spec(">=3.14.0a1")
283-
assert info.satisfies(spec, impl_must_match=False) is True
284-
285-
286-
def test_satisfies_prerelease_beta() -> None:
287-
info = copy.deepcopy(CURRENT)
288-
info.version_info = VersionInfo(3, 14, 0, "beta", 1)
289-
spec = PythonSpec.from_string_spec(">=3.14.0b1")
290-
assert info.satisfies(spec, impl_must_match=False) is True
291-
292-
293-
def test_satisfies_prerelease_candidate() -> None:
294-
info = copy.deepcopy(CURRENT)
295-
info.version_info = VersionInfo(3, 14, 0, "candidate", 1)
296-
spec = PythonSpec.from_string_spec(">=3.14.0rc1")
297-
assert info.satisfies(spec, impl_must_match=False) is True
299+
info.version_info = version_info
300+
spec = PythonSpec.from_string_spec(spec_str)
301+
assert info.satisfies(spec, impl_must_match=False) is expected
298302

299303

300304
def test_satisfies_path_not_abs_basename_match() -> None:

0 commit comments

Comments
Ā (0)