Skip to content

Commit 97f6390

Browse files
authored
Merge pull request #8014 from uranusjr/always-return-installed-candidate
2 parents 9a91f45 + 9c97b28 commit 97f6390

File tree

3 files changed

+92
-37
lines changed

3 files changed

+92
-37
lines changed

Diff for: src/pip/_internal/resolution/resolvelib/factory.py

+33-24
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@
2121
)
2222

2323
if MYPY_CHECK_RUNNING:
24-
from typing import Dict, Optional, Set, Tuple, TypeVar
24+
from typing import Dict, Iterator, Optional, Set, Tuple, TypeVar
2525

2626
from pip._vendor.packaging.specifiers import SpecifierSet
2727
from pip._vendor.packaging.version import _BaseVersion
2828
from pip._vendor.pkg_resources import Distribution
2929
from pip._vendor.resolvelib import ResolutionImpossible
3030

3131
from pip._internal.index.package_finder import PackageFinder
32-
from pip._internal.models.candidate import InstallationCandidate
3332
from pip._internal.models.link import Link
3433
from pip._internal.operations.prepare import RequirementPreparer
3534
from pip._internal.req.req_install import InstallRequirement
@@ -66,7 +65,7 @@ def __init__(
6665

6766
if not ignore_installed:
6867
self._installed_dists = {
69-
dist.project_name: dist
68+
canonicalize_name(dist.project_name): dist
7069
for dist in get_installed_distributions()
7170
}
7271
else:
@@ -93,6 +92,8 @@ def _make_candidate_from_link(
9392
version=None, # type: Optional[_BaseVersion]
9493
):
9594
# type: (...) -> Candidate
95+
# TODO: Check already installed candidate, and use it if the link and
96+
# editable flag match.
9697
if parent.editable:
9798
if link not in self._editable_candidate_cache:
9899
self._editable_candidate_cache[link] = EditableCandidate(
@@ -109,32 +110,40 @@ def _make_candidate_from_link(
109110
return ExtrasCandidate(base, extras)
110111
return base
111112

112-
def make_candidate_from_ican(
113-
self,
114-
ican, # type: InstallationCandidate
115-
extras, # type: Set[str]
116-
parent, # type: InstallRequirement
117-
):
118-
# type: (...) -> Candidate
119-
dist = self._installed_dists.get(ican.name)
120-
should_use_installed_dist = (
121-
not self._force_reinstall and
122-
dist is not None and
123-
dist.parsed_version == ican.version
113+
def iter_found_candidates(self, ireq, extras):
114+
# type: (InstallRequirement, Set[str]) -> Iterator[Candidate]
115+
name = canonicalize_name(ireq.req.name)
116+
if not self._force_reinstall:
117+
installed_dist = self._installed_dists.get(name)
118+
else:
119+
installed_dist = None
120+
121+
found = self.finder.find_best_candidate(
122+
project_name=ireq.req.name,
123+
specifier=ireq.req.specifier,
124+
hashes=ireq.hashes(trust_internet=False),
124125
)
125-
if not should_use_installed_dist:
126-
return self._make_candidate_from_link(
126+
for ican in found.iter_applicable():
127+
if (installed_dist is not None and
128+
installed_dist.parsed_version == ican.version):
129+
continue
130+
yield self._make_candidate_from_link(
127131
link=ican.link,
128132
extras=extras,
129-
parent=parent,
130-
name=canonicalize_name(ican.name),
133+
parent=ireq,
134+
name=name,
131135
version=ican.version,
132136
)
133-
return self._make_candidate_from_dist(
134-
dist=dist,
135-
extras=extras,
136-
parent=parent,
137-
)
137+
138+
# Return installed distribution if it matches the specifier. This is
139+
# done last so the resolver will prefer it over downloading links.
140+
if (installed_dist is not None and
141+
installed_dist.parsed_version in ireq.req.specifier):
142+
yield self._make_candidate_from_dist(
143+
dist=installed_dist,
144+
extras=extras,
145+
parent=ireq,
146+
)
138147

139148
def make_requirement_from_install_req(self, ireq):
140149
# type: (InstallRequirement) -> Requirement

Diff for: src/pip/_internal/resolution/resolvelib/requirements.py

+2-13
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,8 @@ def name(self):
6969

7070
def find_matches(self):
7171
# type: () -> Sequence[Candidate]
72-
found = self._factory.finder.find_best_candidate(
73-
project_name=self._ireq.req.name,
74-
specifier=self._ireq.req.specifier,
75-
hashes=self._ireq.hashes(trust_internet=False),
76-
)
77-
return [
78-
self._factory.make_candidate_from_ican(
79-
ican=ican,
80-
extras=self.extras,
81-
parent=self._ireq,
82-
)
83-
for ican in found.iter_applicable()
84-
]
72+
it = self._factory.iter_found_candidates(self._ireq, self.extras)
73+
return list(it)
8574

8675
def is_satisfied_by(self, candidate):
8776
# type: (Candidate) -> bool

Diff for: tests/functional/test_new_resolver.py

+57
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,63 @@ def test_new_resolver_picks_latest_version(script):
8989
assert_installed(script, simple="0.2.0")
9090

9191

92+
def test_new_resolver_picks_installed_version(script):
93+
create_basic_wheel_for_package(
94+
script,
95+
"simple",
96+
"0.1.0",
97+
)
98+
create_basic_wheel_for_package(
99+
script,
100+
"simple",
101+
"0.2.0",
102+
)
103+
script.pip(
104+
"install", "--unstable-feature=resolver",
105+
"--no-cache-dir", "--no-index",
106+
"--find-links", script.scratch_path,
107+
"simple==0.1.0"
108+
)
109+
assert_installed(script, simple="0.1.0")
110+
111+
result = script.pip(
112+
"install", "--unstable-feature=resolver",
113+
"--no-cache-dir", "--no-index",
114+
"--find-links", script.scratch_path,
115+
"simple"
116+
)
117+
assert "Collecting" not in result.stdout, "Should not fetch new version"
118+
assert_installed(script, simple="0.1.0")
119+
120+
121+
def test_new_resolver_picks_installed_version_if_no_match_found(script):
122+
create_basic_wheel_for_package(
123+
script,
124+
"simple",
125+
"0.1.0",
126+
)
127+
create_basic_wheel_for_package(
128+
script,
129+
"simple",
130+
"0.2.0",
131+
)
132+
script.pip(
133+
"install", "--unstable-feature=resolver",
134+
"--no-cache-dir", "--no-index",
135+
"--find-links", script.scratch_path,
136+
"simple==0.1.0"
137+
)
138+
assert_installed(script, simple="0.1.0")
139+
140+
result = script.pip(
141+
"install", "--unstable-feature=resolver",
142+
"--no-cache-dir", "--no-index",
143+
"simple"
144+
)
145+
assert "Collecting" not in result.stdout, "Should not fetch new version"
146+
assert_installed(script, simple="0.1.0")
147+
148+
92149
def test_new_resolver_installs_dependencies(script):
93150
create_basic_wheel_for_package(
94151
script,

0 commit comments

Comments
 (0)