Skip to content

Commit 0c28452

Browse files
authored
Filter available distributions using hash declarations from constraints files (#10962)
1 parent b73b128 commit 0c28452

File tree

6 files changed

+57
-15
lines changed

6 files changed

+57
-15
lines changed

news/9243.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Filter available distributions using hash declarations from constraints files.

src/pip/_internal/req/req_install.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
hide_url,
5353
redact_auth_from_url,
5454
)
55-
from pip._internal.utils.packaging import safe_extra
55+
from pip._internal.utils.packaging import is_pinned, safe_extra
5656
from pip._internal.utils.subprocess import runner_with_spinner_message
5757
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
5858
from pip._internal.utils.virtualenv import running_under_virtualenv
@@ -238,8 +238,7 @@ def is_pinned(self) -> bool:
238238
239239
For example, some-package==1.2 is pinned; some-package>1.2 is not.
240240
"""
241-
specifiers = self.specifier
242-
return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
241+
return is_pinned(self.specifier)
243242

244243
def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
245244
if not extras_requested:

src/pip/_internal/resolution/resolvelib/factory.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from pip._internal.resolution.base import InstallRequirementProvider
4646
from pip._internal.utils.compatibility_tags import get_supported
4747
from pip._internal.utils.hashes import Hashes
48-
from pip._internal.utils.packaging import get_requirement
48+
from pip._internal.utils.packaging import get_requirement, is_pinned
4949
from pip._internal.utils.virtualenv import running_under_virtualenv
5050

5151
from .base import Candidate, CandidateVersion, Constraint, Requirement
@@ -303,19 +303,13 @@ def iter_index_candidate_infos() -> Iterator[IndexCandidateInfo]:
303303
# solely satisfied by a yanked release.
304304
all_yanked = all(ican.link.is_yanked for ican in icans)
305305

306-
def is_pinned(specifier: SpecifierSet) -> bool:
307-
for sp in specifier:
308-
if sp.operator == "===":
309-
return True
310-
if sp.operator != "==":
311-
continue
312-
if sp.version.endswith(".*"):
313-
continue
314-
return True
315-
return False
316-
317306
pinned = is_pinned(specifier)
318307

308+
if not template.is_pinned:
309+
assert template.req, "Candidates found on index must be PEP 508"
310+
template.req.specifier = specifier
311+
template.hash_options = hashes.allowed
312+
319313
# PackageFinder returns earlier versions first, so we reverse.
320314
for ican in reversed(icans):
321315
if not (all_yanked and pinned) and ican.link.is_yanked:

src/pip/_internal/utils/hashes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ def __and__(self, other: "Hashes") -> "Hashes":
6363
def digest_count(self) -> int:
6464
return sum(len(digests) for digests in self._allowed.values())
6565

66+
@property
67+
def allowed(self) -> Dict[str, List[str]]:
68+
return self._allowed
69+
6670
def is_hash_allowed(self, hash_name: str, hex_digest: str) -> bool:
6771
"""Return whether the given hex digest is allowed."""
6872
return hex_digest in self._allowed.get(hash_name, [])

src/pip/_internal/utils/packaging.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from pip._vendor.packaging import specifiers, version
77
from pip._vendor.packaging.requirements import Requirement
8+
from pip._vendor.packaging.specifiers import SpecifierSet
89

910
NormalizedExtra = NewType("NormalizedExtra", str)
1011

@@ -55,3 +56,15 @@ def safe_extra(extra: str) -> NormalizedExtra:
5556
the same to either ``canonicalize_name`` or ``_egg_link_name``.
5657
"""
5758
return cast(NormalizedExtra, re.sub("[^A-Za-z0-9.-]+", "_", extra).lower())
59+
60+
61+
def is_pinned(specifier: SpecifierSet) -> bool:
62+
for sp in specifier:
63+
if sp.operator == "===":
64+
return True
65+
if sp.operator != "==":
66+
continue
67+
if sp.version.endswith(".*"):
68+
continue
69+
return True
70+
return False

tests/functional/test_new_resolver_hashes.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,34 @@ def test_new_resolver_hash_with_extras(script: PipTestEnvironment) -> None:
373373
child="0.1.0",
374374
extra="0.1.0",
375375
)
376+
377+
378+
def test_new_resolver_hash_with_pin(script: PipTestEnvironment) -> None:
379+
find_links = _create_find_links(script)
380+
381+
requirements_txt = script.scratch_path / "requirements.txt"
382+
requirements_txt.write_text("base")
383+
384+
constraints_txt = script.scratch_path / "constraints.txt"
385+
constraints_txt.write_text(
386+
"""
387+
base==0.1.0 --hash=sha256:{sdist_hash} --hash=sha256:{wheel_hash}
388+
""".format(
389+
sdist_hash=find_links.sdist_hash,
390+
wheel_hash=find_links.wheel_hash,
391+
)
392+
)
393+
394+
script.pip(
395+
"install",
396+
"--no-cache-dir",
397+
"--no-index",
398+
"--find-links",
399+
find_links.index_html,
400+
"--requirement",
401+
requirements_txt,
402+
"--constraint",
403+
constraints_txt,
404+
)
405+
406+
script.assert_installed(base="0.1.0")

0 commit comments

Comments
 (0)