153 lines
5.1 KiB
Python
153 lines
5.1 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from packaging.specifiers import SpecifierSet
|
|
from packaging.version import Version
|
|
from pypi._models import Package
|
|
|
|
_DEFAULT_UPLOAD = "2024-01-01T00:00:00Z"
|
|
|
|
|
|
def _payload(releases: dict[str, list[dict[str, Any]]]) -> dict[str, Any]:
|
|
# Inject a default upload_time into every non-empty dist so tests don't
|
|
# have to spell it out — `from_json` skips releases that lack one.
|
|
normalized = {
|
|
v: [{"upload_time_iso_8601": _DEFAULT_UPLOAD, **d} for d in dists]
|
|
for v, dists in releases.items()
|
|
}
|
|
return {"info": {"name": "demo", "summary": "demo package"}, "releases": normalized}
|
|
|
|
|
|
def test_versions_are_parsed_to_packaging_version() -> None:
|
|
pkg = Package.from_json(
|
|
_payload({
|
|
"1.0.0": [{"upload_time_iso_8601": "2024-01-01T00:00:00Z"}],
|
|
"2.0.0": [{"upload_time_iso_8601": "2024-06-01T00:00:00Z"}],
|
|
})
|
|
)
|
|
assert all(isinstance(v, Version) for v in pkg.versions)
|
|
assert pkg.versions == (Version("1.0.0"), Version("2.0.0"))
|
|
|
|
|
|
def test_releases_without_distributions_are_dropped() -> None:
|
|
pkg = Package.from_json(
|
|
_payload({
|
|
"1.0.0": [{}],
|
|
"0.5.0": [], # all distributions deleted on PyPI
|
|
})
|
|
)
|
|
assert pkg.versions == (Version("1.0.0"),)
|
|
|
|
|
|
def test_releases_without_upload_time_are_dropped() -> None:
|
|
raw = {
|
|
"info": {"name": "demo"},
|
|
"releases": {
|
|
"1.0.0": [{"upload_time_iso_8601": "2024-01-01T00:00:00Z"}],
|
|
"0.5.0": [{"filename": "demo-0.5.0.tar.gz"}], # missing upload_time
|
|
},
|
|
}
|
|
pkg = Package.from_json(raw)
|
|
assert pkg.versions == (Version("1.0.0"),)
|
|
|
|
|
|
def test_invalid_versions_are_dropped() -> None:
|
|
pkg = Package.from_json(
|
|
_payload({
|
|
"1.0.0": [{"upload_time_iso_8601": "2024-01-01T00:00:00Z"}],
|
|
"2004d": [{"upload_time_iso_8601": "2024-01-01T00:00:00Z"}],
|
|
})
|
|
)
|
|
assert pkg.versions == (Version("1.0.0"),)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("releases", "expected"),
|
|
[
|
|
({"1.0.0": [{}], "2.0.0": [{}]}, Version("2.0.0")),
|
|
({"1.0.0": [{}], "2.0.0a1": [{}]}, Version("1.0.0")),
|
|
({"1.0.0": [{}], "2.0.0.dev1": [{}]}, Version("1.0.0")),
|
|
({"1.0.0": [{"yanked": True}], "0.9.0": [{}]}, Version("0.9.0")),
|
|
({}, None),
|
|
],
|
|
)
|
|
def test_latest_version_skips_pre_dev_and_yanked(
|
|
releases: dict[str, list[dict[str, Any]]], expected: Version | None
|
|
) -> None:
|
|
pkg = Package.from_json(_payload(releases))
|
|
assert pkg.latest_version == expected
|
|
|
|
|
|
def test_get_release_accepts_str_or_version() -> None:
|
|
pkg = Package.from_json(_payload({"1.2.3": [{}]}))
|
|
by_str = pkg.get_release("1.2.3")
|
|
by_version = pkg.get_release(Version("1.2.3"))
|
|
assert by_str is not None
|
|
assert by_str is by_version
|
|
assert pkg.get_release("9.9.9") is None
|
|
|
|
|
|
def test_release_upload_time_picks_earliest() -> None:
|
|
pkg = Package.from_json(
|
|
_payload({
|
|
"1.0.0": [
|
|
{"upload_time_iso_8601": "2024-03-01T00:00:00Z"},
|
|
{"upload_time_iso_8601": "2024-01-01T00:00:00Z"},
|
|
],
|
|
})
|
|
)
|
|
release = pkg.get_release("1.0.0")
|
|
assert release is not None
|
|
assert release.upload_time is not None
|
|
assert release.upload_time.year == 2024
|
|
assert release.upload_time.month == 1
|
|
|
|
|
|
def test_release_yanked_only_when_all_distributions_yanked() -> None:
|
|
pkg = Package.from_json(_payload({"1.0.0": [{"yanked": True}, {"yanked": False}]}))
|
|
release = pkg.get_release("1.0.0")
|
|
assert release is not None
|
|
assert release.yanked is False
|
|
|
|
|
|
def test_release_yanked_when_every_distribution_yanked() -> None:
|
|
pkg = Package.from_json(_payload({"1.0.0": [{"yanked": True}, {"yanked": True}]}))
|
|
release = pkg.get_release("1.0.0")
|
|
assert release is not None
|
|
assert release.yanked is True
|
|
|
|
|
|
def test_release_requires_python_parsed_to_specifier_set() -> None:
|
|
pkg = Package.from_json(_payload({"1.0.0": [{"requires_python": ">=3.10,<4"}]}))
|
|
release = pkg.get_release("1.0.0")
|
|
assert release is not None
|
|
assert release.requires_python == SpecifierSet(">=3.10,<4")
|
|
assert Version("3.11") in release.requires_python
|
|
assert Version("3.9") not in release.requires_python
|
|
|
|
|
|
def test_release_requires_python_picks_first_non_empty() -> None:
|
|
pkg = Package.from_json(
|
|
_payload({
|
|
"1.0.0": [
|
|
{"requires_python": ""},
|
|
{"requires_python": ">=3.10"},
|
|
{"requires_python": ">=3.11"},
|
|
]
|
|
})
|
|
)
|
|
release = pkg.get_release("1.0.0")
|
|
assert release is not None
|
|
assert release.requires_python == SpecifierSet(">=3.10")
|
|
|
|
|
|
@pytest.mark.parametrize("raw", [None, "", "not a specifier"])
|
|
def test_release_requires_python_none_for_missing_or_invalid(raw: str | None) -> None:
|
|
payload_dist = {} if raw is None else {"requires_python": raw}
|
|
pkg = Package.from_json(_payload({"1.0.0": [payload_dist]}))
|
|
release = pkg.get_release("1.0.0")
|
|
assert release is not None
|
|
assert release.requires_python is None
|