Skip to content

Commit 647f8db

Browse files
authored
utils.link: add PEP 658 (metadata) support (#333)
1 parent 9e3796b commit 647f8db

File tree

2 files changed

+147
-9
lines changed

2 files changed

+147
-9
lines changed

src/poetry/core/packages/utils/link.py

+41
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def __init__(
1616
url: str,
1717
comes_from: Any | None = None,
1818
requires_python: str | None = None,
19+
metadata: str | bool | None = None,
1920
) -> None:
2021
"""
2122
Object representing a parsed link from https://pypi.python.org/simple/*
@@ -28,6 +29,11 @@ def __init__(
2829
String containing the `Requires-Python` metadata field, specified
2930
in PEP 345. This may be specified by a data-requires-python
3031
attribute in the HTML link tag, as described in PEP 503.
32+
metadata:
33+
String of the syntax `<hashname>=<hashvalue>` representing the hash
34+
of the Core Metadata file. This may be specified by a
35+
data-dist-info-metadata attribute in the HTML link tag, as described
36+
in PEP 658.
3137
"""
3238

3339
# url can be a UNC windows share
@@ -38,6 +44,13 @@ def __init__(
3844
self.comes_from = comes_from
3945
self.requires_python = requires_python if requires_python else None
4046

47+
if isinstance(metadata, str):
48+
metadata = {"true": True, "": False, "false": False}.get(
49+
metadata.strip().lower(), metadata
50+
)
51+
52+
self._metadata = metadata
53+
4154
def __str__(self) -> str:
4255
if self.requires_python:
4356
rp = f" (requires-python:{self.requires_python})"
@@ -136,6 +149,34 @@ def subdirectory_fragment(self) -> str | None:
136149

137150
_hash_re = re.compile(r"(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)")
138151

152+
@property
153+
def has_metadata(self) -> bool:
154+
if self._metadata is None:
155+
return False
156+
return bool(self._metadata) and (self.is_wheel or self.is_sdist)
157+
158+
@property
159+
def metadata_url(self) -> str | None:
160+
if self.has_metadata:
161+
return f"{self.url_without_fragment.split('?', 1)[0]}.metadata"
162+
return None
163+
164+
@property
165+
def metadata_hash(self) -> str | None:
166+
if self.has_metadata and isinstance(self._metadata, str):
167+
match = self._hash_re.search(self._metadata)
168+
if match:
169+
return match.group(2)
170+
return None
171+
172+
@property
173+
def metadata_hash_name(self) -> str | None:
174+
if self.has_metadata and isinstance(self._metadata, str):
175+
match = self._hash_re.search(self._metadata)
176+
if match:
177+
return match.group(1)
178+
return None
179+
139180
@property
140181
def hash(self) -> str | None:
141182
match = self._hash_re.search(self.url)

tests/packages/utils/test_utils_link.py

+106-9
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,117 @@
44

55
from hashlib import sha256
66

7+
import pytest
8+
79
from poetry.core.packages.utils.link import Link
810

911

10-
def make_url(ext: str) -> Link:
11-
checksum = sha256(str(uuid.uuid4()).encode())
12+
def make_checksum() -> str:
13+
return sha256(str(uuid.uuid4()).encode()).hexdigest()
14+
15+
16+
@pytest.fixture()
17+
def file_checksum() -> str:
18+
return make_checksum()
19+
20+
21+
@pytest.fixture()
22+
def metadata_checksum() -> str:
23+
return make_checksum()
24+
25+
26+
def make_url(
27+
ext: str, file_checksum: str | None = None, metadata_checksum: str | None = None
28+
) -> Link:
29+
file_checksum = file_checksum or make_checksum()
1230
return Link(
1331
"https://files.pythonhosted.org/packages/16/52/dead/"
14-
f"demo-1.0.0.{ext}#sha256={checksum}"
32+
f"demo-1.0.0.{ext}#sha256={file_checksum}",
33+
metadata=f"sha256={metadata_checksum}" if metadata_checksum else None,
34+
)
35+
36+
37+
def test_package_link_hash(file_checksum: str) -> None:
38+
link = make_url(ext="whl", file_checksum=file_checksum)
39+
assert link.hash_name == "sha256"
40+
assert link.hash == file_checksum
41+
assert link.show_url == "demo-1.0.0.whl"
42+
43+
# this is legacy PEP 503, no metadata hash is present
44+
assert not link.has_metadata
45+
assert not link.metadata_url
46+
assert not link.metadata_hash
47+
assert not link.metadata_hash_name
48+
49+
50+
@pytest.mark.parametrize(
51+
("ext", "check"),
52+
[
53+
("whl", "wheel"),
54+
("egg", "egg"),
55+
("tar.gz", "sdist"),
56+
("zip", "sdist"),
57+
("cp36-cp36m-manylinux1_x86_64.whl", "wheel"),
58+
],
59+
)
60+
def test_package_link_is_checks(ext: str, check: str) -> None:
61+
link = make_url(ext=ext)
62+
assert getattr(link, f"is_{check}")
63+
64+
65+
@pytest.mark.parametrize(
66+
("ext", "has_metadata"),
67+
[("whl", True), ("egg", False), ("tar.gz", True), ("zip", True)],
68+
)
69+
def test_package_link_pep658(
70+
ext: str, has_metadata: bool, metadata_checksum: str
71+
) -> None:
72+
link = make_url(ext=ext, metadata_checksum=metadata_checksum)
73+
74+
if has_metadata:
75+
assert link.has_metadata
76+
assert link.metadata_url == f"{link.url_without_fragment}.metadata"
77+
assert link.metadata_hash == metadata_checksum
78+
assert link.metadata_hash_name == "sha256"
79+
else:
80+
assert not link.has_metadata
81+
assert not link.metadata_url
82+
assert not link.metadata_hash
83+
assert not link.metadata_hash_name
84+
85+
86+
def test_package_link_pep658_no_default_metadata() -> None:
87+
link = make_url(ext="whl")
88+
89+
assert not link.has_metadata
90+
assert not link.metadata_url
91+
assert not link.metadata_hash
92+
assert not link.metadata_hash_name
93+
94+
95+
@pytest.mark.parametrize(
96+
("metadata", "has_metadata"),
97+
[
98+
("true", True),
99+
("false", False),
100+
("", False),
101+
],
102+
)
103+
def test_package_link_pep653_non_hash_metadata_value(
104+
file_checksum: str, metadata: str | bool, has_metadata: bool
105+
) -> None:
106+
link = Link(
107+
"https://files.pythonhosted.org/packages/16/52/dead/"
108+
f"demo-1.0.0.whl#sha256={file_checksum}",
109+
metadata=metadata,
15110
)
16111

112+
if has_metadata:
113+
assert link.has_metadata
114+
assert link.metadata_url == f"{link.url_without_fragment}.metadata"
115+
else:
116+
assert not link.has_metadata
117+
assert not link.metadata_url
17118

18-
def test_package_link_is_checks() -> None:
19-
assert make_url("egg").is_egg
20-
assert make_url("tar.gz").is_sdist
21-
assert make_url("zip").is_sdist
22-
assert make_url("exe").is_wininst
23-
assert make_url("cp36-cp36m-manylinux1_x86_64.whl").is_wheel
119+
assert not link.metadata_hash
120+
assert not link.metadata_hash_name

0 commit comments

Comments
 (0)