Skip to content

Commit

Permalink
utils.link: add PEP 658 (metadata) support (python-poetry#333)
Browse files Browse the repository at this point in the history
  • Loading branch information
abn authored and bostonrwalker committed Aug 29, 2022
1 parent 21dad13 commit 6cc397b
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 9 deletions.
41 changes: 41 additions & 0 deletions src/poetry/core/packages/utils/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/*
Expand All @@ -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 `<hashname>=<hashvalue>` 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
Expand All @@ -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})"
Expand Down Expand Up @@ -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)
Expand Down
115 changes: 106 additions & 9 deletions tests/packages/utils/test_utils_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 6cc397b

Please sign in to comment.