From 24ee3e1020ec5b95c10df50763a86fa01a2e2039 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 3 May 2022 06:43:41 +0200 Subject: [PATCH] utils.link: add PEP 658 (metadata) support (#333) --- src/poetry/core/packages/utils/link.py | 41 +++++++++ tests/packages/utils/test_utils_link.py | 115 ++++++++++++++++++++++-- 2 files changed, 147 insertions(+), 9 deletions(-) diff --git a/src/poetry/core/packages/utils/link.py b/src/poetry/core/packages/utils/link.py index a06348584..86367e475 100644 --- a/src/poetry/core/packages/utils/link.py +++ b/src/poetry/core/packages/utils/link.py @@ -16,6 +16,7 @@ def __init__( url: str, comes_from: Any | None = None, requires_python: str | None = None, + metadata: str | bool | None = None, ) -> None: """ Object representing a parsed link from https://pypi.python.org/simple/* @@ -28,6 +29,11 @@ def __init__( String containing the `Requires-Python` metadata field, specified in PEP 345. This may be specified by a data-requires-python attribute in the HTML link tag, as described in PEP 503. + metadata: + String of the syntax `=` representing the hash + of the Core Metadata file. This may be specified by a + data-dist-info-metadata attribute in the HTML link tag, as described + in PEP 658. """ # url can be a UNC windows share @@ -38,6 +44,13 @@ def __init__( self.comes_from = comes_from self.requires_python = requires_python if requires_python else None + if isinstance(metadata, str): + metadata = {"true": True, "": False, "false": False}.get( + metadata.strip().lower(), metadata + ) + + self._metadata = metadata + def __str__(self) -> str: if self.requires_python: rp = f" (requires-python:{self.requires_python})" @@ -136,6 +149,34 @@ def subdirectory_fragment(self) -> str | None: _hash_re = re.compile(r"(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)") + @property + def has_metadata(self) -> bool: + if self._metadata is None: + return False + return bool(self._metadata) and (self.is_wheel or self.is_sdist) + + @property + def metadata_url(self) -> str | None: + if self.has_metadata: + return f"{self.url_without_fragment.split('?', 1)[0]}.metadata" + return None + + @property + def metadata_hash(self) -> str | None: + if self.has_metadata and isinstance(self._metadata, str): + match = self._hash_re.search(self._metadata) + if match: + return match.group(2) + return None + + @property + def metadata_hash_name(self) -> str | None: + if self.has_metadata and isinstance(self._metadata, str): + match = self._hash_re.search(self._metadata) + if match: + return match.group(1) + return None + @property def hash(self) -> str | None: match = self._hash_re.search(self.url) diff --git a/tests/packages/utils/test_utils_link.py b/tests/packages/utils/test_utils_link.py index c7067bad0..d31ce2440 100644 --- a/tests/packages/utils/test_utils_link.py +++ b/tests/packages/utils/test_utils_link.py @@ -4,20 +4,117 @@ from hashlib import sha256 +import pytest + from poetry.core.packages.utils.link import Link -def make_url(ext: str) -> Link: - checksum = sha256(str(uuid.uuid4()).encode()) +def make_checksum() -> str: + return sha256(str(uuid.uuid4()).encode()).hexdigest() + + +@pytest.fixture() +def file_checksum() -> str: + return make_checksum() + + +@pytest.fixture() +def metadata_checksum() -> str: + return make_checksum() + + +def make_url( + ext: str, file_checksum: str | None = None, metadata_checksum: str | None = None +) -> Link: + file_checksum = file_checksum or make_checksum() return Link( "https://files.pythonhosted.org/packages/16/52/dead/" - f"demo-1.0.0.{ext}#sha256={checksum}" + f"demo-1.0.0.{ext}#sha256={file_checksum}", + metadata=f"sha256={metadata_checksum}" if metadata_checksum else None, + ) + + +def test_package_link_hash(file_checksum: str) -> None: + link = make_url(ext="whl", file_checksum=file_checksum) + assert link.hash_name == "sha256" + assert link.hash == file_checksum + assert link.show_url == "demo-1.0.0.whl" + + # this is legacy PEP 503, no metadata hash is present + assert not link.has_metadata + assert not link.metadata_url + assert not link.metadata_hash + assert not link.metadata_hash_name + + +@pytest.mark.parametrize( + ("ext", "check"), + [ + ("whl", "wheel"), + ("egg", "egg"), + ("tar.gz", "sdist"), + ("zip", "sdist"), + ("cp36-cp36m-manylinux1_x86_64.whl", "wheel"), + ], +) +def test_package_link_is_checks(ext: str, check: str) -> None: + link = make_url(ext=ext) + assert getattr(link, f"is_{check}") + + +@pytest.mark.parametrize( + ("ext", "has_metadata"), + [("whl", True), ("egg", False), ("tar.gz", True), ("zip", True)], +) +def test_package_link_pep658( + ext: str, has_metadata: bool, metadata_checksum: str +) -> None: + link = make_url(ext=ext, metadata_checksum=metadata_checksum) + + if has_metadata: + assert link.has_metadata + assert link.metadata_url == f"{link.url_without_fragment}.metadata" + assert link.metadata_hash == metadata_checksum + assert link.metadata_hash_name == "sha256" + else: + assert not link.has_metadata + assert not link.metadata_url + assert not link.metadata_hash + assert not link.metadata_hash_name + + +def test_package_link_pep658_no_default_metadata() -> None: + link = make_url(ext="whl") + + assert not link.has_metadata + assert not link.metadata_url + assert not link.metadata_hash + assert not link.metadata_hash_name + + +@pytest.mark.parametrize( + ("metadata", "has_metadata"), + [ + ("true", True), + ("false", False), + ("", False), + ], +) +def test_package_link_pep653_non_hash_metadata_value( + file_checksum: str, metadata: str | bool, has_metadata: bool +) -> None: + link = Link( + "https://files.pythonhosted.org/packages/16/52/dead/" + f"demo-1.0.0.whl#sha256={file_checksum}", + metadata=metadata, ) + if has_metadata: + assert link.has_metadata + assert link.metadata_url == f"{link.url_without_fragment}.metadata" + else: + assert not link.has_metadata + assert not link.metadata_url -def test_package_link_is_checks() -> None: - assert make_url("egg").is_egg - assert make_url("tar.gz").is_sdist - assert make_url("zip").is_sdist - assert make_url("exe").is_wininst - assert make_url("cp36-cp36m-manylinux1_x86_64.whl").is_wheel + assert not link.metadata_hash + assert not link.metadata_hash_name