Skip to content

Commit 9cf1bed

Browse files
committed
Address review comments
1 parent 0db022f commit 9cf1bed

File tree

7 files changed

+57
-32
lines changed

7 files changed

+57
-32
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ def version(self):
4444
# type: () -> _BaseVersion
4545
raise NotImplementedError("Override in subclass")
4646

47+
@property
48+
def is_installed(self):
49+
# type: () -> bool
50+
raise NotImplementedError("Override in subclass")
51+
4752
def get_dependencies(self):
4853
# type: () -> Sequence[Requirement]
4954
raise NotImplementedError("Override in subclass")

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,10 @@ def make_install_req_from_dist(dist, parent):
101101
return ireq
102102

103103

104-
def is_already_installed(cand):
105-
# type: (Candidate) -> bool
106-
# For an ExtrasCandidate, we check the base
107-
if isinstance(cand, ExtrasCandidate):
108-
cand = cand.base
109-
return isinstance(cand, AlreadyInstalledCandidate)
110-
111-
112104
class _InstallRequirementBackedCandidate(Candidate):
105+
# These are not installed
106+
is_installed = False
107+
113108
def __init__(
114109
self,
115110
link, # type: Link
@@ -279,6 +274,8 @@ def _prepare_abstract_distribution(self):
279274

280275

281276
class AlreadyInstalledCandidate(Candidate):
277+
is_installed = True
278+
282279
def __init__(
283280
self,
284281
dist, # type: Distribution
@@ -400,6 +397,11 @@ def version(self):
400397
# type: () -> _BaseVersion
401398
return self.base.version
402399

400+
@property
401+
def is_installed(self):
402+
# type: () -> _BaseVersion
403+
return self.base.is_installed
404+
403405
def get_dependencies(self):
404406
# type: () -> Sequence[Requirement]
405407
factory = self.base._factory
@@ -436,6 +438,8 @@ def get_install_requirement(self):
436438

437439

438440
class RequiresPythonCandidate(Candidate):
441+
is_installed = False
442+
439443
def __init__(self, py_version_info):
440444
# type: (Optional[Tuple[int, ...]]) -> None
441445
if py_version_info is not None:

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ def _make_candidate_from_link(
114114
def iter_found_candidates(self, ireq, extras):
115115
# type: (InstallRequirement, Set[str]) -> Iterator[Candidate]
116116
name = canonicalize_name(ireq.req.name)
117+
118+
# We use this to ensure that we only yield a single candidate for
119+
# each version (the finder's preferred one for that version). The
120+
# requirement needs to return only one candidate per version, so we
121+
# implement that logic here so that requirements using this helper
122+
# don't all have to do the same thing later.
117123
seen_versions = set()
118124

119125
# Yield the installed version, if it matches, unless the user

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

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
55

6-
from .candidates import is_already_installed
7-
86
if MYPY_CHECK_RUNNING:
97
from typing import Any, Dict, Optional, Sequence, Set, Tuple, Union
108

@@ -40,16 +38,16 @@ def __init__(
4038
constraints, # type: Dict[str, SpecifierSet]
4139
ignore_dependencies, # type: bool
4240
upgrade_strategy, # type: str
43-
roots, # type: Set[str]
41+
user_requested, # type: Set[str]
4442
):
4543
# type: (...) -> None
4644
self._factory = factory
4745
self._constraints = constraints
4846
self._ignore_dependencies = ignore_dependencies
4947
self._upgrade_strategy = upgrade_strategy
50-
self.roots = roots
48+
self.user_requested = user_requested
5149

52-
def sort_matches(self, matches):
50+
def _sort_matches(self, matches):
5351
# type: (Sequence[Candidate]) -> Sequence[Candidate]
5452

5553
# The requirement is responsible for returning a sequence of potential
@@ -76,28 +74,36 @@ def sort_matches(self, matches):
7674

7775
def _eligible_for_upgrade(name):
7876
# type: (str) -> bool
77+
"""Are upgrades allowed for this project?
78+
79+
This checks the upgrade strategy, and whether the project was one
80+
that the user specified in the command line, in order to decide
81+
whether we should upgrade if there's a newer version available.
82+
83+
(Note that we don't need access to the `--upgrade` flag, because
84+
an upgrade strategy of "to-satisfy-only" means that `--upgrade`
85+
was not specified).
86+
"""
7987
if self._upgrade_strategy == "eager":
8088
return True
8189
elif self._upgrade_strategy == "only-if-needed":
82-
return (name in self.roots)
90+
return (name in self.user_requested)
8391
return False
8492

85-
def keep_installed(c):
86-
# type: (Candidate) -> int
87-
"""Give priority to an installed version?"""
88-
if not is_already_installed(c):
89-
return 0
90-
91-
if _eligible_for_upgrade(c.name):
92-
return 0
93+
def sort_key(c):
94+
# type: (Candidate) -> Tuple[int, _BaseVersion]
95+
"""Return a sort key for the matches.
9396
94-
return 1
97+
The highest priority should be given to installed candidates that
98+
are not eligible for upgrade. We use the integer value in the first
99+
part of the key to sort these before other candidates.
100+
"""
101+
if c.is_installed and not _eligible_for_upgrade(c.name):
102+
return (1, c.version)
95103

96-
def key(c):
97-
# type: (Candidate) -> Tuple[int, _BaseVersion]
98-
return (keep_installed(c), c.version)
104+
return (0, c.version)
99105

100-
return sorted(matches, key=key)
106+
return sorted(matches, key=sort_key)
101107

102108
def get_install_requirement(self, c):
103109
# type: (Candidate) -> Optional[InstallRequirement]
@@ -121,7 +127,7 @@ def find_matches(self, requirement):
121127
# type: (Requirement) -> Sequence[Candidate]
122128
constraint = self._constraints.get(requirement.name, SpecifierSet())
123129
matches = requirement.find_matches(constraint)
124-
return self.sort_matches(matches)
130+
return self._sort_matches(matches)
125131

126132
def is_satisfied_by(self, requirement, candidate):
127133
# type: (Requirement, Candidate) -> bool

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ def name(self):
7676

7777
def find_matches(self, constraint):
7878
# type: (SpecifierSet) -> Sequence[Candidate]
79+
80+
# We should only return one candidate per version, but
81+
# iter_found_candidates does that for us, so we don't need
82+
# to do anything special here.
7983
return [
8084
c
8185
for c in self._factory.iter_found_candidates(

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def resolve(self, root_reqs, check_supported_wheels):
6969
# type: (List[InstallRequirement], bool) -> RequirementSet
7070

7171
constraints = {} # type: Dict[str, SpecifierSet]
72-
roots = set()
72+
user_requested = set()
7373
requirements = []
7474
for req in root_reqs:
7575
if req.constraint:
@@ -82,7 +82,7 @@ def resolve(self, root_reqs, check_supported_wheels):
8282
constraints[name] = req.specifier
8383
else:
8484
if req.is_direct and req.name:
85-
roots.add(canonicalize_name(req.name))
85+
user_requested.add(canonicalize_name(req.name))
8686
requirements.append(
8787
self.factory.make_requirement_from_install_req(req)
8888
)
@@ -92,7 +92,7 @@ def resolve(self, root_reqs, check_supported_wheels):
9292
constraints=constraints,
9393
ignore_dependencies=self.ignore_dependencies,
9494
upgrade_strategy=self.upgrade_strategy,
95-
roots=roots,
95+
user_requested=user_requested,
9696
)
9797
reporter = BaseReporter()
9898
resolver = RLResolver(provider, reporter)

tests/unit/resolution_resolvelib/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,5 @@ def provider(factory):
6666
constraints={},
6767
ignore_dependencies=False,
6868
upgrade_strategy="to-satisfy-only",
69-
roots=set(),
69+
user_requested=set(),
7070
)

0 commit comments

Comments
 (0)