factory.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from pip._vendor.packaging.utils import canonicalize_name
  2. from pip._internal.exceptions import (
  3. InstallationError,
  4. UnsupportedPythonVersion,
  5. )
  6. from pip._internal.utils.misc import get_installed_distributions
  7. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  8. from .candidates import (
  9. AlreadyInstalledCandidate,
  10. EditableCandidate,
  11. ExtrasCandidate,
  12. LinkCandidate,
  13. RequiresPythonCandidate,
  14. )
  15. from .requirements import (
  16. ExplicitRequirement,
  17. RequiresPythonRequirement,
  18. SpecifierRequirement,
  19. )
  20. if MYPY_CHECK_RUNNING:
  21. from typing import Dict, Iterator, Optional, Set, Tuple, TypeVar
  22. from pip._vendor.packaging.specifiers import SpecifierSet
  23. from pip._vendor.packaging.version import _BaseVersion
  24. from pip._vendor.pkg_resources import Distribution
  25. from pip._vendor.resolvelib import ResolutionImpossible
  26. from pip._internal.index.package_finder import PackageFinder
  27. from pip._internal.models.link import Link
  28. from pip._internal.operations.prepare import RequirementPreparer
  29. from pip._internal.req.req_install import InstallRequirement
  30. from pip._internal.resolution.base import InstallRequirementProvider
  31. from .base import Candidate, Requirement
  32. from .candidates import BaseCandidate
  33. C = TypeVar("C")
  34. Cache = Dict[Link, C]
  35. class Factory(object):
  36. def __init__(
  37. self,
  38. finder, # type: PackageFinder
  39. preparer, # type: RequirementPreparer
  40. make_install_req, # type: InstallRequirementProvider
  41. force_reinstall, # type: bool
  42. ignore_installed, # type: bool
  43. ignore_requires_python, # type: bool
  44. py_version_info=None, # type: Optional[Tuple[int, ...]]
  45. ):
  46. # type: (...) -> None
  47. self.finder = finder
  48. self.preparer = preparer
  49. self._python_candidate = RequiresPythonCandidate(py_version_info)
  50. self._make_install_req_from_spec = make_install_req
  51. self._force_reinstall = force_reinstall
  52. self._ignore_requires_python = ignore_requires_python
  53. self._link_candidate_cache = {} # type: Cache[LinkCandidate]
  54. self._editable_candidate_cache = {} # type: Cache[EditableCandidate]
  55. if not ignore_installed:
  56. self._installed_dists = {
  57. canonicalize_name(dist.project_name): dist
  58. for dist in get_installed_distributions()
  59. }
  60. else:
  61. self._installed_dists = {}
  62. def _make_candidate_from_dist(
  63. self,
  64. dist, # type: Distribution
  65. extras, # type: Set[str]
  66. parent, # type: InstallRequirement
  67. ):
  68. # type: (...) -> Candidate
  69. base = AlreadyInstalledCandidate(dist, parent, factory=self)
  70. if extras:
  71. return ExtrasCandidate(base, extras)
  72. return base
  73. def _make_candidate_from_link(
  74. self,
  75. link, # type: Link
  76. extras, # type: Set[str]
  77. parent, # type: InstallRequirement
  78. name=None, # type: Optional[str]
  79. version=None, # type: Optional[_BaseVersion]
  80. ):
  81. # type: (...) -> Candidate
  82. # TODO: Check already installed candidate, and use it if the link and
  83. # editable flag match.
  84. if parent.editable:
  85. if link not in self._editable_candidate_cache:
  86. self._editable_candidate_cache[link] = EditableCandidate(
  87. link, parent, factory=self, name=name, version=version,
  88. )
  89. base = self._editable_candidate_cache[link] # type: BaseCandidate
  90. else:
  91. if link not in self._link_candidate_cache:
  92. self._link_candidate_cache[link] = LinkCandidate(
  93. link, parent, factory=self, name=name, version=version,
  94. )
  95. base = self._link_candidate_cache[link]
  96. if extras:
  97. return ExtrasCandidate(base, extras)
  98. return base
  99. def iter_found_candidates(self, ireq, extras):
  100. # type: (InstallRequirement, Set[str]) -> Iterator[Candidate]
  101. name = canonicalize_name(ireq.req.name)
  102. if not self._force_reinstall:
  103. installed_dist = self._installed_dists.get(name)
  104. else:
  105. installed_dist = None
  106. found = self.finder.find_best_candidate(
  107. project_name=ireq.req.name,
  108. specifier=ireq.req.specifier,
  109. hashes=ireq.hashes(trust_internet=False),
  110. )
  111. for ican in found.iter_applicable():
  112. if (installed_dist is not None and
  113. installed_dist.parsed_version == ican.version):
  114. continue
  115. yield self._make_candidate_from_link(
  116. link=ican.link,
  117. extras=extras,
  118. parent=ireq,
  119. name=name,
  120. version=ican.version,
  121. )
  122. # Return installed distribution if it matches the specifier. This is
  123. # done last so the resolver will prefer it over downloading links.
  124. if (installed_dist is not None and
  125. installed_dist.parsed_version in ireq.req.specifier):
  126. yield self._make_candidate_from_dist(
  127. dist=installed_dist,
  128. extras=extras,
  129. parent=ireq,
  130. )
  131. def make_requirement_from_install_req(self, ireq):
  132. # type: (InstallRequirement) -> Requirement
  133. if ireq.link:
  134. # TODO: Get name and version from ireq, if possible?
  135. # Specifically, this might be needed in "name @ URL"
  136. # syntax - need to check where that syntax is handled.
  137. cand = self._make_candidate_from_link(
  138. ireq.link, extras=set(), parent=ireq,
  139. )
  140. return ExplicitRequirement(cand)
  141. return SpecifierRequirement(ireq, factory=self)
  142. def make_requirement_from_spec(self, specifier, comes_from):
  143. # type: (str, InstallRequirement) -> Requirement
  144. ireq = self._make_install_req_from_spec(specifier, comes_from)
  145. return self.make_requirement_from_install_req(ireq)
  146. def make_requires_python_requirement(self, specifier):
  147. # type: (Optional[SpecifierSet]) -> Optional[Requirement]
  148. if self._ignore_requires_python or specifier is None:
  149. return None
  150. return RequiresPythonRequirement(specifier, self._python_candidate)
  151. def should_reinstall(self, candidate):
  152. # type: (Candidate) -> bool
  153. # TODO: Are there more cases this needs to return True? Editable?
  154. return candidate.name in self._installed_dists
  155. def _report_requires_python_error(
  156. self,
  157. requirement, # type: RequiresPythonRequirement
  158. parent, # type: Candidate
  159. ):
  160. # type: (...) -> UnsupportedPythonVersion
  161. template = (
  162. "Package {package!r} requires a different Python: "
  163. "{version} not in {specifier!r}"
  164. )
  165. message = template.format(
  166. package=parent.name,
  167. version=self._python_candidate.version,
  168. specifier=str(requirement.specifier),
  169. )
  170. return UnsupportedPythonVersion(message)
  171. def get_installation_error(self, e):
  172. # type: (ResolutionImpossible) -> Optional[InstallationError]
  173. for cause in e.causes:
  174. if isinstance(cause.requirement, RequiresPythonRequirement):
  175. return self._report_requires_python_error(
  176. cause.requirement,
  177. cause.parent,
  178. )
  179. return None