provider.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. from __future__ import annotations
  2. import math
  3. from collections.abc import Iterable, Iterator, Mapping, Sequence
  4. from functools import cache
  5. from typing import (
  6. TYPE_CHECKING,
  7. TypeVar,
  8. )
  9. from pip._vendor.resolvelib.providers import AbstractProvider
  10. from pip._internal.req.req_install import InstallRequirement
  11. from .base import Candidate, Constraint, Requirement
  12. from .candidates import REQUIRES_PYTHON_IDENTIFIER
  13. from .factory import Factory
  14. from .requirements import ExplicitRequirement
  15. if TYPE_CHECKING:
  16. from pip._vendor.resolvelib.providers import Preference
  17. from pip._vendor.resolvelib.resolvers import RequirementInformation
  18. PreferenceInformation = RequirementInformation[Requirement, Candidate]
  19. _ProviderBase = AbstractProvider[Requirement, Candidate, str]
  20. else:
  21. _ProviderBase = AbstractProvider
  22. # Notes on the relationship between the provider, the factory, and the
  23. # candidate and requirement classes.
  24. #
  25. # The provider is a direct implementation of the resolvelib class. Its role
  26. # is to deliver the API that resolvelib expects.
  27. #
  28. # Rather than work with completely abstract "requirement" and "candidate"
  29. # concepts as resolvelib does, pip has concrete classes implementing these two
  30. # ideas. The API of Requirement and Candidate objects are defined in the base
  31. # classes, but essentially map fairly directly to the equivalent provider
  32. # methods. In particular, `find_matches` and `is_satisfied_by` are
  33. # requirement methods, and `get_dependencies` is a candidate method.
  34. #
  35. # The factory is the interface to pip's internal mechanisms. It is stateless,
  36. # and is created by the resolver and held as a property of the provider. It is
  37. # responsible for creating Requirement and Candidate objects, and provides
  38. # services to those objects (access to pip's finder and preparer).
  39. D = TypeVar("D")
  40. V = TypeVar("V")
  41. def _get_with_identifier(
  42. mapping: Mapping[str, V],
  43. identifier: str,
  44. default: D,
  45. ) -> D | V:
  46. """Get item from a package name lookup mapping with a resolver identifier.
  47. This extra logic is needed when the target mapping is keyed by package
  48. name, which cannot be directly looked up with an identifier (which may
  49. contain requested extras). Additional logic is added to also look up a value
  50. by "cleaning up" the extras from the identifier.
  51. """
  52. if identifier in mapping:
  53. return mapping[identifier]
  54. # HACK: Theoretically we should check whether this identifier is a valid
  55. # "NAME[EXTRAS]" format, and parse out the name part with packaging or
  56. # some regular expression. But since pip's resolver only spits out three
  57. # kinds of identifiers: normalized PEP 503 names, normalized names plus
  58. # extras, and Requires-Python, we can cheat a bit here.
  59. name, open_bracket, _ = identifier.partition("[")
  60. if open_bracket and name in mapping:
  61. return mapping[name]
  62. return default
  63. class PipProvider(_ProviderBase):
  64. """Pip's provider implementation for resolvelib.
  65. :params constraints: A mapping of constraints specified by the user. Keys
  66. are canonicalized project names.
  67. :params ignore_dependencies: Whether the user specified ``--no-deps``.
  68. :params upgrade_strategy: The user-specified upgrade strategy.
  69. :params user_requested: A set of canonicalized package names that the user
  70. supplied for pip to install/upgrade.
  71. """
  72. def __init__(
  73. self,
  74. factory: Factory,
  75. constraints: dict[str, Constraint],
  76. ignore_dependencies: bool,
  77. upgrade_strategy: str,
  78. user_requested: dict[str, int],
  79. ) -> None:
  80. self._factory = factory
  81. self._constraints = constraints
  82. self._ignore_dependencies = ignore_dependencies
  83. self._upgrade_strategy = upgrade_strategy
  84. self._user_requested = user_requested
  85. @property
  86. def constraints(self) -> dict[str, Constraint]:
  87. """Public view of user-specified constraints.
  88. Exposes the provider's constraints mapping without encouraging
  89. external callers to reach into private attributes.
  90. """
  91. return self._constraints
  92. def identify(self, requirement_or_candidate: Requirement | Candidate) -> str:
  93. return requirement_or_candidate.name
  94. def narrow_requirement_selection(
  95. self,
  96. identifiers: Iterable[str],
  97. resolutions: Mapping[str, Candidate],
  98. candidates: Mapping[str, Iterator[Candidate]],
  99. information: Mapping[str, Iterator[PreferenceInformation]],
  100. backtrack_causes: Sequence[PreferenceInformation],
  101. ) -> Iterable[str]:
  102. """Produce a subset of identifiers that should be considered before others.
  103. Currently pip narrows the following selection:
  104. * Requires-Python, if present is always returned by itself
  105. * Backtrack causes are considered next because they can be identified
  106. in linear time here, whereas because get_preference() is called
  107. for each identifier, it would be quadratic to check for them there.
  108. Further, the current backtrack causes likely need to be resolved
  109. before other requirements as a resolution can't be found while
  110. there is a conflict.
  111. """
  112. backtrack_identifiers = set()
  113. for info in backtrack_causes:
  114. backtrack_identifiers.add(info.requirement.name)
  115. if info.parent is not None:
  116. backtrack_identifiers.add(info.parent.name)
  117. current_backtrack_causes = []
  118. for identifier in identifiers:
  119. # Requires-Python has only one candidate and the check is basically
  120. # free, so we always do it first to avoid needless work if it fails.
  121. # This skips calling get_preference() for all other identifiers.
  122. if identifier == REQUIRES_PYTHON_IDENTIFIER:
  123. return [identifier]
  124. # Check if this identifier is a backtrack cause
  125. if identifier in backtrack_identifiers:
  126. current_backtrack_causes.append(identifier)
  127. continue
  128. if current_backtrack_causes:
  129. return current_backtrack_causes
  130. return identifiers
  131. def get_preference(
  132. self,
  133. identifier: str,
  134. resolutions: Mapping[str, Candidate],
  135. candidates: Mapping[str, Iterator[Candidate]],
  136. information: Mapping[str, Iterable[PreferenceInformation]],
  137. backtrack_causes: Sequence[PreferenceInformation],
  138. ) -> Preference:
  139. """Produce a sort key for given requirement based on preference.
  140. The lower the return value is, the more preferred this group of
  141. arguments is.
  142. Currently pip considers the following in order:
  143. * Any requirement that is "direct", e.g., points to an explicit URL.
  144. * Any requirement that is "pinned", i.e., contains the operator ``===``
  145. or ``==`` without a wildcard.
  146. * Any requirement that imposes an upper version limit, i.e., contains the
  147. operator ``<``, ``<=``, ``~=``, or ``==`` with a wildcard. Because
  148. pip prioritizes the latest version, preferring explicit upper bounds
  149. can rule out infeasible candidates sooner. This does not imply that
  150. upper bounds are good practice; they can make dependency management
  151. and resolution harder.
  152. * Order user-specified requirements as they are specified, placing
  153. other requirements afterward.
  154. * Any "non-free" requirement, i.e., one that contains at least one
  155. operator, such as ``>=`` or ``!=``.
  156. * Alphabetical order for consistency (aids debuggability).
  157. """
  158. try:
  159. next(iter(information[identifier]))
  160. except StopIteration:
  161. # There is no information for this identifier, so there's no known
  162. # candidates.
  163. has_information = False
  164. else:
  165. has_information = True
  166. if not has_information:
  167. direct = False
  168. ireqs: tuple[InstallRequirement | None, ...] = ()
  169. else:
  170. # Go through the information and for each requirement,
  171. # check if it's explicit (e.g., a direct link) and get the
  172. # InstallRequirement (the second element) from get_candidate_lookup()
  173. directs, ireqs = zip(
  174. *(
  175. (isinstance(r, ExplicitRequirement), r.get_candidate_lookup()[1])
  176. for r, _ in information[identifier]
  177. )
  178. )
  179. direct = any(directs)
  180. operators: list[tuple[str, str]] = [
  181. (specifier.operator, specifier.version)
  182. for specifier_set in (ireq.specifier for ireq in ireqs if ireq)
  183. for specifier in specifier_set
  184. ]
  185. pinned = any(((op[:2] == "==") and ("*" not in ver)) for op, ver in operators)
  186. upper_bounded = any(
  187. ((op in ("<", "<=", "~=")) or (op == "==" and "*" in ver))
  188. for op, ver in operators
  189. )
  190. unfree = bool(operators)
  191. requested_order = self._user_requested.get(identifier, math.inf)
  192. return (
  193. not direct,
  194. not pinned,
  195. not upper_bounded,
  196. requested_order,
  197. not unfree,
  198. identifier,
  199. )
  200. def find_matches(
  201. self,
  202. identifier: str,
  203. requirements: Mapping[str, Iterator[Requirement]],
  204. incompatibilities: Mapping[str, Iterator[Candidate]],
  205. ) -> Iterable[Candidate]:
  206. def _eligible_for_upgrade(identifier: str) -> bool:
  207. """Are upgrades allowed for this project?
  208. This checks the upgrade strategy, and whether the project was one
  209. that the user specified in the command line, in order to decide
  210. whether we should upgrade if there's a newer version available.
  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. constraint = _get_with_identifier(
  226. self._constraints,
  227. identifier,
  228. default=Constraint.empty(),
  229. )
  230. return self._factory.find_candidates(
  231. identifier=identifier,
  232. requirements=requirements,
  233. constraint=constraint,
  234. prefers_installed=(not _eligible_for_upgrade(identifier)),
  235. incompatibilities=incompatibilities,
  236. is_satisfied_by=self.is_satisfied_by,
  237. )
  238. @staticmethod
  239. @cache
  240. def is_satisfied_by(requirement: Requirement, candidate: Candidate) -> bool:
  241. return requirement.is_satisfied_by(candidate)
  242. def get_dependencies(self, candidate: Candidate) -> Iterable[Requirement]:
  243. with_requires = not self._ignore_dependencies
  244. # iter_dependencies() can perform nontrivial work so delay until needed.
  245. return (r for r in candidate.iter_dependencies(with_requires) if r is not None)