Skip to content

Commit 55d2969

Browse files
authored
Merge pull request #9522 from uranusjr/no-dup-fetch
Make candidate download even lazier
2 parents fa0ee31 + 7d43ec5 commit 55d2969

File tree

4 files changed

+99
-30
lines changed

4 files changed

+99
-30
lines changed

news/9516.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
New resolver: Download and prepare a distribution only at the last possible
2+
moment to avoid unnecessary network access when the same version is already
3+
installed locally.

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import functools
12
import logging
23

34
from pip._vendor.packaging.utils import canonicalize_name
@@ -65,6 +66,7 @@
6566

6667
from .base import Candidate, Requirement
6768
from .candidates import BaseCandidate
69+
from .found_candidates import IndexCandidateInfo
6870

6971
C = TypeVar("C")
7072
Cache = Dict[Link, C]
@@ -213,8 +215,8 @@ def _iter_found_candidates(
213215
template=template,
214216
)
215217

216-
def iter_index_candidates():
217-
# type: () -> Iterator[Candidate]
218+
def iter_index_candidate_infos():
219+
# type: () -> Iterator[IndexCandidateInfo]
218220
result = self._finder.find_best_candidate(
219221
project_name=name,
220222
specifier=specifier,
@@ -228,26 +230,21 @@ def iter_index_candidates():
228230
all_yanked = all(ican.link.is_yanked for ican in icans)
229231

230232
# PackageFinder returns earlier versions first, so we reverse.
231-
versions_found = set() # type: Set[_BaseVersion]
232233
for ican in reversed(icans):
233234
if not all_yanked and ican.link.is_yanked:
234235
continue
235-
if ican.version in versions_found:
236-
continue
237-
candidate = self._make_candidate_from_link(
236+
func = functools.partial(
237+
self._make_candidate_from_link,
238238
link=ican.link,
239239
extras=extras,
240240
template=template,
241241
name=name,
242242
version=ican.version,
243243
)
244-
if candidate is None:
245-
continue
246-
yield candidate
247-
versions_found.add(ican.version)
244+
yield ican.version, func
248245

249246
return FoundCandidates(
250-
iter_index_candidates,
247+
iter_index_candidate_infos,
251248
installed_candidate,
252249
prefers_installed,
253250
)

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

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,62 @@
99
"""
1010

1111
import functools
12-
import itertools
1312

1413
from pip._vendor.six.moves import collections_abc # type: ignore
1514

1615
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
1716

1817
if MYPY_CHECK_RUNNING:
19-
from typing import Callable, Iterator, Optional
18+
from typing import Callable, Iterator, Optional, Set, Tuple
19+
20+
from pip._vendor.packaging.version import _BaseVersion
2021

2122
from .base import Candidate
2223

24+
IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]]
25+
26+
27+
def _iter_built(infos):
28+
# type: (Iterator[IndexCandidateInfo]) -> Iterator[Candidate]
29+
"""Iterator for ``FoundCandidates``.
30+
31+
This iterator is used the package is not already installed. Candidates
32+
from index come later in their normal ordering.
33+
"""
34+
versions_found = set() # type: Set[_BaseVersion]
35+
for version, func in infos:
36+
if version in versions_found:
37+
continue
38+
candidate = func()
39+
if candidate is None:
40+
continue
41+
yield candidate
42+
versions_found.add(version)
43+
44+
45+
def _iter_built_with_prepended(installed, infos):
46+
# type: (Candidate, Iterator[IndexCandidateInfo]) -> Iterator[Candidate]
47+
"""Iterator for ``FoundCandidates``.
48+
49+
This iterator is used when the resolver prefers the already-installed
50+
candidate and NOT to upgrade. The installed candidate is therefore
51+
always yielded first, and candidates from index come later in their
52+
normal ordering, except skipped when the version is already installed.
53+
"""
54+
yield installed
55+
versions_found = {installed.version} # type: Set[_BaseVersion]
56+
for version, func in infos:
57+
if version in versions_found:
58+
continue
59+
candidate = func()
60+
if candidate is None:
61+
continue
62+
yield candidate
63+
versions_found.add(version)
64+
2365

24-
def _insert_installed(installed, others):
25-
# type: (Candidate, Iterator[Candidate]) -> Iterator[Candidate]
66+
def _iter_built_with_inserted(installed, infos):
67+
# type: (Candidate, Iterator[IndexCandidateInfo]) -> Iterator[Candidate]
2668
"""Iterator for ``FoundCandidates``.
2769
2870
This iterator is used when the resolver prefers to upgrade an
@@ -33,16 +75,22 @@ def _insert_installed(installed, others):
3375
the installed candidate exactly once before we start yielding older or
3476
equivalent candidates, or after all other candidates if they are all newer.
3577
"""
36-
installed_yielded = False
37-
for candidate in others:
78+
versions_found = set() # type: Set[_BaseVersion]
79+
for version, func in infos:
80+
if version in versions_found:
81+
continue
3882
# If the installed candidate is better, yield it first.
39-
if not installed_yielded and installed.version >= candidate.version:
83+
if installed.version >= version:
4084
yield installed
41-
installed_yielded = True
85+
versions_found.add(installed.version)
86+
candidate = func()
87+
if candidate is None:
88+
continue
4289
yield candidate
90+
versions_found.add(version)
4391

4492
# If the installed candidate is older than all other candidates.
45-
if not installed_yielded:
93+
if installed.version not in versions_found:
4694
yield installed
4795

4896

@@ -56,11 +104,11 @@ class FoundCandidates(collections_abc.Sequence):
56104
"""
57105
def __init__(
58106
self,
59-
get_others, # type: Callable[[], Iterator[Candidate]]
107+
get_infos, # type: Callable[[], Iterator[IndexCandidateInfo]]
60108
installed, # type: Optional[Candidate]
61109
prefers_installed, # type: bool
62110
):
63-
self._get_others = get_others
111+
self._get_infos = get_infos
64112
self._installed = installed
65113
self._prefers_installed = prefers_installed
66114

@@ -73,16 +121,12 @@ def __getitem__(self, index):
73121

74122
def __iter__(self):
75123
# type: () -> Iterator[Candidate]
124+
infos = self._get_infos()
76125
if not self._installed:
77-
return self._get_others()
78-
others = (
79-
candidate
80-
for candidate in self._get_others()
81-
if candidate.version != self._installed.version
82-
)
126+
return _iter_built(infos)
83127
if self._prefers_installed:
84-
return itertools.chain([self._installed], others)
85-
return _insert_installed(self._installed, others)
128+
return _iter_built_with_prepended(self._installed, infos)
129+
return _iter_built_with_inserted(self._installed, infos)
86130

87131
def __len__(self):
88132
# type: () -> int

tests/functional/test_new_resolver.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,3 +1253,28 @@ def test_new_resolver_lazy_fetch_candidates(script, upgrade):
12531253
# But should reach there in the best route possible, without trying
12541254
# candidates it does not need to.
12551255
assert "myuberpkg-2" not in result.stdout, str(result)
1256+
1257+
1258+
def test_new_resolver_no_fetch_no_satisfying(script):
1259+
create_basic_wheel_for_package(script, "myuberpkg", "1")
1260+
1261+
# Install the package. This should emit a "Processing" message for
1262+
# fetching the distribution from the --find-links page.
1263+
result = script.pip(
1264+
"install",
1265+
"--no-cache-dir", "--no-index",
1266+
"--find-links", script.scratch_path,
1267+
"myuberpkg",
1268+
)
1269+
assert "Processing " in result.stdout, str(result)
1270+
1271+
# Try to upgrade the package. This should NOT emit the "Processing"
1272+
# message because the currently installed version is latest.
1273+
result = script.pip(
1274+
"install",
1275+
"--no-cache-dir", "--no-index",
1276+
"--find-links", script.scratch_path,
1277+
"--upgrade",
1278+
"myuberpkg",
1279+
)
1280+
assert "Processing " not in result.stdout, str(result)

0 commit comments

Comments
 (0)