Skip to content

Commit 0233bf2

Browse files
committed
Merge link_hash back into _hashes
Commit bad03ef introduced the new link_hash attribute that holds the link's hash info, but that attribute does the same thing as _hashes, and some existing usages still populate that old attribute. Since the plural variant covers more use cases (a file can be hashed with multiple algorithms), we restore the old logic that uses _hashes before the commit, and consolidate link_hash back into that attribute.
1 parent ba38c33 commit 0233bf2

File tree

2 files changed

+34
-36
lines changed

2 files changed

+34
-36
lines changed

src/pip/_internal/models/link.py

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ def split_hash_name_and_value(cls, url: str) -> Optional["LinkHash"]:
7979
name, value = match.groups()
8080
return cls(name=name, value=value)
8181

82+
def as_dict(self) -> Dict[str, str]:
83+
return {self.name: self.value}
84+
8285
def as_hashes(self) -> Hashes:
8386
"""Return a Hashes instance which checks only for the current hash."""
8487
return Hashes({self.name: [self.value]})
@@ -165,7 +168,6 @@ class Link(KeyBasedCompareMixin):
165168
"requires_python",
166169
"yanked_reason",
167170
"dist_info_metadata",
168-
"link_hash",
169171
"cache_link_parsing",
170172
"egg_fragment",
171173
]
@@ -177,7 +179,6 @@ def __init__(
177179
requires_python: Optional[str] = None,
178180
yanked_reason: Optional[str] = None,
179181
dist_info_metadata: Optional[str] = None,
180-
link_hash: Optional[LinkHash] = None,
181182
cache_link_parsing: bool = True,
182183
hashes: Optional[Mapping[str, str]] = None,
183184
) -> None:
@@ -200,16 +201,11 @@ def __init__(
200201
attribute, if present, in a simple repository HTML link. This may be parsed
201202
into its own `Link` by `self.metadata_link()`. See PEP 658 for more
202203
information and the specification.
203-
:param link_hash: a checksum for the content the link points to. If not
204-
provided, this will be extracted from the link URL, if the URL has
205-
any checksum.
206204
:param cache_link_parsing: A flag that is used elsewhere to determine
207-
whether resources retrieved from this link
208-
should be cached. PyPI index urls should
209-
generally have this set to False, for
210-
example.
205+
whether resources retrieved from this link should be cached. PyPI
206+
URLs should generally have this set to False, for example.
211207
:param hashes: A mapping of hash names to digests to allow us to
212-
determine the validity of a download.
208+
determine the validity of a download.
213209
"""
214210

215211
# url can be a UNC windows share
@@ -220,13 +216,18 @@ def __init__(
220216
# Store the url as a private attribute to prevent accidentally
221217
# trying to set a new value.
222218
self._url = url
223-
self._hashes = hashes if hashes is not None else {}
219+
220+
link_hash = LinkHash.split_hash_name_and_value(url)
221+
hashes_from_link = {} if link_hash is None else link_hash.as_dict()
222+
if hashes is None:
223+
self._hashes = hashes_from_link
224+
else:
225+
self._hashes = {**hashes, **hashes_from_link}
224226

225227
self.comes_from = comes_from
226228
self.requires_python = requires_python if requires_python else None
227229
self.yanked_reason = yanked_reason
228230
self.dist_info_metadata = dist_info_metadata
229-
self.link_hash = link_hash or LinkHash.split_hash_name_and_value(self._url)
230231

231232
super().__init__(key=url, defining_class=Link)
232233

@@ -401,29 +402,26 @@ def metadata_link(self) -> Optional["Link"]:
401402
if self.dist_info_metadata is None:
402403
return None
403404
metadata_url = f"{self.url_without_fragment}.metadata"
404-
link_hash: Optional[LinkHash] = None
405405
# If data-dist-info-metadata="true" is set, then the metadata file exists,
406406
# but there is no information about its checksum or anything else.
407407
if self.dist_info_metadata != "true":
408408
link_hash = LinkHash.split_hash_name_and_value(self.dist_info_metadata)
409-
return Link(metadata_url, link_hash=link_hash)
409+
else:
410+
link_hash = None
411+
if link_hash is None:
412+
return Link(metadata_url)
413+
return Link(metadata_url, hashes=link_hash.as_dict())
410414

411-
def as_hashes(self) -> Optional[Hashes]:
412-
if self.link_hash is not None:
413-
return self.link_hash.as_hashes()
414-
return None
415+
def as_hashes(self) -> Hashes:
416+
return Hashes({k: [v] for k, v in self._hashes.items()})
415417

416418
@property
417419
def hash(self) -> Optional[str]:
418-
if self.link_hash is not None:
419-
return self.link_hash.value
420-
return None
420+
return next(iter(self._hashes.values()), None)
421421

422422
@property
423423
def hash_name(self) -> Optional[str]:
424-
if self.link_hash is not None:
425-
return self.link_hash.name
426-
return None
424+
return next(iter(self._hashes), None)
427425

428426
@property
429427
def show_url(self) -> str:
@@ -452,15 +450,15 @@ def is_yanked(self) -> bool:
452450

453451
@property
454452
def has_hash(self) -> bool:
455-
return self.link_hash is not None
453+
return bool(self._hashes)
456454

457455
def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
458456
"""
459457
Return True if the link has a hash and it is allowed by `hashes`.
460458
"""
461-
if self.link_hash is None:
459+
if hashes is None:
462460
return False
463-
return self.link_hash.is_hash_allowed(hashes)
461+
return any(hashes.is_hash_allowed(k, v) for k, v in self._hashes.items())
464462

465463

466464
class _CleanResult(NamedTuple):

tests/unit/test_collector.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import uuid
77
from pathlib import Path
88
from textwrap import dedent
9-
from typing import List, Optional, Tuple
9+
from typing import Dict, List, Optional, Tuple
1010
from unittest import mock
1111

1212
import pytest
@@ -538,7 +538,7 @@ def test_parse_links_json() -> None:
538538
metadata_link.url
539539
== "https://example.com/files/holygrail-1.0-py3-none-any.whl.metadata"
540540
)
541-
assert metadata_link.link_hash == LinkHash("sha512", "aabdd41")
541+
assert metadata_link._hashes == {"sha512": "aabdd41"}
542542

543543

544544
@pytest.mark.parametrize(
@@ -575,41 +575,41 @@ def test_parse_links__yanked_reason(anchor_html: str, expected: Optional[str]) -
575575

576576

577577
@pytest.mark.parametrize(
578-
"anchor_html, expected, link_hash",
578+
"anchor_html, expected, hashes",
579579
[
580580
# Test not present.
581581
(
582582
'<a href="/pkg1-1.0.tar.gz"></a>',
583583
None,
584-
None,
584+
{},
585585
),
586586
# Test with value "true".
587587
(
588588
'<a href="/pkg1-1.0.tar.gz" data-dist-info-metadata="true"></a>',
589589
"true",
590-
None,
590+
{},
591591
),
592592
# Test with a provided hash value.
593593
(
594594
'<a href="/pkg1-1.0.tar.gz" data-dist-info-metadata="sha256=aa113592bbe"></a>', # noqa: E501
595595
"sha256=aa113592bbe",
596-
None,
596+
{},
597597
),
598598
# Test with a provided hash value for both the requirement as well as metadata.
599599
(
600600
'<a href="/pkg1-1.0.tar.gz#sha512=abc132409cb" data-dist-info-metadata="sha256=aa113592bbe"></a>', # noqa: E501
601601
"sha256=aa113592bbe",
602-
LinkHash("sha512", "abc132409cb"),
602+
{"sha512": "abc132409cb"},
603603
),
604604
],
605605
)
606606
def test_parse_links__dist_info_metadata(
607607
anchor_html: str,
608608
expected: Optional[str],
609-
link_hash: Optional[LinkHash],
609+
hashes: Dict[str, str],
610610
) -> None:
611611
link = _test_parse_links_data_attribute(anchor_html, "dist_info_metadata", expected)
612-
assert link.link_hash == link_hash
612+
assert link._hashes == hashes
613613

614614

615615
def test_parse_links_caches_same_page_by_url() -> None:

0 commit comments

Comments
 (0)