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
,
68 global_options
=template
.global_options
,
69 hash_options
=template
.hash_options
,
70 config_settings
=template
.config_settings
,
72 ireq
.original_link
= template
.original_link
74 ireq
.extras
= template
.extras
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(
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
,
94 ireq
.extras
= template
.extras
98 def _make_install_req_from_dist(
99 dist
: BaseDistribution
, template
: InstallRequirement
100 ) -> InstallRequirement
:
102 line
= str(template
.req
)
104 line
= f
"{dist.canonical_name} @ {template.link.url}"
106 line
= f
"{dist.canonical_name}=={dist.version}"
107 ireq
= install_req_from_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
,
118 ireq
.satisfied_by
= dist
122 class _InstallRequirementBackedCandidate(Candidate
):
123 """A candidate backed by an ``InstallRequirement``.
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.
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).
138 dist
: BaseDistribution
145 ireq
: InstallRequirement
,
147 name
: Optional
[NormalizedName
] = None,
148 version
: Optional
[CandidateVersion
] = None,
151 self
._source
_link
= source_link
152 self
._factory
= factory
155 self
._version
= version
156 self
.dist
= self
._prepare
()
158 def __str__(self
) -> str:
159 return f
"{self.name} {self.version}"
161 def __repr__(self
) -> str:
162 return "{class_name}({link!r})".format(
163 class_name
=self
.__class
__.__name
__,
164 link
=str(self
._link
),
167 def __hash__(self
) -> int:
168 return hash((self
.__class
__, self
._link
))
170 def __eq__(self
, other
: Any
) -> bool:
171 if isinstance(other
, self
.__class
__):
172 return links_equivalent(self
._link
, other
._link
)
176 def source_link(self
) -> Optional
[Link
]:
177 return self
._source
_link
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
187 def name(self
) -> str:
188 return self
.project_name
191 def version(self
) -> CandidateVersion
:
192 if self
._version
is None:
193 self
._version
= self
.dist
.version
196 def format_for_error(self
) -> str:
197 return "{} {} (from {})".format(
200 self
._link
.file_path
if self
._link
.is_file
else self
._link
,
203 def _prepare_distribution(self
) -> BaseDistribution
:
204 raise NotImplementedError("Override in subclass")
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(
215 if self
._version
is not None and self
._version
!= dist
.version
:
216 raise MetadataInconsistent(
223 def _prepare(self
) -> BaseDistribution
:
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.
232 except InstallationSubprocessError
as exc
:
233 # The output has been presented already, so don't duplicate it.
234 exc
.context
= "See above for output."
237 self
._check
_metadata
_consistency
(dist
)
240 def iter_dependencies(self
, with_requires
: bool) -> Iterable
[Optional
[Requirement
]]:
241 requires
= self
.dist
.iter_dependencies() if with_requires
else ()
243 yield self
._factory
.make_requirement_from_spec(str(r
), self
._ireq
)
244 yield self
._factory
.make_requires_python_requirement(self
.dist
.requires_python
)
246 def get_install_requirement(self
) -> Optional
[InstallRequirement
]:
250 class LinkCandidate(_InstallRequirementBackedCandidate
):
256 template
: InstallRequirement
,
258 name
: Optional
[NormalizedName
] = None,
259 version
: Optional
[CandidateVersion
] = None,
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
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
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
295 source_link
=source_link
,
302 def _prepare_distribution(self
) -> BaseDistribution
:
303 preparer
= self
._factory
.preparer
304 return preparer
.prepare_linked_requirement(self
._ireq
, parallel_builds
=True)
307 class EditableCandidate(_InstallRequirementBackedCandidate
):
313 template
: InstallRequirement
,
315 name
: Optional
[NormalizedName
] = None,
316 version
: Optional
[CandidateVersion
] = None,
321 ireq
=make_install_req_from_editable(link
, template
),
327 def _prepare_distribution(self
) -> BaseDistribution
:
328 return self
._factory
.preparer
.prepare_editable_requirement(self
._ireq
)
331 class AlreadyInstalledCandidate(Candidate
):
337 dist
: BaseDistribution
,
338 template
: InstallRequirement
,
342 self
._ireq
= _make_install_req_from_dist(dist
, template
)
343 self
._factory
= factory
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
)
353 def __str__(self
) -> str:
354 return str(self
.dist
)
356 def __repr__(self
) -> str:
357 return "{class_name}({distribution!r})".format(
358 class_name
=self
.__class
__.__name
__,
359 distribution
=self
.dist
,
362 def __hash__(self
) -> int:
363 return hash((self
.__class
__, self
.name
, self
.version
))
365 def __eq__(self
, other
: Any
) -> bool:
366 if isinstance(other
, self
.__class
__):
367 return self
.name
== other
.name
and self
.version
== other
.version
371 def project_name(self
) -> NormalizedName
:
372 return self
.dist
.canonical_name
375 def name(self
) -> str:
376 return self
.project_name
379 def version(self
) -> CandidateVersion
:
380 if self
._version
is None:
381 self
._version
= self
.dist
.version
385 def is_editable(self
) -> bool:
386 return self
.dist
.editable
388 def format_for_error(self
) -> str:
389 return f
"{self.name} {self.version} (Installed)"
391 def iter_dependencies(self
, with_requires
: bool) -> Iterable
[Optional
[Requirement
]]:
392 if not with_requires
:
394 for r
in self
.dist
.iter_dependencies():
395 yield self
._factory
.make_requirement_from_spec(str(r
), self
._ireq
)
397 def get_install_requirement(self
) -> Optional
[InstallRequirement
]:
401 class ExtrasCandidate(Candidate
):
402 """A candidate that has 'extras', indicating additional dependencies.
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.
409 The ExtrasCandidate differs from the base in the following ways:
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.
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.
429 extras
: FrozenSet
[str],
434 def __str__(self
) -> str:
435 name
, rest
= str(self
.base
).split(" ", 1)
436 return "{}[{}] {}".format(name
, ",".join(self
.extras
), rest
)
438 def __repr__(self
) -> str:
439 return "{class_name}(base={base!r}, extras={extras!r})".format(
440 class_name
=self
.__class
__.__name
__,
445 def __hash__(self
) -> int:
446 return hash((self
.base
, self
.extras
))
448 def __eq__(self
, other
: Any
) -> bool:
449 if isinstance(other
, self
.__class
__):
450 return self
.base
== other
.base
and self
.extras
== other
.extras
454 def project_name(self
) -> NormalizedName
:
455 return self
.base
.project_name
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
)
463 def version(self
) -> CandidateVersion
:
464 return self
.base
.version
466 def format_for_error(self
) -> str:
467 return "{} [{}]".format(
468 self
.base
.format_for_error(), ", ".join(sorted(self
.extras
))
472 def is_installed(self
) -> bool:
473 return self
.base
.is_installed
476 def is_editable(self
) -> bool:
477 return self
.base
.is_editable
480 def source_link(self
) -> Optional
[Link
]:
481 return self
.base
.source_link
483 def iter_dependencies(self
, with_requires
: bool) -> Iterable
[Optional
[Requirement
]]:
484 factory
= self
.base
._factory
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
:
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
):
498 "%s %s does not provide the extra '%s'",
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
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.
518 class RequiresPythonCandidate(Candidate
):
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
)
526 version_info
= sys
.version_info
[:3]
527 self
._version
= Version(".".join(str(c
) for c
in version_info
))
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.
533 def __str__(self
) -> str:
534 return f
"Python {self._version}"
537 def project_name(self
) -> NormalizedName
:
538 return REQUIRES_PYTHON_IDENTIFIER
541 def name(self
) -> str:
542 return REQUIRES_PYTHON_IDENTIFIER
545 def version(self
) -> CandidateVersion
:
548 def format_for_error(self
) -> str:
549 return f
"Python {self.version}"
551 def iter_dependencies(self
, with_requires
: bool) -> Iterable
[Optional
[Requirement
]]:
554 def get_install_requirement(self
) -> Optional
[InstallRequirement
]: