Skip to content

Commit 2e70ec0

Browse files
committed
Create the candidate lazily to avoid download
1 parent 68a86c5 commit 2e70ec0

File tree

3 files changed

+74
-30
lines changed

3 files changed

+74
-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

0 commit comments

Comments
 (0)