candidates.py 20 KB

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