-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement "lazy sequence" to avoid Internet
find_matches() is modified to return a special type that implements the sequence protocol (instead of a plain list). This special sequence type tries to use the installed candidate as the first element if possible, and only access indexes when the installed candidate is considered unsatisfactory.
- Loading branch information
Showing
4 changed files
with
180 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
src/pip/_internal/resolution/resolvelib/found_candidates.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
from pip._vendor.six.moves import collections_abc | ||
|
||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING | ||
|
||
if MYPY_CHECK_RUNNING: | ||
from typing import Callable, Iterator, Optional, Set | ||
|
||
from pip._vendor.packaging.version import _BaseVersion | ||
|
||
from .base import Candidate | ||
|
||
|
||
class _InstalledFirstCandidatesIterator(collections_abc.Iterator): | ||
"""Iterator for ``FoundCandidates``. | ||
This iterator is used when the resolver prefers to keep the version of an | ||
already-installed package. The already-installed candidate is always | ||
returned first. Candidates from index are accessed only when the resolver | ||
wants them, and the already-installed version is excluded from them. | ||
""" | ||
def __init__( | ||
self, | ||
get_others, # type: Callable[[], Iterator[Candidate]] | ||
installed, # type: Optional[Candidate] | ||
): | ||
self._installed = installed | ||
self._get_others = get_others | ||
self._others = None # type: Optional[Iterator[Candidate]] | ||
self._returned = set() # type: Set[_BaseVersion] | ||
|
||
def __next__(self): | ||
# type: () -> Candidate | ||
if self._installed and self._installed.version not in self._returned: | ||
self._returned.add(self._installed.version) | ||
return self._installed | ||
if self._others is None: | ||
self._others = self._get_others() | ||
cand = next(self._others) | ||
while cand.version in self._returned: | ||
cand = next(self._others) | ||
self._returned.add(cand.version) | ||
return cand | ||
|
||
next = __next__ # XXX: Python 2. | ||
|
||
|
||
class _InstalledReplacesCandidatesIterator(collections_abc.Iterator): | ||
"""Iterator for ``FoundCandidates``. | ||
This iterator is used when the resolver prefers to upgrade an | ||
already-installed package. Candidates from index are returned in their | ||
normal ordering, except replaced when the version is already installed. | ||
""" | ||
def __init__( | ||
self, | ||
get_others, # type: Callable[[], Iterator[Candidate]] | ||
installed, # type: Optional[Candidate] | ||
): | ||
self._installed = installed | ||
self._get_others = get_others | ||
self._others = None # type: Optional[Iterator[Candidate]] | ||
self._returned = set() # type: Set[_BaseVersion] | ||
|
||
def __next__(self): | ||
# type: () -> Candidate | ||
if self._others is None: | ||
self._others = self._get_others() | ||
cand = next(self._others) | ||
while cand.version in self._returned: | ||
cand = next(self._others) | ||
if self._installed and cand.version == self._installed.version: | ||
cand = self._installed | ||
self._returned.add(cand.version) | ||
return cand | ||
|
||
next = __next__ # XXX: Python 2. | ||
|
||
|
||
class FoundCandidates(collections_abc.Sequence): | ||
"""A lazy sequence to provide candidates to the resolver. | ||
The intended usage is to return this from `find_matches()` so the resolver | ||
can iterate through the sequence multiple times, but only access the index | ||
page when remote packages are actually needed. This improve performances | ||
when suitable candidates are already installed on disk. | ||
""" | ||
def __init__( | ||
self, | ||
get_others, # type: Callable[[], Iterator[Candidate]] | ||
installed, # type: Optional[Candidate] | ||
prefers_installed, # type: bool | ||
): | ||
self._get_others = get_others | ||
self._installed = installed | ||
self._prefers_installed = prefers_installed | ||
|
||
def __getitem__(self, index): | ||
# type: (int) -> Candidate | ||
for i, value in enumerate(self): | ||
if index == i: | ||
return value | ||
raise IndexError(index) | ||
|
||
def __iter__(self): | ||
# type: () -> Iterator[Candidate] | ||
if self._prefers_installed: | ||
klass = _InstalledFirstCandidatesIterator | ||
else: | ||
klass = _InstalledReplacesCandidatesIterator | ||
return klass(self._get_others, self._installed) | ||
|
||
def __len__(self): | ||
# type: () -> int | ||
return sum(1 for _ in self) | ||
|
||
def __bool__(self): | ||
# type: () -> bool | ||
if self._prefers_installed and self._installed: | ||
return True | ||
return any(1 for _ in self) | ||
|
||
__nonzero__ = __bool__ # XXX: Python 2. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters