python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
pip/
_internal/
resolution/
resolvelib/
candidates.py
       1  import logging
       2  import sys
       3  from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast
       4  
       5  from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
       6  from pip._vendor.packaging.version import Version
       7  
       8  from pip._internal.exceptions import (
       9      HashError,
      10      InstallationSubprocessError,
      11      MetadataInconsistent,
      12  )
      13  from pip._internal.metadata import BaseDistribution
      14  from pip._internal.models.link import Link, links_equivalent
      15  from pip._internal.models.wheel import Wheel
      16  from pip._internal.req.constructors import (
      17      install_req_from_editable,
      18      install_req_from_line,
      19  )
      20  from pip._internal.req.req_install import InstallRequirement
      21  from pip._internal.utils.direct_url_helpers import direct_url_from_link
      22  from pip._internal.utils.misc import normalize_version_info
      23  
      24  from .base import Candidate, CandidateVersion, Requirement, format_name
      25  
      26  if TYPE_CHECKING:
      27      from .factory import Factory
      28  
      29  logger = logging.getLogger(__name__)
      30  
      31  BaseCandidate = Union[
      32      "AlreadyInstalledCandidate",
      33      "EditableCandidate",
      34      "LinkCandidate",
      35  ]
      36  
      37  # Avoid conflicting with the PyPI package "Python".
      38  REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")
      39  
      40  
      41  def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
      42      """The runtime version of BaseCandidate."""
      43      base_candidate_classes = (
      44          AlreadyInstalledCandidate,
      45          EditableCandidate,
      46          LinkCandidate,
      47      )
      48      if isinstance(candidate, base_candidate_classes):
      49          return candidate
      50      return None
      51  
      52  
      53  def make_install_req_from_link(
      54      link: Link, template: InstallRequirement
      55  ) -> InstallRequirement:
      56      assert not template.editable, "template is editable"
      57      if template.req:
      58          line = str(template.req)
      59      else:
      60          line = link.url
      61      ireq = install_req_from_line(
      62          line,
      63          user_supplied=template.user_supplied,
      64          comes_from=template.comes_from,
      65          use_pep517=template.use_pep517,
      66          isolated=template.isolated,
      67          constraint=template.constraint,
      68          global_options=template.global_options,
      69          hash_options=template.hash_options,
      70          config_settings=template.config_settings,
      71      )
      72      ireq.original_link = template.original_link
      73      ireq.link = link
      74      ireq.extras = template.extras
      75      return ireq
      76  
      77  
      78  def make_install_req_from_editable(
      79      link: Link, template: InstallRequirement
      80  ) -> InstallRequirement:
      81      assert template.editable, "template not editable"
      82      ireq = install_req_from_editable(
      83          link.url,
      84          user_supplied=template.user_supplied,
      85          comes_from=template.comes_from,
      86          use_pep517=template.use_pep517,
      87          isolated=template.isolated,
      88          constraint=template.constraint,
      89          permit_editable_wheels=template.permit_editable_wheels,
      90          global_options=template.global_options,
      91          hash_options=template.hash_options,
      92          config_settings=template.config_settings,
      93      )
      94      ireq.extras = template.extras
      95      return ireq
      96  
      97  
      98  def _make_install_req_from_dist(
      99      dist: BaseDistribution, template: InstallRequirement
     100  ) -> InstallRequirement:
     101      if template.req:
     102          line = str(template.req)
     103      elif template.link:
     104          line = f"{dist.canonical_name} @ {template.link.url}"
     105      else:
     106          line = f"{dist.canonical_name}=={dist.version}"
     107      ireq = install_req_from_line(
     108          line,
     109          user_supplied=template.user_supplied,
     110          comes_from=template.comes_from,
     111          use_pep517=template.use_pep517,
     112          isolated=template.isolated,
     113          constraint=template.constraint,
     114          global_options=template.global_options,
     115          hash_options=template.hash_options,
     116          config_settings=template.config_settings,
     117      )
     118      ireq.satisfied_by = dist
     119      return ireq
     120  
     121  
     122  class ESC[4;38;5;81m_InstallRequirementBackedCandidate(ESC[4;38;5;149mCandidate):
     123      """A candidate backed by an ``InstallRequirement``.
     124  
     125      This represents a package request with the target not being already
     126      in the environment, and needs to be fetched and installed. The backing
     127      ``InstallRequirement`` is responsible for most of the leg work; this
     128      class exposes appropriate information to the resolver.
     129  
     130      :param link: The link passed to the ``InstallRequirement``. The backing
     131          ``InstallRequirement`` will use this link to fetch the distribution.
     132      :param source_link: The link this candidate "originates" from. This is
     133          different from ``link`` when the link is found in the wheel cache.
     134          ``link`` would point to the wheel cache, while this points to the
     135          found remote link (e.g. from pypi.org).
     136      """
     137  
     138      dist: BaseDistribution
     139      is_installed = False
     140  
     141      def __init__(
     142          self,
     143          link: Link,
     144          source_link: Link,
     145          ireq: InstallRequirement,
     146          factory: "Factory",
     147          name: Optional[NormalizedName] = None,
     148          version: Optional[CandidateVersion] = None,
     149      ) -> None:
     150          self._link = link
     151          self._source_link = source_link
     152          self._factory = factory
     153          self._ireq = ireq
     154          self._name = name
     155          self._version = version
     156          self.dist = self._prepare()
     157  
     158      def __str__(self) -> str:
     159          return f"{self.name} {self.version}"
     160  
     161      def __repr__(self) -> str:
     162          return "{class_name}({link!r})".format(
     163              class_name=self.__class__.__name__,
     164              link=str(self._link),
     165          )
     166  
     167      def __hash__(self) -> int:
     168          return hash((self.__class__, self._link))
     169  
     170      def __eq__(self, other: Any) -> bool:
     171          if isinstance(other, self.__class__):
     172              return links_equivalent(self._link, other._link)
     173          return False
     174  
     175      @property
     176      def source_link(self) -> Optional[Link]:
     177          return self._source_link
     178  
     179      @property
     180      def project_name(self) -> NormalizedName:
     181          """The normalised name of the project the candidate refers to"""
     182          if self._name is None:
     183              self._name = self.dist.canonical_name
     184          return self._name
     185  
     186      @property
     187      def name(self) -> str:
     188          return self.project_name
     189  
     190      @property
     191      def version(self) -> CandidateVersion:
     192          if self._version is None:
     193              self._version = self.dist.version
     194          return self._version
     195  
     196      def format_for_error(self) -> str:
     197          return "{} {} (from {})".format(
     198              self.name,
     199              self.version,
     200              self._link.file_path if self._link.is_file else self._link,
     201          )
     202  
     203      def _prepare_distribution(self) -> BaseDistribution:
     204          raise NotImplementedError("Override in subclass")
     205  
     206      def _check_metadata_consistency(self, dist: BaseDistribution) -> None:
     207          """Check for consistency of project name and version of dist."""
     208          if self._name is not None and self._name != dist.canonical_name:
     209              raise MetadataInconsistent(
     210                  self._ireq,
     211                  "name",
     212                  self._name,
     213                  dist.canonical_name,
     214              )
     215          if self._version is not None and self._version != dist.version:
     216              raise MetadataInconsistent(
     217                  self._ireq,
     218                  "version",
     219                  str(self._version),
     220                  str(dist.version),
     221              )
     222  
     223      def _prepare(self) -> BaseDistribution:
     224          try:
     225              dist = self._prepare_distribution()
     226          except HashError as e:
     227              # Provide HashError the underlying ireq that caused it. This
     228              # provides context for the resulting error message to show the
     229              # offending line to the user.
     230              e.req = self._ireq
     231              raise
     232          except InstallationSubprocessError as exc:
     233              # The output has been presented already, so don't duplicate it.
     234              exc.context = "See above for output."
     235              raise
     236  
     237          self._check_metadata_consistency(dist)
     238          return dist
     239  
     240      def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
     241          requires = self.dist.iter_dependencies() if with_requires else ()
     242          for r in requires:
     243              yield self._factory.make_requirement_from_spec(str(r), self._ireq)
     244          yield self._factory.make_requires_python_requirement(self.dist.requires_python)
     245  
     246      def get_install_requirement(self) -> Optional[InstallRequirement]:
     247          return self._ireq
     248  
     249  
     250  class ESC[4;38;5;81mLinkCandidate(ESC[4;38;5;149m_InstallRequirementBackedCandidate):
     251      is_editable = False
     252  
     253      def __init__(
     254          self,
     255          link: Link,
     256          template: InstallRequirement,
     257          factory: "Factory",
     258          name: Optional[NormalizedName] = None,
     259          version: Optional[CandidateVersion] = None,
     260      ) -> None:
     261          source_link = link
     262          cache_entry = factory.get_wheel_cache_entry(source_link, name)
     263          if cache_entry is not None:
     264              logger.debug("Using cached wheel link: %s", cache_entry.link)
     265              link = cache_entry.link
     266          ireq = make_install_req_from_link(link, template)
     267          assert ireq.link == link
     268          if ireq.link.is_wheel and not ireq.link.is_file:
     269              wheel = Wheel(ireq.link.filename)
     270              wheel_name = canonicalize_name(wheel.name)
     271              assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel"
     272              # Version may not be present for PEP 508 direct URLs
     273              if version is not None:
     274                  wheel_version = Version(wheel.version)
     275                  assert version == wheel_version, "{!r} != {!r} for wheel {}".format(
     276                      version, wheel_version, name
     277                  )
     278  
     279          if cache_entry is not None:
     280              assert ireq.link.is_wheel
     281              assert ireq.link.is_file
     282              if cache_entry.persistent and template.link is template.original_link:
     283                  ireq.cached_wheel_source_link = source_link
     284              if cache_entry.origin is not None:
     285                  ireq.download_info = cache_entry.origin
     286              else:
     287                  # Legacy cache entry that does not have origin.json.
     288                  # download_info may miss the archive_info.hashes field.
     289                  ireq.download_info = direct_url_from_link(
     290                      source_link, link_is_in_wheel_cache=cache_entry.persistent
     291                  )
     292  
     293          super().__init__(
     294              link=link,
     295              source_link=source_link,
     296              ireq=ireq,
     297              factory=factory,
     298              name=name,
     299              version=version,
     300          )
     301  
     302      def _prepare_distribution(self) -> BaseDistribution:
     303          preparer = self._factory.preparer
     304          return preparer.prepare_linked_requirement(self._ireq, parallel_builds=True)
     305  
     306  
     307  class ESC[4;38;5;81mEditableCandidate(ESC[4;38;5;149m_InstallRequirementBackedCandidate):
     308      is_editable = True
     309  
     310      def __init__(
     311          self,
     312          link: Link,
     313          template: InstallRequirement,
     314          factory: "Factory",
     315          name: Optional[NormalizedName] = None,
     316          version: Optional[CandidateVersion] = None,
     317      ) -> None:
     318          super().__init__(
     319              link=link,
     320              source_link=link,
     321              ireq=make_install_req_from_editable(link, template),
     322              factory=factory,
     323              name=name,
     324              version=version,
     325          )
     326  
     327      def _prepare_distribution(self) -> BaseDistribution:
     328          return self._factory.preparer.prepare_editable_requirement(self._ireq)
     329  
     330  
     331  class ESC[4;38;5;81mAlreadyInstalledCandidate(ESC[4;38;5;149mCandidate):
     332      is_installed = True
     333      source_link = None
     334  
     335      def __init__(
     336          self,
     337          dist: BaseDistribution,
     338          template: InstallRequirement,
     339          factory: "Factory",
     340      ) -> None:
     341          self.dist = dist
     342          self._ireq = _make_install_req_from_dist(dist, template)
     343          self._factory = factory
     344          self._version = None
     345  
     346          # This is just logging some messages, so we can do it eagerly.
     347          # The returned dist would be exactly the same as self.dist because we
     348          # set satisfied_by in _make_install_req_from_dist.
     349          # TODO: Supply reason based on force_reinstall and upgrade_strategy.
     350          skip_reason = "already satisfied"
     351          factory.preparer.prepare_installed_requirement(self._ireq, skip_reason)
     352  
     353      def __str__(self) -> str:
     354          return str(self.dist)
     355  
     356      def __repr__(self) -> str:
     357          return "{class_name}({distribution!r})".format(
     358              class_name=self.__class__.__name__,
     359              distribution=self.dist,
     360          )
     361  
     362      def __hash__(self) -> int:
     363          return hash((self.__class__, self.name, self.version))
     364  
     365      def __eq__(self, other: Any) -> bool:
     366          if isinstance(other, self.__class__):
     367              return self.name == other.name and self.version == other.version
     368          return False
     369  
     370      @property
     371      def project_name(self) -> NormalizedName:
     372          return self.dist.canonical_name
     373  
     374      @property
     375      def name(self) -> str:
     376          return self.project_name
     377  
     378      @property
     379      def version(self) -> CandidateVersion:
     380          if self._version is None:
     381              self._version = self.dist.version
     382          return self._version
     383  
     384      @property
     385      def is_editable(self) -> bool:
     386          return self.dist.editable
     387  
     388      def format_for_error(self) -> str:
     389          return f"{self.name} {self.version} (Installed)"
     390  
     391      def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
     392          if not with_requires:
     393              return
     394          for r in self.dist.iter_dependencies():
     395              yield self._factory.make_requirement_from_spec(str(r), self._ireq)
     396  
     397      def get_install_requirement(self) -> Optional[InstallRequirement]:
     398          return None
     399  
     400  
     401  class ESC[4;38;5;81mExtrasCandidate(ESC[4;38;5;149mCandidate):
     402      """A candidate that has 'extras', indicating additional dependencies.
     403  
     404      Requirements can be for a project with dependencies, something like
     405      foo[extra].  The extras don't affect the project/version being installed
     406      directly, but indicate that we need additional dependencies. We model that
     407      by having an artificial ExtrasCandidate that wraps the "base" candidate.
     408  
     409      The ExtrasCandidate differs from the base in the following ways:
     410  
     411      1. It has a unique name, of the form foo[extra]. This causes the resolver
     412         to treat it as a separate node in the dependency graph.
     413      2. When we're getting the candidate's dependencies,
     414         a) We specify that we want the extra dependencies as well.
     415         b) We add a dependency on the base candidate.
     416            See below for why this is needed.
     417      3. We return None for the underlying InstallRequirement, as the base
     418         candidate will provide it, and we don't want to end up with duplicates.
     419  
     420      The dependency on the base candidate is needed so that the resolver can't
     421      decide that it should recommend foo[extra1] version 1.0 and foo[extra2]
     422      version 2.0. Having those candidates depend on foo=1.0 and foo=2.0
     423      respectively forces the resolver to recognise that this is a conflict.
     424      """
     425  
     426      def __init__(
     427          self,
     428          base: BaseCandidate,
     429          extras: FrozenSet[str],
     430      ) -> None:
     431          self.base = base
     432          self.extras = extras
     433  
     434      def __str__(self) -> str:
     435          name, rest = str(self.base).split(" ", 1)
     436          return "{}[{}] {}".format(name, ",".join(self.extras), rest)
     437  
     438      def __repr__(self) -> str:
     439          return "{class_name}(base={base!r}, extras={extras!r})".format(
     440              class_name=self.__class__.__name__,
     441              base=self.base,
     442              extras=self.extras,
     443          )
     444  
     445      def __hash__(self) -> int:
     446          return hash((self.base, self.extras))
     447  
     448      def __eq__(self, other: Any) -> bool:
     449          if isinstance(other, self.__class__):
     450              return self.base == other.base and self.extras == other.extras
     451          return False
     452  
     453      @property
     454      def project_name(self) -> NormalizedName:
     455          return self.base.project_name
     456  
     457      @property
     458      def name(self) -> str:
     459          """The normalised name of the project the candidate refers to"""
     460          return format_name(self.base.project_name, self.extras)
     461  
     462      @property
     463      def version(self) -> CandidateVersion:
     464          return self.base.version
     465  
     466      def format_for_error(self) -> str:
     467          return "{} [{}]".format(
     468              self.base.format_for_error(), ", ".join(sorted(self.extras))
     469          )
     470  
     471      @property
     472      def is_installed(self) -> bool:
     473          return self.base.is_installed
     474  
     475      @property
     476      def is_editable(self) -> bool:
     477          return self.base.is_editable
     478  
     479      @property
     480      def source_link(self) -> Optional[Link]:
     481          return self.base.source_link
     482  
     483      def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
     484          factory = self.base._factory
     485  
     486          # Add a dependency on the exact base
     487          # (See note 2b in the class docstring)
     488          yield factory.make_requirement_from_candidate(self.base)
     489          if not with_requires:
     490              return
     491  
     492          # The user may have specified extras that the candidate doesn't
     493          # support. We ignore any unsupported extras here.
     494          valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras())
     495          invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras())
     496          for extra in sorted(invalid_extras):
     497              logger.warning(
     498                  "%s %s does not provide the extra '%s'",
     499                  self.base.name,
     500                  self.version,
     501                  extra,
     502              )
     503  
     504          for r in self.base.dist.iter_dependencies(valid_extras):
     505              requirement = factory.make_requirement_from_spec(
     506                  str(r), self.base._ireq, valid_extras
     507              )
     508              if requirement:
     509                  yield requirement
     510  
     511      def get_install_requirement(self) -> Optional[InstallRequirement]:
     512          # We don't return anything here, because we always
     513          # depend on the base candidate, and we'll get the
     514          # install requirement from that.
     515          return None
     516  
     517  
     518  class ESC[4;38;5;81mRequiresPythonCandidate(ESC[4;38;5;149mCandidate):
     519      is_installed = False
     520      source_link = None
     521  
     522      def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None:
     523          if py_version_info is not None:
     524              version_info = normalize_version_info(py_version_info)
     525          else:
     526              version_info = sys.version_info[:3]
     527          self._version = Version(".".join(str(c) for c in version_info))
     528  
     529      # We don't need to implement __eq__() and __ne__() since there is always
     530      # only one RequiresPythonCandidate in a resolution, i.e. the host Python.
     531      # The built-in object.__eq__() and object.__ne__() do exactly what we want.
     532  
     533      def __str__(self) -> str:
     534          return f"Python {self._version}"
     535  
     536      @property
     537      def project_name(self) -> NormalizedName:
     538          return REQUIRES_PYTHON_IDENTIFIER
     539  
     540      @property
     541      def name(self) -> str:
     542          return REQUIRES_PYTHON_IDENTIFIER
     543  
     544      @property
     545      def version(self) -> CandidateVersion:
     546          return self._version
     547  
     548      def format_for_error(self) -> str:
     549          return f"Python {self.version}"
     550  
     551      def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
     552          return ()
     553  
     554      def get_install_requirement(self) -> Optional[InstallRequirement]:
     555          return None