Skip to content

Commit

Permalink
add LinkHash.{from_archive_info(),__post_init__()}
Browse files Browse the repository at this point in the history
these two objects are intended to be fungible, but play different roles, since LinkHash actually
parses its input and ArchiveInfo does not. ArchiveInfo's JSON (de)?serialization does not employ
hash name or value validation, while LinkHash does not offer a JSON serialization.
  • Loading branch information
cosmicexplorer committed May 10, 2022
1 parent 76520a5 commit c068eb5
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 1 deletion.
17 changes: 16 additions & 1 deletion src/pip/_internal/models/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
class LinkHash:
"""Links to content may have embedded hash values. This class parses those.
`name` must be any member of `_SUPPORTED_HASHES`."""
`name` must be any member of `_SUPPORTED_HASHES`.
This class can be converted to and from `ArchiveInfo`. While ArchiveInfo intends to
be JSON-serializable to conform to PEP 610, this class contains the logic for
parsing a hash name and value for correctness, and then checking whether that hash
conforms to a schema with `.is_hash_allowed()`."""

name: str
value: str
Expand All @@ -52,6 +57,9 @@ class LinkHash:
)
)

def __post_init__(self) -> None:
assert self._hash_re.match(f"{self.name}={self.value}")

@classmethod
@functools.lru_cache(maxsize=None)
def split_hash_name_and_value(cls, url: str) -> Optional["LinkHash"]:
Expand All @@ -66,6 +74,13 @@ def to_archive_info(self) -> ArchiveInfo:
"""Convert to ArchiveInfo to form a DirectUrl instance (see PEP 610)."""
return ArchiveInfo(hash=f"{self.name}={self.value}")

@classmethod
def from_archive_info(cls, info: ArchiveInfo) -> Optional["LinkHash"]:
"""Parse an ArchiveInfo hash into a LinkHash instance."""
if info.hash is None:
return None
return cls.split_hash_name_and_value(info.hash)

def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
"""
Return True if the current hash is allowed by `hashes`.
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/test_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,3 +1048,36 @@ def expand_path(path: str) -> str:
)
def test_link_hash_parsing(url: str, result: Optional[LinkHash]) -> None:
assert LinkHash.split_hash_name_and_value(url) == result


@pytest.mark.parametrize(
"archive_info, link_hash",
[
(
ArchiveInfo(hash=None),
None,
),
(
ArchiveInfo(hash="sha256=aabe42af"),
LinkHash(name="sha256", value="aabe42af"),
),
# Test invalid hash strings, which ArchiveInfo doesn't validate.
(
# Invalid hash name.
ArchiveInfo(hash="sha500=aabe42af"),
None,
),
(
# Invalid hash value.
ArchiveInfo(hash="sha256=g42afbe"),
None,
),
],
)
def test_link_hash_archive_info_fungibility(
archive_info: ArchiveInfo,
link_hash: Optional[LinkHash],
) -> None:
assert LinkHash.from_archive_info(archive_info) == link_hash
if link_hash is not None:
assert link_hash.to_archive_info() == archive_info

0 comments on commit c068eb5

Please sign in to comment.