Skip to content

Commit

Permalink
Filter available distributions using hash declarations from constrain…
Browse files Browse the repository at this point in the history
…ts files (#10962)
  • Loading branch information
q0w committed Mar 16, 2022
1 parent b73b128 commit 0c28452
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 15 deletions.
1 change: 1 addition & 0 deletions news/9243.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Filter available distributions using hash declarations from constraints files.
5 changes: 2 additions & 3 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
hide_url,
redact_auth_from_url,
)
from pip._internal.utils.packaging import safe_extra
from pip._internal.utils.packaging import is_pinned, safe_extra
from pip._internal.utils.subprocess import runner_with_spinner_message
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
from pip._internal.utils.virtualenv import running_under_virtualenv
Expand Down Expand Up @@ -238,8 +238,7 @@ def is_pinned(self) -> bool:
For example, some-package==1.2 is pinned; some-package>1.2 is not.
"""
specifiers = self.specifier
return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
return is_pinned(self.specifier)

def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
if not extras_requested:
Expand Down
18 changes: 6 additions & 12 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from pip._internal.resolution.base import InstallRequirementProvider
from pip._internal.utils.compatibility_tags import get_supported
from pip._internal.utils.hashes import Hashes
from pip._internal.utils.packaging import get_requirement
from pip._internal.utils.packaging import get_requirement, is_pinned
from pip._internal.utils.virtualenv import running_under_virtualenv

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

def is_pinned(specifier: SpecifierSet) -> bool:
for sp in specifier:
if sp.operator == "===":
return True
if sp.operator != "==":
continue
if sp.version.endswith(".*"):
continue
return True
return False

pinned = is_pinned(specifier)

if not template.is_pinned:
assert template.req, "Candidates found on index must be PEP 508"
template.req.specifier = specifier

This comment has been minimized.

Copy link
@atugushev

atugushev Apr 8, 2022

Contributor

This particular line breaks tests for the new resolver on pip-tools. I have no simple reproducer yet though, but I'm working on it.

See also jazzband/pip-tools#1539 (comment).

This comment has been minimized.

Copy link
@atugushev

atugushev Apr 8, 2022

Contributor

Any insights from @q0w on why the line has been added would be helpful and much appreciated.

This comment has been minimized.

Copy link
@q0w

q0w Apr 8, 2022

Author Contributor

because template without that always does not have neither hashes nor specifier and it will be failed after when checking if req.is_pinned in operations/prepare

This comment has been minimized.

Copy link
@pradyunsg

pradyunsg Apr 8, 2022

Member

If you could file an issue with a reproducer, that'd be great.

This comment has been minimized.

Copy link
@atugushev

atugushev Apr 8, 2022

Contributor

Deepcopying template before mutating fixes the issue on:


If you could file an issue with a reproducer, that'd be great.

@pradyunsg still can't find the reproducer. Tests on pip-tools are failing in random combinations and if you run a single failed test - it passes. Weird-weird-weird.

This comment has been minimized.

Copy link
@atugushev

atugushev Apr 8, 2022

Contributor

Are ireqs supposed to be mutable during iteration?

This comment has been minimized.

Copy link
@pradyunsg

pradyunsg Apr 8, 2022

Member

No, I'm pretty sure that we shouldn't be modfying template here -- since that's an actual requirement that we're supposed to be installing later.

This comment has been minimized.

Copy link
@atugushev

atugushev Apr 8, 2022

Contributor

Do you have any ideas on how to avoid mutating ireqs[0]? Deepcopy works but I wonder is there another way?

This comment has been minimized.

Copy link
@pradyunsg

pradyunsg Apr 8, 2022

Member

Not really -- could you file an issue for this, so that we can avoid having an extended discussion on a commit? :)

This comment has been minimized.

Copy link
@atugushev

atugushev Apr 8, 2022

Contributor

Submitted #11019

This comment has been minimized.

Copy link
@pradyunsg

pradyunsg Apr 8, 2022

Member

Awesome, thanks!

template.hash_options = hashes.allowed

# PackageFinder returns earlier versions first, so we reverse.
for ican in reversed(icans):
if not (all_yanked and pinned) and ican.link.is_yanked:
Expand Down
4 changes: 4 additions & 0 deletions src/pip/_internal/utils/hashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ def __and__(self, other: "Hashes") -> "Hashes":
def digest_count(self) -> int:
return sum(len(digests) for digests in self._allowed.values())

@property
def allowed(self) -> Dict[str, List[str]]:
return self._allowed

def is_hash_allowed(self, hash_name: str, hex_digest: str) -> bool:
"""Return whether the given hex digest is allowed."""
return hex_digest in self._allowed.get(hash_name, [])
Expand Down
13 changes: 13 additions & 0 deletions src/pip/_internal/utils/packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from pip._vendor.packaging import specifiers, version
from pip._vendor.packaging.requirements import Requirement
from pip._vendor.packaging.specifiers import SpecifierSet

NormalizedExtra = NewType("NormalizedExtra", str)

Expand Down Expand Up @@ -55,3 +56,15 @@ def safe_extra(extra: str) -> NormalizedExtra:
the same to either ``canonicalize_name`` or ``_egg_link_name``.
"""
return cast(NormalizedExtra, re.sub("[^A-Za-z0-9.-]+", "_", extra).lower())


def is_pinned(specifier: SpecifierSet) -> bool:
for sp in specifier:
if sp.operator == "===":
return True
if sp.operator != "==":
continue
if sp.version.endswith(".*"):
continue
return True
return False
31 changes: 31 additions & 0 deletions tests/functional/test_new_resolver_hashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,34 @@ def test_new_resolver_hash_with_extras(script: PipTestEnvironment) -> None:
child="0.1.0",
extra="0.1.0",
)


def test_new_resolver_hash_with_pin(script: PipTestEnvironment) -> None:
find_links = _create_find_links(script)

requirements_txt = script.scratch_path / "requirements.txt"
requirements_txt.write_text("base")

constraints_txt = script.scratch_path / "constraints.txt"
constraints_txt.write_text(
"""
base==0.1.0 --hash=sha256:{sdist_hash} --hash=sha256:{wheel_hash}
""".format(
sdist_hash=find_links.sdist_hash,
wheel_hash=find_links.wheel_hash,
)
)

script.pip(
"install",
"--no-cache-dir",
"--no-index",
"--find-links",
find_links.index_html,
"--requirement",
requirements_txt,
"--constraint",
constraints_txt,
)

script.assert_installed(base="0.1.0")

0 comments on commit 0c28452

Please sign in to comment.