]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_internal/resolution/resolvelib/candidates.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _internal / resolution / resolvelib / candidates.py
1 import logging
2 import sys
3 from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast
4
5 from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
6 from pip._vendor.packaging.version import Version
7
8 from pip._internal.exceptions import (
9 HashError,
10 InstallationSubprocessError,
11 MetadataInconsistent,
12 )
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,
19 )
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
23
24 from .base import Candidate, CandidateVersion, Requirement, format_name
25
26 if TYPE_CHECKING:
27 from .factory import Factory
28
29 logger = logging.getLogger(__name__)
30
31 BaseCandidate = Union[
32 "AlreadyInstalledCandidate",
33 "EditableCandidate",
34 "LinkCandidate",
35 ]
36
37 # Avoid conflicting with the PyPI package "Python".
38 REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")
39
40
41 def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
42 """The runtime version of BaseCandidate."""
43 base_candidate_classes = (
44 AlreadyInstalledCandidate,
45 EditableCandidate,
46 LinkCandidate,
47 )
48 if isinstance(candidate, base_candidate_classes):
49 return candidate
50 return None
51
52
53 def make_install_req_from_link(
54 link: Link, template: InstallRequirement
55 ) -> InstallRequirement:
56 assert not template.editable, "template is editable"
57 if template.req:
58 line = str(template.req)
59 else:
60 line = link.url
61 ireq = install_req_from_line(
62 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,
71 )
72 ireq.original_link = template.original_link
73 ireq.link = link
74 ireq.extras = template.extras
75 return ireq
76
77
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(
83 link.url,
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,
93 )
94 ireq.extras = template.extras
95 return ireq
96
97
98 def _make_install_req_from_dist(
99 dist: BaseDistribution, template: InstallRequirement
100 ) -> InstallRequirement:
101 if template.req:
102 line = str(template.req)
103 elif template.link:
104 line = f"{dist.canonical_name} @ {template.link.url}"
105 else:
106 line = f"{dist.canonical_name}=={dist.version}"
107 ireq = install_req_from_line(
108 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,
117 )
118 ireq.satisfied_by = dist
119 return ireq
120
121
122 class _InstallRequirementBackedCandidate(Candidate):
123 """A candidate backed by an ``InstallRequirement``.
124
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.
129
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).
136 """
137
138 dist: BaseDistribution
139 is_installed = False
140
141 def __init__(
142 self,
143 link: Link,
144 source_link: Link,
145 ireq: InstallRequirement,
146 factory: "Factory",
147 name: Optional[NormalizedName] = None,
148 version: Optional[CandidateVersion] = None,
149 ) -> None:
150 self._link = link
151 self._source_link = source_link
152 self._factory = factory
153 self._ireq = ireq
154 self._name = name
155 self._version = version
156 self.dist = self._prepare()
157
158 def __str__(self) -> str:
159 return f"{self.name} {self.version}"
160
161 def __repr__(self) -> str:
162 return "{class_name}({link!r})".format(
163 class_name=self.__class__.__name__,
164 link=str(self._link),
165 )
166
167 def __hash__(self) -> int:
168 return hash((self.__class__, self._link))
169
170 def __eq__(self, other: Any) -> bool:
171 if isinstance(other, self.__class__):
172 return links_equivalent(self._link, other._link)
173 return False
174
175 @property
176 def source_link(self) -> Optional[Link]:
177 return self._source_link
178
179 @property
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
184 return self._name
185
186 @property
187 def name(self) -> str:
188 return self.project_name
189
190 @property
191 def version(self) -> CandidateVersion:
192 if self._version is None:
193 self._version = self.dist.version
194 return self._version
195
196 def format_for_error(self) -> str:
197 return "{} {} (from {})".format(
198 self.name,
199 self.version,
200 self._link.file_path if self._link.is_file else self._link,
201 )
202
203 def _prepare_distribution(self) -> BaseDistribution:
204 raise NotImplementedError("Override in subclass")
205
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(
210 self._ireq,
211 "name",
212 self._name,
213 dist.canonical_name,
214 )
215 if self._version is not None and self._version != dist.version:
216 raise MetadataInconsistent(
217 self._ireq,
218 "version",
219 str(self._version),
220 str(dist.version),
221 )
222
223 def _prepare(self) -> BaseDistribution:
224 try:
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.
230 e.req = self._ireq
231 raise
232 except InstallationSubprocessError as exc:
233 # The output has been presented already, so don't duplicate it.
234 exc.context = "See above for output."
235 raise
236
237 self._check_metadata_consistency(dist)
238 return dist
239
240 def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
241 requires = self.dist.iter_dependencies() if with_requires else ()
242 for r in requires:
243 yield self._factory.make_requirement_from_spec(str(r), self._ireq)
244 yield self._factory.make_requires_python_requirement(self.dist.requires_python)
245
246 def get_install_requirement(self) -> Optional[InstallRequirement]:
247 return self._ireq
248
249
250 class LinkCandidate(_InstallRequirementBackedCandidate):
251 is_editable = False
252
253 def __init__(
254 self,
255 link: Link,
256 template: InstallRequirement,
257 factory: "Factory",
258 name: Optional[NormalizedName] = None,
259 version: Optional[CandidateVersion] = None,
260 ) -> None:
261 source_link = link
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
277 )
278
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
286 else:
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
291 )
292
293 super().__init__(
294 link=link,
295 source_link=source_link,
296 ireq=ireq,
297 factory=factory,
298 name=name,
299 version=version,
300 )
301
302 def _prepare_distribution(self) -> BaseDistribution:
303 preparer = self._factory.preparer
304 return preparer.prepare_linked_requirement(self._ireq, parallel_builds=True)
305
306
307 class EditableCandidate(_InstallRequirementBackedCandidate):
308 is_editable = True
309
310 def __init__(
311 self,
312 link: Link,
313 template: InstallRequirement,
314 factory: "Factory",
315 name: Optional[NormalizedName] = None,
316 version: Optional[CandidateVersion] = None,
317 ) -> None:
318 super().__init__(
319 link=link,
320 source_link=link,
321 ireq=make_install_req_from_editable(link, template),
322 factory=factory,
323 name=name,
324 version=version,
325 )
326
327 def _prepare_distribution(self) -> BaseDistribution:
328 return self._factory.preparer.prepare_editable_requirement(self._ireq)
329
330
331 class AlreadyInstalledCandidate(Candidate):
332 is_installed = True
333 source_link = None
334
335 def __init__(
336 self,
337 dist: BaseDistribution,
338 template: InstallRequirement,
339 factory: "Factory",
340 ) -> None:
341 self.dist = dist
342 self._ireq = _make_install_req_from_dist(dist, template)
343 self._factory = factory
344 self._version = None
345
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)
352
353 def __str__(self) -> str:
354 return str(self.dist)
355
356 def __repr__(self) -> str:
357 return "{class_name}({distribution!r})".format(
358 class_name=self.__class__.__name__,
359 distribution=self.dist,
360 )
361
362 def __hash__(self) -> int:
363 return hash((self.__class__, self.name, self.version))
364
365 def __eq__(self, other: Any) -> bool:
366 if isinstance(other, self.__class__):
367 return self.name == other.name and self.version == other.version
368 return False
369
370 @property
371 def project_name(self) -> NormalizedName:
372 return self.dist.canonical_name
373
374 @property
375 def name(self) -> str:
376 return self.project_name
377
378 @property
379 def version(self) -> CandidateVersion:
380 if self._version is None:
381 self._version = self.dist.version
382 return self._version
383
384 @property
385 def is_editable(self) -> bool:
386 return self.dist.editable
387
388 def format_for_error(self) -> str:
389 return f"{self.name} {self.version} (Installed)"
390
391 def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
392 if not with_requires:
393 return
394 for r in self.dist.iter_dependencies():
395 yield self._factory.make_requirement_from_spec(str(r), self._ireq)
396
397 def get_install_requirement(self) -> Optional[InstallRequirement]:
398 return None
399
400
401 class ExtrasCandidate(Candidate):
402 """A candidate that has 'extras', indicating additional dependencies.
403
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.
408
409 The ExtrasCandidate differs from the base in the following ways:
410
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.
419
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.
424 """
425
426 def __init__(
427 self,
428 base: BaseCandidate,
429 extras: FrozenSet[str],
430 ) -> None:
431 self.base = base
432 self.extras = extras
433
434 def __str__(self) -> str:
435 name, rest = str(self.base).split(" ", 1)
436 return "{}[{}] {}".format(name, ",".join(self.extras), rest)
437
438 def __repr__(self) -> str:
439 return "{class_name}(base={base!r}, extras={extras!r})".format(
440 class_name=self.__class__.__name__,
441 base=self.base,
442 extras=self.extras,
443 )
444
445 def __hash__(self) -> int:
446 return hash((self.base, self.extras))
447
448 def __eq__(self, other: Any) -> bool:
449 if isinstance(other, self.__class__):
450 return self.base == other.base and self.extras == other.extras
451 return False
452
453 @property
454 def project_name(self) -> NormalizedName:
455 return self.base.project_name
456
457 @property
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)
461
462 @property
463 def version(self) -> CandidateVersion:
464 return self.base.version
465
466 def format_for_error(self) -> str:
467 return "{} [{}]".format(
468 self.base.format_for_error(), ", ".join(sorted(self.extras))
469 )
470
471 @property
472 def is_installed(self) -> bool:
473 return self.base.is_installed
474
475 @property
476 def is_editable(self) -> bool:
477 return self.base.is_editable
478
479 @property
480 def source_link(self) -> Optional[Link]:
481 return self.base.source_link
482
483 def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
484 factory = self.base._factory
485
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:
490 return
491
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):
497 logger.warning(
498 "%s %s does not provide the extra '%s'",
499 self.base.name,
500 self.version,
501 extra,
502 )
503
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
507 )
508 if requirement:
509 yield requirement
510
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.
515 return None
516
517
518 class RequiresPythonCandidate(Candidate):
519 is_installed = False
520 source_link = None
521
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)
525 else:
526 version_info = sys.version_info[:3]
527 self._version = Version(".".join(str(c) for c in version_info))
528
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.
532
533 def __str__(self) -> str:
534 return f"Python {self._version}"
535
536 @property
537 def project_name(self) -> NormalizedName:
538 return REQUIRES_PYTHON_IDENTIFIER
539
540 @property
541 def name(self) -> str:
542 return REQUIRES_PYTHON_IDENTIFIER
543
544 @property
545 def version(self) -> CandidateVersion:
546 return self._version
547
548 def format_for_error(self) -> str:
549 return f"Python {self.version}"
550
551 def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
552 return ()
553
554 def get_install_requirement(self) -> Optional[InstallRequirement]:
555 return None