Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pick up hashes from contraints files and intersect #8839

Merged
merged 4 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/8792.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
New resolver: Pick up hash declarations in constraints files and use them to
filter available distributions.
3 changes: 3 additions & 0 deletions news/8839.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
New resolver: If a package appears multiple times in user specification with
different ``--hash`` options, only hashes that present in all specifications
should be allowed.
37 changes: 36 additions & 1 deletion src/pip/_internal/resolution/resolvelib/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.utils import canonicalize_name

from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.hashes import Hashes
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

if MYPY_CHECK_RUNNING:
Expand All @@ -8,7 +11,6 @@
from pip._vendor.packaging.version import _BaseVersion

from pip._internal.models.link import Link
from pip._internal.req.req_install import InstallRequirement

CandidateLookup = Tuple[
Optional["Candidate"],
Expand All @@ -24,6 +26,39 @@ def format_name(project, extras):
return "{}[{}]".format(project, ",".join(canonical_extras))


class Constraint(object):
def __init__(self, specifier, hashes):
# type: (SpecifierSet, Hashes) -> None
self.specifier = specifier
self.hashes = hashes

@classmethod
def empty(cls):
# type: () -> Constraint
return Constraint(SpecifierSet(), Hashes())

@classmethod
def from_ireq(cls, ireq):
# type: (InstallRequirement) -> Constraint
return Constraint(ireq.specifier, ireq.hashes(trust_internet=False))

def __nonzero__(self):
# type: () -> bool
return bool(self.specifier) or bool(self.hashes)

def __bool__(self):
# type: () -> bool
return self.__nonzero__()

def __and__(self, other):
# type: (InstallRequirement) -> Constraint
if not isinstance(other, InstallRequirement):
return NotImplemented
specifier = self.specifier & other.specifier
hashes = self.hashes & other.hashes(trust_internet=False)
return Constraint(specifier, hashes)


class Requirement(object):
@property
def name(self):
Expand Down
13 changes: 9 additions & 4 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.virtualenv import running_under_virtualenv

from .base import Constraint
from .candidates import (
AlreadyInstalledCandidate,
EditableCandidate,
Expand Down Expand Up @@ -152,6 +153,7 @@ def _iter_found_candidates(
self,
ireqs, # type: Sequence[InstallRequirement]
specifier, # type: SpecifierSet
hashes, # type: Hashes
):
# type: (...) -> Iterable[Candidate]
if not ireqs:
Expand All @@ -164,11 +166,10 @@ def _iter_found_candidates(
template = ireqs[0]
name = canonicalize_name(template.req.name)

hashes = Hashes()
extras = frozenset() # type: FrozenSet[str]
for ireq in ireqs:
specifier &= ireq.req.specifier
hashes |= ireq.hashes(trust_internet=False)
hashes &= ireq.hashes(trust_internet=False)
extras |= frozenset(ireq.extras)

# We use this to ensure that we only yield a single candidate for
Expand Down Expand Up @@ -218,7 +219,7 @@ def _iter_found_candidates(
return six.itervalues(candidates)

def find_candidates(self, requirements, constraint):
# type: (Sequence[Requirement], SpecifierSet) -> Iterable[Candidate]
# type: (Sequence[Requirement], Constraint) -> Iterable[Candidate]
explicit_candidates = set() # type: Set[Candidate]
ireqs = [] # type: List[InstallRequirement]
for req in requirements:
Expand All @@ -231,7 +232,11 @@ def find_candidates(self, requirements, constraint):
# If none of the requirements want an explicit candidate, we can ask
# the finder for candidates.
if not explicit_candidates:
return self._iter_found_candidates(ireqs, constraint)
return self._iter_found_candidates(
ireqs,
constraint.specifier,
constraint.hashes,
)

if constraint:
name = explicit_candidates.pop().name
Expand Down
7 changes: 4 additions & 3 deletions src/pip/_internal/resolution/resolvelib/provider.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.resolvelib.providers import AbstractProvider

from pip._internal.utils.typing import MYPY_CHECK_RUNNING

from .base import Constraint

if MYPY_CHECK_RUNNING:
from typing import (
Any,
Expand Down Expand Up @@ -41,7 +42,7 @@ class PipProvider(AbstractProvider):
def __init__(
self,
factory, # type: Factory
constraints, # type: Dict[str, SpecifierSet]
constraints, # type: Dict[str, Constraint]
ignore_dependencies, # type: bool
upgrade_strategy, # type: str
user_requested, # type: Set[str]
Expand Down Expand Up @@ -134,7 +135,7 @@ def find_matches(self, requirements):
if not requirements:
return []
constraint = self._constraints.get(
requirements[0].name, SpecifierSet(),
requirements[0].name, Constraint.empty(),
)
candidates = self._factory.find_candidates(requirements, constraint)
return reversed(self._sort_matches(candidates))
Expand Down
8 changes: 4 additions & 4 deletions src/pip/_internal/resolution/resolvelib/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
from pip._internal.utils.misc import dist_is_editable
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

from .base import Constraint
from .factory import Factory

if MYPY_CHECK_RUNNING:
from typing import Dict, List, Optional, Set, Tuple

from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.resolvelib.resolvers import Result
from pip._vendor.resolvelib.structs import Graph

Expand Down Expand Up @@ -71,7 +71,7 @@ def __init__(
def resolve(self, root_reqs, check_supported_wheels):
# type: (List[InstallRequirement], bool) -> RequirementSet

constraints = {} # type: Dict[str, SpecifierSet]
constraints = {} # type: Dict[str, Constraint]
user_requested = set() # type: Set[str]
requirements = []
for req in root_reqs:
Expand All @@ -84,9 +84,9 @@ def resolve(self, root_reqs, check_supported_wheels):
continue
name = canonicalize_name(req.name)
if name in constraints:
constraints[name] = constraints[name] & req.specifier
constraints[name] &= req
else:
constraints[name] = req.specifier
constraints[name] = Constraint.from_ireq(req)
else:
if req.user_supplied and req.name:
user_requested.add(canonicalize_name(req.name))
Expand Down
20 changes: 14 additions & 6 deletions src/pip/_internal/utils/hashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,24 @@ def __init__(self, hashes=None):
"""
self._allowed = {} if hashes is None else hashes

def __or__(self, other):
def __and__(self, other):
# type: (Hashes) -> Hashes
if not isinstance(other, Hashes):
return NotImplemented
new = self._allowed.copy()

# If either of the Hashes object is entirely empty (i.e. no hash
# specified at all), all hashes from the other object are allowed.
if not other:
return self
if not self:
return other

# Otherwise only hashes that present in both objects are allowed.
new = {}
for alg, values in iteritems(other._allowed):
try:
new[alg] += values
except KeyError:
new[alg] = values
if alg not in self._allowed:
continue
new[alg] = [v for v in values if v in self._allowed[alg]]
return Hashes(new)

@property
Expand Down
Loading