prolove.github.io
/
mypython
/Lib
/site-packages
/pip
/_internal
/resolution
/resolvelib
/found_candidates.py
"""Utilities to lazily create and visit candidates found. | |
Creating and visiting a candidate is a *very* costly operation. It involves | |
fetching, extracting, potentially building modules from source, and verifying | |
distribution metadata. It is therefore crucial for performance to keep | |
everything here lazy all the way down, so we only touch candidates that we | |
absolutely need, and not "download the world" when we only need one version of | |
something. | |
""" | |
import functools | |
from collections.abc import Sequence | |
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Set, Tuple | |
from pip._vendor.packaging.version import _BaseVersion | |
from .base import Candidate | |
IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]] | |
if TYPE_CHECKING: | |
SequenceCandidate = Sequence[Candidate] | |
else: | |
# For compatibility: Python before 3.9 does not support using [] on the | |
# Sequence class. | |
# | |
# >>> from collections.abc import Sequence | |
# >>> Sequence[str] | |
# Traceback (most recent call last): | |
# File "<stdin>", line 1, in <module> | |
# TypeError: 'ABCMeta' object is not subscriptable | |
# | |
# TODO: Remove this block after dropping Python 3.8 support. | |
SequenceCandidate = Sequence | |
def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]: | |
"""Iterator for ``FoundCandidates``. | |
This iterator is used when the package is not already installed. Candidates | |
from index come later in their normal ordering. | |
""" | |
versions_found: Set[_BaseVersion] = set() | |
for version, func in infos: | |
if version in versions_found: | |
continue | |
candidate = func() | |
if candidate is None: | |
continue | |
yield candidate | |
versions_found.add(version) | |
def _iter_built_with_prepended( | |
installed: Candidate, infos: Iterator[IndexCandidateInfo] | |
) -> Iterator[Candidate]: | |
"""Iterator for ``FoundCandidates``. | |
This iterator is used when the resolver prefers the already-installed | |
candidate and NOT to upgrade. The installed candidate is therefore | |
always yielded first, and candidates from index come later in their | |
normal ordering, except skipped when the version is already installed. | |
""" | |
yield installed | |
versions_found: Set[_BaseVersion] = {installed.version} | |
for version, func in infos: | |
if version in versions_found: | |
continue | |
candidate = func() | |
if candidate is None: | |
continue | |
yield candidate | |
versions_found.add(version) | |
def _iter_built_with_inserted( | |
installed: Candidate, infos: Iterator[IndexCandidateInfo] | |
) -> Iterator[Candidate]: | |
"""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. | |
The implementation iterates through and yields other candidates, inserting | |
the installed candidate exactly once before we start yielding older or | |
equivalent candidates, or after all other candidates if they are all newer. | |
""" | |
versions_found: Set[_BaseVersion] = set() | |
for version, func in infos: | |
if version in versions_found: | |
continue | |
# If the installed candidate is better, yield it first. | |
if installed.version >= version: | |
yield installed | |
versions_found.add(installed.version) | |
candidate = func() | |
if candidate is None: | |
continue | |
yield candidate | |
versions_found.add(version) | |
# If the installed candidate is older than all other candidates. | |
if installed.version not in versions_found: | |
yield installed | |
class FoundCandidates(SequenceCandidate): | |
"""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_infos: Callable[[], Iterator[IndexCandidateInfo]], | |
installed: Optional[Candidate], | |
prefers_installed: bool, | |
incompatible_ids: Set[int], | |
): | |
self._get_infos = get_infos | |
self._installed = installed | |
self._prefers_installed = prefers_installed | |
self._incompatible_ids = incompatible_ids | |
def __getitem__(self, index: Any) -> Any: | |
# Implemented to satisfy the ABC check. This is not needed by the | |
# resolver, and should not be used by the provider either (for | |
# performance reasons). | |
raise NotImplementedError("don't do this") | |
def __iter__(self) -> Iterator[Candidate]: | |
infos = self._get_infos() | |
if not self._installed: | |
iterator = _iter_built(infos) | |
elif self._prefers_installed: | |
iterator = _iter_built_with_prepended(self._installed, infos) | |
else: | |
iterator = _iter_built_with_inserted(self._installed, infos) | |
return (c for c in iterator if id(c) not in self._incompatible_ids) | |
def __len__(self) -> int: | |
# Implemented to satisfy the ABC check. This is not needed by the | |
# resolver, and should not be used by the provider either (for | |
# performance reasons). | |
raise NotImplementedError("don't do this") | |
def __bool__(self) -> bool: | |
if self._prefers_installed and self._installed: | |
return True | |
return any(self) | |