3 from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast
5 from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
6 from pip._vendor.packaging.version import Version
8 from pip._internal.exceptions import (
10 InstallationSubprocessError,
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,
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
24 from .base import Candidate, CandidateVersion, Requirement, format_name
27 from .factory import Factory
29 logger = logging.getLogger(__name__)
31 BaseCandidate = Union[
32 "AlreadyInstalledCandidate",
37 # Avoid conflicting with the PyPI package "Python".
38 REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")
41 def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
42 """The runtime version of BaseCandidate."""
43 base_candidate_classes = (
44 AlreadyInstalledCandidate,
48 if isinstance(candidate, base_candidate_classes):
53 def make_install_req_from_link(
54 link: Link, template: InstallRequirement
55 ) -> InstallRequirement:
56 assert not template.editable, "template is editable"
58 line = str(template.req)
61 ireq = install_req_from_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,
69 install_options=template.install_options,
70 global_options=template.global_options,
71 hashes=template.hash_options,
73 config_settings=template.config_settings,
75 ireq.original_link = template.original_link
80 def make_install_req_from_editable(
81 link: Link, template: InstallRequirement
82 ) -> InstallRequirement:
83 assert template.editable, "template not editable"
84 return install_req_from_editable(
86 user_supplied=template.user_supplied,
87 comes_from=template.comes_from,
88 use_pep517=template.use_pep517,
89 isolated=template.isolated,
90 constraint=template.constraint,
91 permit_editable_wheels=template.permit_editable_wheels,
93 install_options=template.install_options,
94 global_options=template.global_options,
95 hashes=template.hash_options,
97 config_settings=template.config_settings,
101 def _make_install_req_from_dist(
102 dist: BaseDistribution, template: InstallRequirement
103 ) -> InstallRequirement:
105 line = str(template.req)
107 line = f"{dist.canonical_name} @ {template.link.url}"
109 line = f"{dist.canonical_name}=={dist.version}"
110 ireq = install_req_from_line(
112 user_supplied=template.user_supplied,
113 comes_from=template.comes_from,
114 use_pep517=template.use_pep517,
115 isolated=template.isolated,
116 constraint=template.constraint,
118 install_options=template.install_options,
119 global_options=template.global_options,
120 hashes=template.hash_options,
122 config_settings=template.config_settings,
124 ireq.satisfied_by = dist
128 class _InstallRequirementBackedCandidate(Candidate):
129 """A candidate backed by an ``InstallRequirement``.
131 This represents a package request with the target not being already
132 in the environment, and needs to be fetched and installed. The backing
133 ``InstallRequirement`` is responsible for most of the leg work; this
134 class exposes appropriate information to the resolver.
136 :param link: The link passed to the ``InstallRequirement``. The backing
137 ``InstallRequirement`` will use this link to fetch the distribution.
138 :param source_link: The link this candidate "originates" from. This is
139 different from ``link`` when the link is found in the wheel cache.
140 ``link`` would point to the wheel cache, while this points to the
141 found remote link (e.g. from pypi.org).
144 dist: BaseDistribution
151 ireq: InstallRequirement,
153 name: Optional[NormalizedName] = None,
154 version: Optional[CandidateVersion] = None,
157 self._source_link = source_link
158 self._factory = factory
161 self._version = version
162 self.dist = self._prepare()
164 def __str__(self) -> str:
165 return f"{self.name} {self.version}"
167 def __repr__(self) -> str:
168 return "{class_name}({link!r})".format(
169 class_name=self.__class__.__name__,
170 link=str(self._link),
173 def __hash__(self) -> int:
174 return hash((self.__class__, self._link))
176 def __eq__(self, other: Any) -> bool:
177 if isinstance(other, self.__class__):
178 return links_equivalent(self._link, other._link)
182 def source_link(self) -> Optional[Link]:
183 return self._source_link
186 def project_name(self) -> NormalizedName:
187 """The normalised name of the project the candidate refers to"""
188 if self._name is None:
189 self._name = self.dist.canonical_name
193 def name(self) -> str:
194 return self.project_name
197 def version(self) -> CandidateVersion:
198 if self._version is None:
199 self._version = self.dist.version
202 def format_for_error(self) -> str:
203 return "{} {} (from {})".format(
206 self._link.file_path if self._link.is_file else self._link,
209 def _prepare_distribution(self) -> BaseDistribution:
210 raise NotImplementedError("Override in subclass")
212 def _check_metadata_consistency(self, dist: BaseDistribution) -> None:
213 """Check for consistency of project name and version of dist."""
214 if self._name is not None and self._name != dist.canonical_name:
215 raise MetadataInconsistent(
221 if self._version is not None and self._version != dist.version:
222 raise MetadataInconsistent(
229 def _prepare(self) -> BaseDistribution:
231 dist = self._prepare_distribution()
232 except HashError as e:
233 # Provide HashError the underlying ireq that caused it. This
234 # provides context for the resulting error message to show the
235 # offending line to the user.
238 except InstallationSubprocessError as exc:
239 # The output has been presented already, so don't duplicate it.
240 exc.context = "See above for output."
243 self._check_metadata_consistency(dist)
246 def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
247 requires = self.dist.iter_dependencies() if with_requires else ()
249 yield self._factory.make_requirement_from_spec(str(r), self._ireq)
250 yield self._factory.make_requires_python_requirement(self.dist.requires_python)
252 def get_install_requirement(self) -> Optional[InstallRequirement]:
256 class LinkCandidate(_InstallRequirementBackedCandidate):
262 template: InstallRequirement,
264 name: Optional[NormalizedName] = None,
265 version: Optional[CandidateVersion] = None,
268 cache_entry = factory.get_wheel_cache_entry(link, name)
269 if cache_entry is not None:
270 logger.debug("Using cached wheel link: %s", cache_entry.link)
271 link = cache_entry.link
272 ireq = make_install_req_from_link(link, template)
273 assert ireq.link == link
274 if ireq.link.is_wheel and not ireq.link.is_file:
275 wheel = Wheel(ireq.link.filename)
276 wheel_name = canonicalize_name(wheel.name)
277 assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel"
278 # Version may not be present for PEP 508 direct URLs
279 if version is not None:
280 wheel_version = Version(wheel.version)
281 assert version == wheel_version, "{!r} != {!r} for wheel {}".format(
282 version, wheel_version, name
285 if cache_entry is not None:
286 if cache_entry.persistent and template.link is template.original_link:
287 ireq.original_link_is_in_wheel_cache = True
288 if cache_entry.origin is not None:
289 ireq.download_info = cache_entry.origin
291 # Legacy cache entry that does not have origin.json.
292 # download_info may miss the archive_info.hash field.
293 ireq.download_info = direct_url_from_link(
294 source_link, link_is_in_wheel_cache=cache_entry.persistent
299 source_link=source_link,
306 def _prepare_distribution(self) -> BaseDistribution:
307 preparer = self._factory.preparer
308 return preparer.prepare_linked_requirement(self._ireq, parallel_builds=True)
311 class EditableCandidate(_InstallRequirementBackedCandidate):
317 template: InstallRequirement,
319 name: Optional[NormalizedName] = None,
320 version: Optional[CandidateVersion] = None,
325 ireq=make_install_req_from_editable(link, template),
331 def _prepare_distribution(self) -> BaseDistribution:
332 return self._factory.preparer.prepare_editable_requirement(self._ireq)
335 class AlreadyInstalledCandidate(Candidate):
341 dist: BaseDistribution,
342 template: InstallRequirement,
346 self._ireq = _make_install_req_from_dist(dist, template)
347 self._factory = factory
349 # This is just logging some messages, so we can do it eagerly.
350 # The returned dist would be exactly the same as self.dist because we
351 # set satisfied_by in _make_install_req_from_dist.
352 # TODO: Supply reason based on force_reinstall and upgrade_strategy.
353 skip_reason = "already satisfied"
354 factory.preparer.prepare_installed_requirement(self._ireq, skip_reason)
356 def __str__(self) -> str:
357 return str(self.dist)
359 def __repr__(self) -> str:
360 return "{class_name}({distribution!r})".format(
361 class_name=self.__class__.__name__,
362 distribution=self.dist,
365 def __hash__(self) -> int:
366 return hash((self.__class__, self.name, self.version))
368 def __eq__(self, other: Any) -> bool:
369 if isinstance(other, self.__class__):
370 return self.name == other.name and self.version == other.version
374 def project_name(self) -> NormalizedName:
375 return self.dist.canonical_name
378 def name(self) -> str:
379 return self.project_name
382 def version(self) -> CandidateVersion:
383 return self.dist.version
386 def is_editable(self) -> bool:
387 return self.dist.editable
389 def format_for_error(self) -> str:
390 return f"{self.name} {self.version} (Installed)"
392 def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
393 if not with_requires:
395 for r in self.dist.iter_dependencies():
396 yield self._factory.make_requirement_from_spec(str(r), self._ireq)
398 def get_install_requirement(self) -> Optional[InstallRequirement]:
402 class ExtrasCandidate(Candidate):
403 """A candidate that has 'extras', indicating additional dependencies.
405 Requirements can be for a project with dependencies, something like
406 foo[extra]. The extras don't affect the project/version being installed
407 directly, but indicate that we need additional dependencies. We model that
408 by having an artificial ExtrasCandidate that wraps the "base" candidate.
410 The ExtrasCandidate differs from the base in the following ways:
412 1. It has a unique name, of the form foo[extra]. This causes the resolver
413 to treat it as a separate node in the dependency graph.
414 2. When we're getting the candidate's dependencies,
415 a) We specify that we want the extra dependencies as well.
416 b) We add a dependency on the base candidate.
417 See below for why this is needed.
418 3. We return None for the underlying InstallRequirement, as the base
419 candidate will provide it, and we don't want to end up with duplicates.
421 The dependency on the base candidate is needed so that the resolver can't
422 decide that it should recommend foo[extra1] version 1.0 and foo[extra2]
423 version 2.0. Having those candidates depend on foo=1.0 and foo=2.0
424 respectively forces the resolver to recognise that this is a conflict.
430 extras: FrozenSet[str],
435 def __str__(self) -> str:
436 name, rest = str(self.base).split(" ", 1)
437 return "{}[{}] {}".format(name, ",".join(self.extras), rest)
439 def __repr__(self) -> str:
440 return "{class_name}(base={base!r}, extras={extras!r})".format(
441 class_name=self.__class__.__name__,
446 def __hash__(self) -> int:
447 return hash((self.base, self.extras))
449 def __eq__(self, other: Any) -> bool:
450 if isinstance(other, self.__class__):
451 return self.base == other.base and self.extras == other.extras
455 def project_name(self) -> NormalizedName:
456 return self.base.project_name
459 def name(self) -> str:
460 """The normalised name of the project the candidate refers to"""
461 return format_name(self.base.project_name, self.extras)
464 def version(self) -> CandidateVersion:
465 return self.base.version
467 def format_for_error(self) -> str:
468 return "{} [{}]".format(
469 self.base.format_for_error(), ", ".join(sorted(self.extras))
473 def is_installed(self) -> bool:
474 return self.base.is_installed
477 def is_editable(self) -> bool:
478 return self.base.is_editable
481 def source_link(self) -> Optional[Link]:
482 return self.base.source_link
484 def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
485 factory = self.base._factory
487 # Add a dependency on the exact base
488 # (See note 2b in the class docstring)
489 yield factory.make_requirement_from_candidate(self.base)
490 if not with_requires:
493 # The user may have specified extras that the candidate doesn't
494 # support. We ignore any unsupported extras here.
495 valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras())
496 invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras())
497 for extra in sorted(invalid_extras):
499 "%s %s does not provide the extra '%s'",
505 for r in self.base.dist.iter_dependencies(valid_extras):
506 requirement = factory.make_requirement_from_spec(
507 str(r), self.base._ireq, valid_extras
512 def get_install_requirement(self) -> Optional[InstallRequirement]:
513 # We don't return anything here, because we always
514 # depend on the base candidate, and we'll get the
515 # install requirement from that.
519 class RequiresPythonCandidate(Candidate):
523 def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None:
524 if py_version_info is not None:
525 version_info = normalize_version_info(py_version_info)
527 version_info = sys.version_info[:3]
528 self._version = Version(".".join(str(c) for c in version_info))
530 # We don't need to implement __eq__() and __ne__() since there is always
531 # only one RequiresPythonCandidate in a resolution, i.e. the host Python.
532 # The built-in object.__eq__() and object.__ne__() do exactly what we want.
534 def __str__(self) -> str:
535 return f"Python {self._version}"
538 def project_name(self) -> NormalizedName:
539 return REQUIRES_PYTHON_IDENTIFIER
542 def name(self) -> str:
543 return REQUIRES_PYTHON_IDENTIFIER
546 def version(self) -> CandidateVersion:
549 def format_for_error(self) -> str:
550 return f"Python {self.version}"
552 def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
555 def get_install_requirement(self) -> Optional[InstallRequirement]: