| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- """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.
- """
- from __future__ import annotations
- import logging
- from collections.abc import Iterator, Sequence
- from typing import Any, Callable, Optional
- from pip._vendor.packaging.version import _BaseVersion
- from pip._internal.exceptions import MetadataInvalid
- from .base import Candidate
- logger = logging.getLogger(__name__)
- IndexCandidateInfo = tuple[_BaseVersion, Callable[[], Optional[Candidate]]]
- 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
- try:
- candidate = func()
- except MetadataInvalid as e:
- logger.warning(
- "Ignoring version %s of %s since it has invalid metadata:\n"
- "%s\n"
- "Please use pip<24.1 if you need to use this version.",
- version,
- e.ireq.name,
- e,
- )
- # Mark version as found to avoid trying other candidates with the same
- # version, since they most likely have invalid metadata as well.
- versions_found.add(version)
- else:
- 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(Sequence[Candidate]):
- """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: Candidate | None,
- 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
- self._bool: bool | None = None
- 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._bool is not None:
- return self._bool
- if self._prefers_installed and self._installed:
- self._bool = True
- return True
- self._bool = any(self)
- return self._bool
|