python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
pip/
_internal/
resolution/
resolvelib/
provider.py
       1  import collections
       2  import math
       3  from typing import (
       4      TYPE_CHECKING,
       5      Dict,
       6      Iterable,
       7      Iterator,
       8      Mapping,
       9      Sequence,
      10      TypeVar,
      11      Union,
      12  )
      13  
      14  from pip._vendor.resolvelib.providers import AbstractProvider
      15  
      16  from .base import Candidate, Constraint, Requirement
      17  from .candidates import REQUIRES_PYTHON_IDENTIFIER
      18  from .factory import Factory
      19  
      20  if TYPE_CHECKING:
      21      from pip._vendor.resolvelib.providers import Preference
      22      from pip._vendor.resolvelib.resolvers import RequirementInformation
      23  
      24      PreferenceInformation = RequirementInformation[Requirement, Candidate]
      25  
      26      _ProviderBase = AbstractProvider[Requirement, Candidate, str]
      27  else:
      28      _ProviderBase = AbstractProvider
      29  
      30  # Notes on the relationship between the provider, the factory, and the
      31  # candidate and requirement classes.
      32  #
      33  # The provider is a direct implementation of the resolvelib class. Its role
      34  # is to deliver the API that resolvelib expects.
      35  #
      36  # Rather than work with completely abstract "requirement" and "candidate"
      37  # concepts as resolvelib does, pip has concrete classes implementing these two
      38  # ideas. The API of Requirement and Candidate objects are defined in the base
      39  # classes, but essentially map fairly directly to the equivalent provider
      40  # methods. In particular, `find_matches` and `is_satisfied_by` are
      41  # requirement methods, and `get_dependencies` is a candidate method.
      42  #
      43  # The factory is the interface to pip's internal mechanisms. It is stateless,
      44  # and is created by the resolver and held as a property of the provider. It is
      45  # responsible for creating Requirement and Candidate objects, and provides
      46  # services to those objects (access to pip's finder and preparer).
      47  
      48  
      49  D = TypeVar("D")
      50  V = TypeVar("V")
      51  
      52  
      53  def _get_with_identifier(
      54      mapping: Mapping[str, V],
      55      identifier: str,
      56      default: D,
      57  ) -> Union[D, V]:
      58      """Get item from a package name lookup mapping with a resolver identifier.
      59  
      60      This extra logic is needed when the target mapping is keyed by package
      61      name, which cannot be directly looked up with an identifier (which may
      62      contain requested extras). Additional logic is added to also look up a value
      63      by "cleaning up" the extras from the identifier.
      64      """
      65      if identifier in mapping:
      66          return mapping[identifier]
      67      # HACK: Theoretically we should check whether this identifier is a valid
      68      # "NAME[EXTRAS]" format, and parse out the name part with packaging or
      69      # some regular expression. But since pip's resolver only spits out three
      70      # kinds of identifiers: normalized PEP 503 names, normalized names plus
      71      # extras, and Requires-Python, we can cheat a bit here.
      72      name, open_bracket, _ = identifier.partition("[")
      73      if open_bracket and name in mapping:
      74          return mapping[name]
      75      return default
      76  
      77  
      78  class ESC[4;38;5;81mPipProvider(ESC[4;38;5;149m_ProviderBase):
      79      """Pip's provider implementation for resolvelib.
      80  
      81      :params constraints: A mapping of constraints specified by the user. Keys
      82          are canonicalized project names.
      83      :params ignore_dependencies: Whether the user specified ``--no-deps``.
      84      :params upgrade_strategy: The user-specified upgrade strategy.
      85      :params user_requested: A set of canonicalized package names that the user
      86          supplied for pip to install/upgrade.
      87      """
      88  
      89      def __init__(
      90          self,
      91          factory: Factory,
      92          constraints: Dict[str, Constraint],
      93          ignore_dependencies: bool,
      94          upgrade_strategy: str,
      95          user_requested: Dict[str, int],
      96      ) -> None:
      97          self._factory = factory
      98          self._constraints = constraints
      99          self._ignore_dependencies = ignore_dependencies
     100          self._upgrade_strategy = upgrade_strategy
     101          self._user_requested = user_requested
     102          self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf)
     103  
     104      def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
     105          return requirement_or_candidate.name
     106  
     107      def get_preference(
     108          self,
     109          identifier: str,
     110          resolutions: Mapping[str, Candidate],
     111          candidates: Mapping[str, Iterator[Candidate]],
     112          information: Mapping[str, Iterable["PreferenceInformation"]],
     113          backtrack_causes: Sequence["PreferenceInformation"],
     114      ) -> "Preference":
     115          """Produce a sort key for given requirement based on preference.
     116  
     117          The lower the return value is, the more preferred this group of
     118          arguments is.
     119  
     120          Currently pip considers the following in order:
     121  
     122          * Prefer if any of the known requirements is "direct", e.g. points to an
     123            explicit URL.
     124          * If equal, prefer if any requirement is "pinned", i.e. contains
     125            operator ``===`` or ``==``.
     126          * If equal, calculate an approximate "depth" and resolve requirements
     127            closer to the user-specified requirements first. If the depth cannot
     128            by determined (eg: due to no matching parents), it is considered
     129            infinite.
     130          * Order user-specified requirements by the order they are specified.
     131          * If equal, prefers "non-free" requirements, i.e. contains at least one
     132            operator, such as ``>=`` or ``<``.
     133          * If equal, order alphabetically for consistency (helps debuggability).
     134          """
     135          try:
     136              next(iter(information[identifier]))
     137          except StopIteration:
     138              # There is no information for this identifier, so there's no known
     139              # candidates.
     140              has_information = False
     141          else:
     142              has_information = True
     143  
     144          if has_information:
     145              lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
     146              candidate, ireqs = zip(*lookups)
     147          else:
     148              candidate, ireqs = None, ()
     149  
     150          operators = [
     151              specifier.operator
     152              for specifier_set in (ireq.specifier for ireq in ireqs if ireq)
     153              for specifier in specifier_set
     154          ]
     155  
     156          direct = candidate is not None
     157          pinned = any(op[:2] == "==" for op in operators)
     158          unfree = bool(operators)
     159  
     160          try:
     161              requested_order: Union[int, float] = self._user_requested[identifier]
     162          except KeyError:
     163              requested_order = math.inf
     164              if has_information:
     165                  parent_depths = (
     166                      self._known_depths[parent.name] if parent is not None else 0.0
     167                      for _, parent in information[identifier]
     168                  )
     169                  inferred_depth = min(d for d in parent_depths) + 1.0
     170              else:
     171                  inferred_depth = math.inf
     172          else:
     173              inferred_depth = 1.0
     174          self._known_depths[identifier] = inferred_depth
     175  
     176          requested_order = self._user_requested.get(identifier, math.inf)
     177  
     178          # Requires-Python has only one candidate and the check is basically
     179          # free, so we always do it first to avoid needless work if it fails.
     180          requires_python = identifier == REQUIRES_PYTHON_IDENTIFIER
     181  
     182          # Prefer the causes of backtracking on the assumption that the problem
     183          # resolving the dependency tree is related to the failures that caused
     184          # the backtracking
     185          backtrack_cause = self.is_backtrack_cause(identifier, backtrack_causes)
     186  
     187          return (
     188              not requires_python,
     189              not direct,
     190              not pinned,
     191              not backtrack_cause,
     192              inferred_depth,
     193              requested_order,
     194              not unfree,
     195              identifier,
     196          )
     197  
     198      def find_matches(
     199          self,
     200          identifier: str,
     201          requirements: Mapping[str, Iterator[Requirement]],
     202          incompatibilities: Mapping[str, Iterator[Candidate]],
     203      ) -> Iterable[Candidate]:
     204          def _eligible_for_upgrade(identifier: str) -> bool:
     205              """Are upgrades allowed for this project?
     206  
     207              This checks the upgrade strategy, and whether the project was one
     208              that the user specified in the command line, in order to decide
     209              whether we should upgrade if there's a newer version available.
     210  
     211              (Note that we don't need access to the `--upgrade` flag, because
     212              an upgrade strategy of "to-satisfy-only" means that `--upgrade`
     213              was not specified).
     214              """
     215              if self._upgrade_strategy == "eager":
     216                  return True
     217              elif self._upgrade_strategy == "only-if-needed":
     218                  user_order = _get_with_identifier(
     219                      self._user_requested,
     220                      identifier,
     221                      default=None,
     222                  )
     223                  return user_order is not None
     224              return False
     225  
     226          constraint = _get_with_identifier(
     227              self._constraints,
     228              identifier,
     229              default=Constraint.empty(),
     230          )
     231          return self._factory.find_candidates(
     232              identifier=identifier,
     233              requirements=requirements,
     234              constraint=constraint,
     235              prefers_installed=(not _eligible_for_upgrade(identifier)),
     236              incompatibilities=incompatibilities,
     237          )
     238  
     239      def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool:
     240          return requirement.is_satisfied_by(candidate)
     241  
     242      def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]:
     243          with_requires = not self._ignore_dependencies
     244          return [r for r in candidate.iter_dependencies(with_requires) if r is not None]
     245  
     246      @staticmethod
     247      def is_backtrack_cause(
     248          identifier: str, backtrack_causes: Sequence["PreferenceInformation"]
     249      ) -> bool:
     250          for backtrack_cause in backtrack_causes:
     251              if identifier == backtrack_cause.requirement.name:
     252                  return True
     253              if backtrack_cause.parent and identifier == backtrack_cause.parent.name:
     254                  return True
     255          return False