Voice et bot modif
This commit is contained in:
parent
189d56026b
commit
7333a22bcd
10774 changed files with 634644 additions and 933308 deletions
Binary file not shown.
Binary file not shown.
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Callable, Optional
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_set import RequirementSet
|
||||
|
|
@ -10,11 +10,11 @@ InstallRequirementProvider = Callable[
|
|||
|
||||
class BaseResolver:
|
||||
def resolve(
|
||||
self, root_reqs: list[InstallRequirement], check_supported_wheels: bool
|
||||
self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
|
||||
) -> RequirementSet:
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_installation_order(
|
||||
self, req_set: RequirementSet
|
||||
) -> list[InstallRequirement]:
|
||||
) -> List[InstallRequirement]:
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -10,14 +10,14 @@ for sub-dependencies
|
|||
a. "first found, wins" (where the order is breadth first)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from collections.abc import Iterable
|
||||
from itertools import chain
|
||||
from typing import Optional
|
||||
from typing import DefaultDict, Iterable, List, Optional, Set, Tuple
|
||||
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
|
|
@ -52,12 +52,12 @@ from pip._internal.utils.packaging import check_requires_python
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DiscoveredDependencies = defaultdict[Optional[str], list[InstallRequirement]]
|
||||
DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]]
|
||||
|
||||
|
||||
def _check_dist_requires_python(
|
||||
dist: BaseDistribution,
|
||||
version_info: tuple[int, int, int],
|
||||
version_info: Tuple[int, int, int],
|
||||
ignore_requires_python: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -104,8 +104,9 @@ def _check_dist_requires_python(
|
|||
return
|
||||
|
||||
raise UnsupportedPythonVersion(
|
||||
f"Package {dist.raw_name!r} requires a different Python: "
|
||||
f"{version} not in {requires_python!r}"
|
||||
"Package {!r} requires a different Python: {} not in {!r}".format(
|
||||
dist.raw_name, version, requires_python
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -120,7 +121,7 @@ class Resolver(BaseResolver):
|
|||
self,
|
||||
preparer: RequirementPreparer,
|
||||
finder: PackageFinder,
|
||||
wheel_cache: WheelCache | None,
|
||||
wheel_cache: Optional[WheelCache],
|
||||
make_install_req: InstallRequirementProvider,
|
||||
use_user_site: bool,
|
||||
ignore_dependencies: bool,
|
||||
|
|
@ -128,7 +129,7 @@ class Resolver(BaseResolver):
|
|||
ignore_requires_python: bool,
|
||||
force_reinstall: bool,
|
||||
upgrade_strategy: str,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
assert upgrade_strategy in self._allowed_strategies
|
||||
|
|
@ -155,7 +156,7 @@ class Resolver(BaseResolver):
|
|||
self._discovered_dependencies: DiscoveredDependencies = defaultdict(list)
|
||||
|
||||
def resolve(
|
||||
self, root_reqs: list[InstallRequirement], check_supported_wheels: bool
|
||||
self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
|
||||
) -> RequirementSet:
|
||||
"""Resolve what operations need to be done
|
||||
|
||||
|
|
@ -177,7 +178,7 @@ class Resolver(BaseResolver):
|
|||
# exceptions cannot be checked ahead of time, because
|
||||
# _populate_link() needs to be called before we can make decisions
|
||||
# based on link type.
|
||||
discovered_reqs: list[InstallRequirement] = []
|
||||
discovered_reqs: List[InstallRequirement] = []
|
||||
hash_errors = HashErrors()
|
||||
for req in chain(requirement_set.all_requirements, discovered_reqs):
|
||||
try:
|
||||
|
|
@ -195,9 +196,9 @@ class Resolver(BaseResolver):
|
|||
self,
|
||||
requirement_set: RequirementSet,
|
||||
install_req: InstallRequirement,
|
||||
parent_req_name: str | None = None,
|
||||
extras_requested: Iterable[str] | None = None,
|
||||
) -> tuple[list[InstallRequirement], InstallRequirement | None]:
|
||||
parent_req_name: Optional[str] = None,
|
||||
extras_requested: Optional[Iterable[str]] = None,
|
||||
) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
|
||||
"""Add install_req as a requirement to install.
|
||||
|
||||
:param parent_req_name: The name of the requirement that needed this
|
||||
|
|
@ -245,9 +246,9 @@ class Resolver(BaseResolver):
|
|||
return [install_req], None
|
||||
|
||||
try:
|
||||
existing_req: InstallRequirement | None = requirement_set.get_requirement(
|
||||
install_req.name
|
||||
)
|
||||
existing_req: Optional[
|
||||
InstallRequirement
|
||||
] = requirement_set.get_requirement(install_req.name)
|
||||
except KeyError:
|
||||
existing_req = None
|
||||
|
||||
|
|
@ -262,8 +263,9 @@ class Resolver(BaseResolver):
|
|||
)
|
||||
if has_conflicting_requirement:
|
||||
raise InstallationError(
|
||||
f"Double requirement given: {install_req} "
|
||||
f"(already in {existing_req}, name={install_req.name!r})"
|
||||
"Double requirement given: {} (already in {}, name={!r})".format(
|
||||
install_req, existing_req, install_req.name
|
||||
)
|
||||
)
|
||||
|
||||
# When no existing requirement exists, add the requirement as a
|
||||
|
|
@ -321,12 +323,13 @@ class Resolver(BaseResolver):
|
|||
"""
|
||||
# Don't uninstall the conflict if doing a user install and the
|
||||
# conflict is not a user install.
|
||||
assert req.satisfied_by is not None
|
||||
if not self.use_user_site or req.satisfied_by.in_usersite:
|
||||
req.should_reinstall = True
|
||||
req.satisfied_by = None
|
||||
|
||||
def _check_skip_installed(self, req_to_install: InstallRequirement) -> str | None:
|
||||
def _check_skip_installed(
|
||||
self, req_to_install: InstallRequirement
|
||||
) -> Optional[str]:
|
||||
"""Check if req_to_install should be skipped.
|
||||
|
||||
This will check if the req is installed, and whether we should upgrade
|
||||
|
|
@ -378,7 +381,7 @@ class Resolver(BaseResolver):
|
|||
self._set_req_to_reinstall(req_to_install)
|
||||
return None
|
||||
|
||||
def _find_requirement_link(self, req: InstallRequirement) -> Link | None:
|
||||
def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]:
|
||||
upgrade = self._is_upgrade_allowed(req)
|
||||
best_candidate = self.finder.find_requirement(req, upgrade)
|
||||
if not best_candidate:
|
||||
|
|
@ -418,8 +421,6 @@ class Resolver(BaseResolver):
|
|||
|
||||
if self.wheel_cache is None or self.preparer.require_hashes:
|
||||
return
|
||||
|
||||
assert req.link is not None, "_find_requirement_link unexpectedly returned None"
|
||||
cache_entry = self.wheel_cache.get_cache_entry(
|
||||
link=req.link,
|
||||
package_name=req.name,
|
||||
|
|
@ -489,7 +490,7 @@ class Resolver(BaseResolver):
|
|||
self,
|
||||
requirement_set: RequirementSet,
|
||||
req_to_install: InstallRequirement,
|
||||
) -> list[InstallRequirement]:
|
||||
) -> List[InstallRequirement]:
|
||||
"""Prepare a single requirements file.
|
||||
|
||||
:return: A list of additional InstallRequirements to also install.
|
||||
|
|
@ -512,7 +513,7 @@ class Resolver(BaseResolver):
|
|||
ignore_requires_python=self.ignore_requires_python,
|
||||
)
|
||||
|
||||
more_reqs: list[InstallRequirement] = []
|
||||
more_reqs: List[InstallRequirement] = []
|
||||
|
||||
def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None:
|
||||
# This idiosyncratically converts the Requirement to str and let
|
||||
|
|
@ -533,7 +534,6 @@ class Resolver(BaseResolver):
|
|||
with indent_log():
|
||||
# We add req_to_install before its dependencies, so that we
|
||||
# can refer to it when adding dependencies.
|
||||
assert req_to_install.name is not None
|
||||
if not requirement_set.has_requirement(req_to_install.name):
|
||||
# 'unnamed' requirements will get added here
|
||||
# 'unnamed' requirements can only come from being directly
|
||||
|
|
@ -570,7 +570,7 @@ class Resolver(BaseResolver):
|
|||
|
||||
def get_installation_order(
|
||||
self, req_set: RequirementSet
|
||||
) -> list[InstallRequirement]:
|
||||
) -> List[InstallRequirement]:
|
||||
"""Create the installation order.
|
||||
|
||||
The installation order is topological - requirements are installed
|
||||
|
|
@ -581,7 +581,7 @@ class Resolver(BaseResolver):
|
|||
# installs the user specified things in the order given, except when
|
||||
# dependencies must come earlier to achieve topological order.
|
||||
order = []
|
||||
ordered_reqs: set[InstallRequirement] = set()
|
||||
ordered_reqs: Set[InstallRequirement] = set()
|
||||
|
||||
def schedule(req: InstallRequirement) -> None:
|
||||
if req.satisfied_by or req in ordered_reqs:
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,46 +1,45 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from typing import FrozenSet, Iterable, Optional, Tuple, Union
|
||||
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.packaging.version import Version
|
||||
from pip._vendor.packaging.version import LegacyVersion, Version
|
||||
|
||||
from pip._internal.models.link import Link, links_equivalent
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
|
||||
CandidateLookup = tuple[Optional["Candidate"], Optional[InstallRequirement]]
|
||||
CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]]
|
||||
CandidateVersion = Union[LegacyVersion, Version]
|
||||
|
||||
|
||||
def format_name(project: NormalizedName, extras: frozenset[NormalizedName]) -> str:
|
||||
def format_name(project: NormalizedName, extras: FrozenSet[NormalizedName]) -> str:
|
||||
if not extras:
|
||||
return project
|
||||
extras_expr = ",".join(sorted(extras))
|
||||
return f"{project}[{extras_expr}]"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Constraint:
|
||||
specifier: SpecifierSet
|
||||
hashes: Hashes
|
||||
links: frozenset[Link]
|
||||
def __init__(
|
||||
self, specifier: SpecifierSet, hashes: Hashes, links: FrozenSet[Link]
|
||||
) -> None:
|
||||
self.specifier = specifier
|
||||
self.hashes = hashes
|
||||
self.links = links
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> Constraint:
|
||||
def empty(cls) -> "Constraint":
|
||||
return Constraint(SpecifierSet(), Hashes(), frozenset())
|
||||
|
||||
@classmethod
|
||||
def from_ireq(cls, ireq: InstallRequirement) -> Constraint:
|
||||
def from_ireq(cls, ireq: InstallRequirement) -> "Constraint":
|
||||
links = frozenset([ireq.link]) if ireq.link else frozenset()
|
||||
return Constraint(ireq.specifier, ireq.hashes(trust_internet=False), links)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.specifier) or bool(self.hashes) or bool(self.links)
|
||||
|
||||
def __and__(self, other: InstallRequirement) -> Constraint:
|
||||
def __and__(self, other: InstallRequirement) -> "Constraint":
|
||||
if not isinstance(other, InstallRequirement):
|
||||
return NotImplemented
|
||||
specifier = self.specifier & other.specifier
|
||||
|
|
@ -50,7 +49,7 @@ class Constraint:
|
|||
links = links.union([other.link])
|
||||
return Constraint(specifier, hashes, links)
|
||||
|
||||
def is_satisfied_by(self, candidate: Candidate) -> bool:
|
||||
def is_satisfied_by(self, candidate: "Candidate") -> bool:
|
||||
# Reject if there are any mismatched URL constraints on this package.
|
||||
if self.links and not all(_match_link(link, candidate) for link in self.links):
|
||||
return False
|
||||
|
|
@ -80,7 +79,7 @@ class Requirement:
|
|||
"""
|
||||
raise NotImplementedError("Subclass should override")
|
||||
|
||||
def is_satisfied_by(self, candidate: Candidate) -> bool:
|
||||
def is_satisfied_by(self, candidate: "Candidate") -> bool:
|
||||
return False
|
||||
|
||||
def get_candidate_lookup(self) -> CandidateLookup:
|
||||
|
|
@ -90,7 +89,7 @@ class Requirement:
|
|||
raise NotImplementedError("Subclass should override")
|
||||
|
||||
|
||||
def _match_link(link: Link, candidate: Candidate) -> bool:
|
||||
def _match_link(link: Link, candidate: "Candidate") -> bool:
|
||||
if candidate.source_link:
|
||||
return links_equivalent(link, candidate.source_link)
|
||||
return False
|
||||
|
|
@ -117,7 +116,7 @@ class Candidate:
|
|||
raise NotImplementedError("Override in subclass")
|
||||
|
||||
@property
|
||||
def version(self) -> Version:
|
||||
def version(self) -> CandidateVersion:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
|
||||
@property
|
||||
|
|
@ -129,13 +128,13 @@ class Candidate:
|
|||
raise NotImplementedError("Override in subclass")
|
||||
|
||||
@property
|
||||
def source_link(self) -> Link | None:
|
||||
def source_link(self) -> Optional[Link]:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Any, Union, cast
|
||||
from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast
|
||||
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
from pip._internal.exceptions import (
|
||||
FailedToPrepareCandidate,
|
||||
HashError,
|
||||
InstallationSubprocessError,
|
||||
InvalidInstalledPackage,
|
||||
MetadataInconsistent,
|
||||
MetadataInvalid,
|
||||
)
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.models.link import Link, links_equivalent
|
||||
|
|
@ -28,7 +21,7 @@ from pip._internal.req.req_install import InstallRequirement
|
|||
from pip._internal.utils.direct_url_helpers import direct_url_from_link
|
||||
from pip._internal.utils.misc import normalize_version_info
|
||||
|
||||
from .base import Candidate, Requirement, format_name
|
||||
from .base import Candidate, CandidateVersion, Requirement, format_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .factory import Factory
|
||||
|
|
@ -45,7 +38,7 @@ BaseCandidate = Union[
|
|||
REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")
|
||||
|
||||
|
||||
def as_base_candidate(candidate: Candidate) -> BaseCandidate | None:
|
||||
def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
|
||||
"""The runtime version of BaseCandidate."""
|
||||
base_candidate_classes = (
|
||||
AlreadyInstalledCandidate,
|
||||
|
|
@ -69,8 +62,10 @@ def make_install_req_from_link(
|
|||
line,
|
||||
user_supplied=template.user_supplied,
|
||||
comes_from=template.comes_from,
|
||||
use_pep517=template.use_pep517,
|
||||
isolated=template.isolated,
|
||||
constraint=template.constraint,
|
||||
global_options=template.global_options,
|
||||
hash_options=template.hash_options,
|
||||
config_settings=template.config_settings,
|
||||
)
|
||||
|
|
@ -84,17 +79,15 @@ def make_install_req_from_editable(
|
|||
link: Link, template: InstallRequirement
|
||||
) -> InstallRequirement:
|
||||
assert template.editable, "template not editable"
|
||||
if template.name:
|
||||
req_string = f"{template.name} @ {link.url}"
|
||||
else:
|
||||
req_string = link.url
|
||||
ireq = install_req_from_editable(
|
||||
req_string,
|
||||
link.url,
|
||||
user_supplied=template.user_supplied,
|
||||
comes_from=template.comes_from,
|
||||
use_pep517=template.use_pep517,
|
||||
isolated=template.isolated,
|
||||
constraint=template.constraint,
|
||||
permit_editable_wheels=template.permit_editable_wheels,
|
||||
global_options=template.global_options,
|
||||
hash_options=template.hash_options,
|
||||
config_settings=template.config_settings,
|
||||
)
|
||||
|
|
@ -115,8 +108,10 @@ def _make_install_req_from_dist(
|
|||
line,
|
||||
user_supplied=template.user_supplied,
|
||||
comes_from=template.comes_from,
|
||||
use_pep517=template.use_pep517,
|
||||
isolated=template.isolated,
|
||||
constraint=template.constraint,
|
||||
global_options=template.global_options,
|
||||
hash_options=template.hash_options,
|
||||
config_settings=template.config_settings,
|
||||
)
|
||||
|
|
@ -148,9 +143,9 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
link: Link,
|
||||
source_link: Link,
|
||||
ireq: InstallRequirement,
|
||||
factory: Factory,
|
||||
name: NormalizedName | None = None,
|
||||
version: Version | None = None,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
) -> None:
|
||||
self._link = link
|
||||
self._source_link = source_link
|
||||
|
|
@ -159,7 +154,6 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
self._name = name
|
||||
self._version = version
|
||||
self.dist = self._prepare()
|
||||
self._hash: int | None = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name} {self.version}"
|
||||
|
|
@ -168,11 +162,7 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
return f"{self.__class__.__name__}({str(self._link)!r})"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
if self._hash is not None:
|
||||
return self._hash
|
||||
|
||||
self._hash = hash((self.__class__, self._link))
|
||||
return self._hash
|
||||
return hash((self.__class__, self._link))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
|
|
@ -180,7 +170,7 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
return False
|
||||
|
||||
@property
|
||||
def source_link(self) -> Link | None:
|
||||
def source_link(self) -> Optional[Link]:
|
||||
return self._source_link
|
||||
|
||||
@property
|
||||
|
|
@ -195,15 +185,16 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
return self.project_name
|
||||
|
||||
@property
|
||||
def version(self) -> Version:
|
||||
def version(self) -> CandidateVersion:
|
||||
if self._version is None:
|
||||
self._version = self.dist.version
|
||||
return self._version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return (
|
||||
f"{self.name} {self.version} "
|
||||
f"(from {self._link.file_path if self._link.is_file else self._link})"
|
||||
return "{} {} (from {})".format(
|
||||
self.name,
|
||||
self.version,
|
||||
self._link.file_path if self._link.is_file else self._link,
|
||||
)
|
||||
|
||||
def _prepare_distribution(self) -> BaseDistribution:
|
||||
|
|
@ -225,13 +216,6 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
str(self._version),
|
||||
str(dist.version),
|
||||
)
|
||||
# check dependencies are valid
|
||||
# TODO performance: this means we iterate the dependencies at least twice,
|
||||
# we may want to cache parsed Requires-Dist
|
||||
try:
|
||||
list(dist.iter_dependencies(list(dist.iter_provided_extras())))
|
||||
except InvalidRequirement as e:
|
||||
raise MetadataInvalid(self._ireq, str(e))
|
||||
|
||||
def _prepare(self) -> BaseDistribution:
|
||||
try:
|
||||
|
|
@ -243,32 +227,20 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
e.req = self._ireq
|
||||
raise
|
||||
except InstallationSubprocessError as exc:
|
||||
if isinstance(self._ireq.comes_from, InstallRequirement):
|
||||
request_chain = self._ireq.comes_from.from_path()
|
||||
else:
|
||||
request_chain = self._ireq.comes_from
|
||||
|
||||
if request_chain is None:
|
||||
request_chain = "directly requested"
|
||||
|
||||
raise FailedToPrepareCandidate(
|
||||
package_name=self._ireq.name or str(self._link),
|
||||
requirement_chain=request_chain,
|
||||
failed_step=exc.command_description,
|
||||
)
|
||||
# The output has been presented already, so don't duplicate it.
|
||||
exc.context = "See above for output."
|
||||
raise
|
||||
|
||||
self._check_metadata_consistency(dist)
|
||||
return dist
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
# Emit the Requires-Python requirement first to fail fast on
|
||||
# unsupported candidates and avoid pointless downloads/preparation.
|
||||
yield self._factory.make_requires_python_requirement(self.dist.requires_python)
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
requires = self.dist.iter_dependencies() if with_requires else ()
|
||||
for r in requires:
|
||||
yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
|
||||
yield self._factory.make_requires_python_requirement(self.dist.requires_python)
|
||||
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
return self._ireq
|
||||
|
||||
|
||||
|
|
@ -279,9 +251,9 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
|
|||
self,
|
||||
link: Link,
|
||||
template: InstallRequirement,
|
||||
factory: Factory,
|
||||
name: NormalizedName | None = None,
|
||||
version: Version | None = None,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
) -> None:
|
||||
source_link = link
|
||||
cache_entry = factory.get_wheel_cache_entry(source_link, name)
|
||||
|
|
@ -292,14 +264,14 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
|
|||
assert ireq.link == link
|
||||
if ireq.link.is_wheel and not ireq.link.is_file:
|
||||
wheel = Wheel(ireq.link.filename)
|
||||
wheel_name = wheel.name
|
||||
wheel_name = canonicalize_name(wheel.name)
|
||||
assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel"
|
||||
# Version may not be present for PEP 508 direct URLs
|
||||
if version is not None:
|
||||
wheel_version = Version(wheel.version)
|
||||
assert (
|
||||
version == wheel_version
|
||||
), f"{version!r} != {wheel_version!r} for wheel {name}"
|
||||
assert version == wheel_version, "{!r} != {!r} for wheel {}".format(
|
||||
version, wheel_version, name
|
||||
)
|
||||
|
||||
if cache_entry is not None:
|
||||
assert ireq.link.is_wheel
|
||||
|
|
@ -336,9 +308,9 @@ class EditableCandidate(_InstallRequirementBackedCandidate):
|
|||
self,
|
||||
link: Link,
|
||||
template: InstallRequirement,
|
||||
factory: Factory,
|
||||
name: NormalizedName | None = None,
|
||||
version: Version | None = None,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
link=link,
|
||||
|
|
@ -361,7 +333,7 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
self,
|
||||
dist: BaseDistribution,
|
||||
template: InstallRequirement,
|
||||
factory: Factory,
|
||||
factory: "Factory",
|
||||
) -> None:
|
||||
self.dist = dist
|
||||
self._ireq = _make_install_req_from_dist(dist, template)
|
||||
|
|
@ -381,13 +353,13 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.dist!r})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, AlreadyInstalledCandidate):
|
||||
return NotImplemented
|
||||
return self.name == other.name and self.version == other.version
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.name, self.version))
|
||||
return hash((self.__class__, self.name, self.version))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
return self.name == other.name and self.version == other.version
|
||||
return False
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
|
|
@ -398,7 +370,7 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
return self.project_name
|
||||
|
||||
@property
|
||||
def version(self) -> Version:
|
||||
def version(self) -> CandidateVersion:
|
||||
if self._version is None:
|
||||
self._version = self.dist.version
|
||||
return self._version
|
||||
|
|
@ -410,17 +382,13 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
def format_for_error(self) -> str:
|
||||
return f"{self.name} {self.version} (Installed)"
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
if not with_requires:
|
||||
return
|
||||
for r in self.dist.iter_dependencies():
|
||||
yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
|
||||
|
||||
try:
|
||||
for r in self.dist.iter_dependencies():
|
||||
yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
|
||||
except InvalidRequirement as exc:
|
||||
raise InvalidInstalledPackage(dist=self.dist, invalid_exc=exc) from None
|
||||
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -452,9 +420,9 @@ class ExtrasCandidate(Candidate):
|
|||
def __init__(
|
||||
self,
|
||||
base: BaseCandidate,
|
||||
extras: frozenset[str],
|
||||
extras: FrozenSet[str],
|
||||
*,
|
||||
comes_from: InstallRequirement | None = None,
|
||||
comes_from: Optional[InstallRequirement] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param comes_from: the InstallRequirement that led to this candidate if it
|
||||
|
|
@ -466,6 +434,14 @@ class ExtrasCandidate(Candidate):
|
|||
"""
|
||||
self.base = base
|
||||
self.extras = frozenset(canonicalize_name(e) for e in extras)
|
||||
# If any extras are requested in their non-normalized forms, keep track
|
||||
# of their raw values. This is needed when we look up dependencies
|
||||
# since PEP 685 has not been implemented for marker-matching, and using
|
||||
# the non-normalized extra for lookup ensures the user can select a
|
||||
# non-normalized extra in a package with its non-normalized form.
|
||||
# TODO: Remove this attribute when packaging is upgraded to support the
|
||||
# marker comparison logic specified in PEP 685.
|
||||
self._unnormalized_extras = extras.difference(self.extras)
|
||||
self._comes_from = comes_from if comes_from is not None else self.base._ireq
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
@ -493,7 +469,7 @@ class ExtrasCandidate(Candidate):
|
|||
return format_name(self.base.project_name, self.extras)
|
||||
|
||||
@property
|
||||
def version(self) -> Version:
|
||||
def version(self) -> CandidateVersion:
|
||||
return self.base.version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
|
|
@ -510,10 +486,54 @@ class ExtrasCandidate(Candidate):
|
|||
return self.base.is_editable
|
||||
|
||||
@property
|
||||
def source_link(self) -> Link | None:
|
||||
def source_link(self) -> Optional[Link]:
|
||||
return self.base.source_link
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
def _warn_invalid_extras(
|
||||
self,
|
||||
requested: FrozenSet[str],
|
||||
valid: FrozenSet[str],
|
||||
) -> None:
|
||||
"""Emit warnings for invalid extras being requested.
|
||||
|
||||
This emits a warning for each requested extra that is not in the
|
||||
candidate's ``Provides-Extra`` list.
|
||||
"""
|
||||
invalid_extras_to_warn = frozenset(
|
||||
extra
|
||||
for extra in requested
|
||||
if extra not in valid
|
||||
# If an extra is requested in an unnormalized form, skip warning
|
||||
# about the normalized form being missing.
|
||||
and extra in self.extras
|
||||
)
|
||||
if not invalid_extras_to_warn:
|
||||
return
|
||||
for extra in sorted(invalid_extras_to_warn):
|
||||
logger.warning(
|
||||
"%s %s does not provide the extra '%s'",
|
||||
self.base.name,
|
||||
self.version,
|
||||
extra,
|
||||
)
|
||||
|
||||
def _calculate_valid_requested_extras(self) -> FrozenSet[str]:
|
||||
"""Get a list of valid extras requested by this candidate.
|
||||
|
||||
The user (or upstream dependant) may have specified extras that the
|
||||
candidate doesn't support. Any unsupported extras are dropped, and each
|
||||
cause a warning to be logged here.
|
||||
"""
|
||||
requested_extras = self.extras.union(self._unnormalized_extras)
|
||||
valid_extras = frozenset(
|
||||
extra
|
||||
for extra in requested_extras
|
||||
if self.base.dist.is_extra_provided(extra)
|
||||
)
|
||||
self._warn_invalid_extras(requested_extras, valid_extras)
|
||||
return valid_extras
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
factory = self.base._factory
|
||||
|
||||
# Add a dependency on the exact base
|
||||
|
|
@ -522,18 +542,7 @@ class ExtrasCandidate(Candidate):
|
|||
if not with_requires:
|
||||
return
|
||||
|
||||
# The user may have specified extras that the candidate doesn't
|
||||
# support. We ignore any unsupported extras here.
|
||||
valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras())
|
||||
invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras())
|
||||
for extra in sorted(invalid_extras):
|
||||
logger.warning(
|
||||
"%s %s does not provide the extra '%s'",
|
||||
self.base.name,
|
||||
self.version,
|
||||
extra,
|
||||
)
|
||||
|
||||
valid_extras = self._calculate_valid_requested_extras()
|
||||
for r in self.base.dist.iter_dependencies(valid_extras):
|
||||
yield from factory.make_requirements_from_spec(
|
||||
str(r),
|
||||
|
|
@ -541,7 +550,7 @@ class ExtrasCandidate(Candidate):
|
|||
valid_extras,
|
||||
)
|
||||
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
# We don't return anything here, because we always
|
||||
# depend on the base candidate, and we'll get the
|
||||
# install requirement from that.
|
||||
|
|
@ -552,7 +561,7 @@ class RequiresPythonCandidate(Candidate):
|
|||
is_installed = False
|
||||
source_link = None
|
||||
|
||||
def __init__(self, py_version_info: tuple[int, ...] | None) -> None:
|
||||
def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None:
|
||||
if py_version_info is not None:
|
||||
version_info = normalize_version_info(py_version_info)
|
||||
else:
|
||||
|
|
@ -566,9 +575,6 @@ class RequiresPythonCandidate(Candidate):
|
|||
def __str__(self) -> str:
|
||||
return f"Python {self._version}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self._version!r})"
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
return REQUIRES_PYTHON_IDENTIFIER
|
||||
|
|
@ -578,14 +584,14 @@ class RequiresPythonCandidate(Candidate):
|
|||
return REQUIRES_PYTHON_IDENTIFIER
|
||||
|
||||
@property
|
||||
def version(self) -> Version:
|
||||
def version(self) -> CandidateVersion:
|
||||
return self._version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return f"Python {self.version}"
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
return ()
|
||||
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import logging
|
||||
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
FrozenSet,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Protocol,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
cast,
|
||||
)
|
||||
|
|
@ -16,16 +21,13 @@ from typing import (
|
|||
from pip._vendor.packaging.requirements import InvalidRequirement
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.version import InvalidVersion, Version
|
||||
from pip._vendor.resolvelib import ResolutionImpossible
|
||||
|
||||
from pip._internal.cache import CacheEntry, WheelCache
|
||||
from pip._internal.exceptions import (
|
||||
DistributionNotFound,
|
||||
InstallationError,
|
||||
InvalidInstalledPackage,
|
||||
MetadataInconsistent,
|
||||
MetadataInvalid,
|
||||
UnsupportedPythonVersion,
|
||||
UnsupportedWheel,
|
||||
)
|
||||
|
|
@ -48,7 +50,7 @@ from pip._internal.utils.hashes import Hashes
|
|||
from pip._internal.utils.packaging import get_requirement
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
||||
from .base import Candidate, Constraint, Requirement
|
||||
from .base import Candidate, CandidateVersion, Constraint, Requirement
|
||||
from .candidates import (
|
||||
AlreadyInstalledCandidate,
|
||||
BaseCandidate,
|
||||
|
|
@ -68,6 +70,7 @@ from .requirements import (
|
|||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
|
||||
class ConflictCause(Protocol):
|
||||
requirement: RequiresPythonRequirement
|
||||
|
|
@ -77,13 +80,13 @@ if TYPE_CHECKING:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
C = TypeVar("C")
|
||||
Cache = dict[Link, C]
|
||||
Cache = Dict[Link, C]
|
||||
|
||||
|
||||
class CollectedRootRequirements(NamedTuple):
|
||||
requirements: list[Requirement]
|
||||
constraints: dict[str, Constraint]
|
||||
user_requested: dict[str, int]
|
||||
requirements: List[Requirement]
|
||||
constraints: Dict[str, Constraint]
|
||||
user_requested: Dict[str, int]
|
||||
|
||||
|
||||
class Factory:
|
||||
|
|
@ -92,12 +95,12 @@ class Factory:
|
|||
finder: PackageFinder,
|
||||
preparer: RequirementPreparer,
|
||||
make_install_req: InstallRequirementProvider,
|
||||
wheel_cache: WheelCache | None,
|
||||
wheel_cache: Optional[WheelCache],
|
||||
use_user_site: bool,
|
||||
force_reinstall: bool,
|
||||
ignore_installed: bool,
|
||||
ignore_requires_python: bool,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
) -> None:
|
||||
self._finder = finder
|
||||
self.preparer = preparer
|
||||
|
|
@ -111,11 +114,10 @@ class Factory:
|
|||
self._build_failures: Cache[InstallationError] = {}
|
||||
self._link_candidate_cache: Cache[LinkCandidate] = {}
|
||||
self._editable_candidate_cache: Cache[EditableCandidate] = {}
|
||||
self._installed_candidate_cache: dict[str, AlreadyInstalledCandidate] = {}
|
||||
self._extras_candidate_cache: dict[
|
||||
tuple[int, frozenset[NormalizedName]], ExtrasCandidate
|
||||
self._installed_candidate_cache: Dict[str, AlreadyInstalledCandidate] = {}
|
||||
self._extras_candidate_cache: Dict[
|
||||
Tuple[int, FrozenSet[NormalizedName]], ExtrasCandidate
|
||||
] = {}
|
||||
self._supported_tags_cache = get_supported()
|
||||
|
||||
if not ignore_installed:
|
||||
env = get_default_environment()
|
||||
|
|
@ -142,9 +144,9 @@ class Factory:
|
|||
def _make_extras_candidate(
|
||||
self,
|
||||
base: BaseCandidate,
|
||||
extras: frozenset[str],
|
||||
extras: FrozenSet[str],
|
||||
*,
|
||||
comes_from: InstallRequirement | None = None,
|
||||
comes_from: Optional[InstallRequirement] = None,
|
||||
) -> ExtrasCandidate:
|
||||
cache_key = (id(base), frozenset(canonicalize_name(e) for e in extras))
|
||||
try:
|
||||
|
|
@ -157,7 +159,7 @@ class Factory:
|
|||
def _make_candidate_from_dist(
|
||||
self,
|
||||
dist: BaseDistribution,
|
||||
extras: frozenset[str],
|
||||
extras: FrozenSet[str],
|
||||
template: InstallRequirement,
|
||||
) -> Candidate:
|
||||
try:
|
||||
|
|
@ -172,12 +174,12 @@ class Factory:
|
|||
def _make_candidate_from_link(
|
||||
self,
|
||||
link: Link,
|
||||
extras: frozenset[str],
|
||||
extras: FrozenSet[str],
|
||||
template: InstallRequirement,
|
||||
name: NormalizedName | None,
|
||||
version: Version | None,
|
||||
) -> Candidate | None:
|
||||
base: BaseCandidate | None = self._make_base_candidate_from_link(
|
||||
name: Optional[NormalizedName],
|
||||
version: Optional[CandidateVersion],
|
||||
) -> Optional[Candidate]:
|
||||
base: Optional[BaseCandidate] = self._make_base_candidate_from_link(
|
||||
link, template, name, version
|
||||
)
|
||||
if not extras or base is None:
|
||||
|
|
@ -188,9 +190,9 @@ class Factory:
|
|||
self,
|
||||
link: Link,
|
||||
template: InstallRequirement,
|
||||
name: NormalizedName | None,
|
||||
version: Version | None,
|
||||
) -> BaseCandidate | None:
|
||||
name: Optional[NormalizedName],
|
||||
version: Optional[CandidateVersion],
|
||||
) -> Optional[BaseCandidate]:
|
||||
# TODO: Check already installed candidate, and use it if the link and
|
||||
# editable flag match.
|
||||
|
||||
|
|
@ -209,7 +211,7 @@ class Factory:
|
|||
name=name,
|
||||
version=version,
|
||||
)
|
||||
except (MetadataInconsistent, MetadataInvalid) as e:
|
||||
except MetadataInconsistent as e:
|
||||
logger.info(
|
||||
"Discarding [blue underline]%s[/]: [yellow]%s[reset]",
|
||||
link,
|
||||
|
|
@ -247,7 +249,7 @@ class Factory:
|
|||
specifier: SpecifierSet,
|
||||
hashes: Hashes,
|
||||
prefers_installed: bool,
|
||||
incompatible_ids: set[int],
|
||||
incompatible_ids: Set[int],
|
||||
) -> Iterable[Candidate]:
|
||||
if not ireqs:
|
||||
return ()
|
||||
|
|
@ -260,14 +262,14 @@ class Factory:
|
|||
assert template.req, "Candidates found on index must be PEP 508"
|
||||
name = canonicalize_name(template.req.name)
|
||||
|
||||
extras: frozenset[str] = frozenset()
|
||||
extras: FrozenSet[str] = frozenset()
|
||||
for ireq in ireqs:
|
||||
assert ireq.req, "Candidates found on index must be PEP 508"
|
||||
specifier &= ireq.req.specifier
|
||||
hashes &= ireq.hashes(trust_internet=False)
|
||||
extras |= frozenset(ireq.extras)
|
||||
|
||||
def _get_installed_candidate() -> Candidate | None:
|
||||
def _get_installed_candidate() -> Optional[Candidate]:
|
||||
"""Get the candidate for the currently-installed version."""
|
||||
# If --force-reinstall is set, we want the version from the index
|
||||
# instead, so we "pretend" there is nothing installed.
|
||||
|
|
@ -277,15 +279,10 @@ class Factory:
|
|||
installed_dist = self._installed_dists[name]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Don't use the installed distribution if its version
|
||||
# does not fit the current dependency graph.
|
||||
if not specifier.contains(installed_dist.version, prereleases=True):
|
||||
return None
|
||||
except InvalidVersion as e:
|
||||
raise InvalidInstalledPackage(dist=installed_dist, invalid_exc=e)
|
||||
|
||||
# Don't use the installed distribution if its version does not fit
|
||||
# the current dependency graph.
|
||||
if not specifier.contains(installed_dist.version, prereleases=True):
|
||||
return None
|
||||
candidate = self._make_candidate_from_dist(
|
||||
dist=installed_dist,
|
||||
extras=extras,
|
||||
|
|
@ -302,7 +299,7 @@ class Factory:
|
|||
specifier=specifier,
|
||||
hashes=hashes,
|
||||
)
|
||||
icans = result.applicable_candidates
|
||||
icans = list(result.iter_applicable())
|
||||
|
||||
# PEP 592: Yanked releases are ignored unless the specifier
|
||||
# explicitly pins a version (via '==' or '===') that can be
|
||||
|
|
@ -346,7 +343,7 @@ class Factory:
|
|||
def _iter_explicit_candidates_from_base(
|
||||
self,
|
||||
base_requirements: Iterable[Requirement],
|
||||
extras: frozenset[str],
|
||||
extras: FrozenSet[str],
|
||||
) -> Iterator[Candidate]:
|
||||
"""Produce explicit candidates from the base given an extra-ed package.
|
||||
|
||||
|
|
@ -394,11 +391,10 @@ class Factory:
|
|||
incompatibilities: Mapping[str, Iterator[Candidate]],
|
||||
constraint: Constraint,
|
||||
prefers_installed: bool,
|
||||
is_satisfied_by: Callable[[Requirement, Candidate], bool],
|
||||
) -> Iterable[Candidate]:
|
||||
# Collect basic lookup information from the requirements.
|
||||
explicit_candidates: set[Candidate] = set()
|
||||
ireqs: list[InstallRequirement] = []
|
||||
explicit_candidates: Set[Candidate] = set()
|
||||
ireqs: List[InstallRequirement] = []
|
||||
for req in requirements[identifier]:
|
||||
cand, ireq = req.get_candidate_lookup()
|
||||
if cand is not None:
|
||||
|
|
@ -460,7 +456,7 @@ class Factory:
|
|||
for c in explicit_candidates
|
||||
if id(c) not in incompat_ids
|
||||
and constraint.is_satisfied_by(c)
|
||||
and all(is_satisfied_by(req, c) for req in requirements[identifier])
|
||||
and all(req.is_satisfied_by(c) for req in requirements[identifier])
|
||||
)
|
||||
|
||||
def _make_requirements_from_install_req(
|
||||
|
|
@ -517,7 +513,7 @@ class Factory:
|
|||
)
|
||||
|
||||
def collect_root_requirements(
|
||||
self, root_ireqs: list[InstallRequirement]
|
||||
self, root_ireqs: List[InstallRequirement]
|
||||
) -> CollectedRootRequirements:
|
||||
collected = CollectedRootRequirements([], {}, {})
|
||||
for i, ireq in enumerate(root_ireqs):
|
||||
|
|
@ -566,7 +562,7 @@ class Factory:
|
|||
def make_requirements_from_spec(
|
||||
self,
|
||||
specifier: str,
|
||||
comes_from: InstallRequirement | None,
|
||||
comes_from: Optional[InstallRequirement],
|
||||
requested_extras: Iterable[str] = (),
|
||||
) -> Iterator[Requirement]:
|
||||
"""
|
||||
|
|
@ -584,7 +580,7 @@ class Factory:
|
|||
def make_requires_python_requirement(
|
||||
self,
|
||||
specifier: SpecifierSet,
|
||||
) -> Requirement | None:
|
||||
) -> Optional[Requirement]:
|
||||
if self._ignore_requires_python:
|
||||
return None
|
||||
# Don't bother creating a dependency for an empty Requires-Python.
|
||||
|
|
@ -592,7 +588,9 @@ class Factory:
|
|||
return None
|
||||
return RequiresPythonRequirement(specifier, self._python_candidate)
|
||||
|
||||
def get_wheel_cache_entry(self, link: Link, name: str | None) -> CacheEntry | None:
|
||||
def get_wheel_cache_entry(
|
||||
self, link: Link, name: Optional[str]
|
||||
) -> Optional[CacheEntry]:
|
||||
"""Look up the link in the wheel cache.
|
||||
|
||||
If ``preparer.require_hashes`` is True, don't use the wheel cache,
|
||||
|
|
@ -606,10 +604,10 @@ class Factory:
|
|||
return self._wheel_cache.get_cache_entry(
|
||||
link=link,
|
||||
package_name=name,
|
||||
supported_tags=self._supported_tags_cache,
|
||||
supported_tags=get_supported(),
|
||||
)
|
||||
|
||||
def get_dist_to_uninstall(self, candidate: Candidate) -> BaseDistribution | None:
|
||||
def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[BaseDistribution]:
|
||||
# TODO: Are there more cases this needs to return True? Editable?
|
||||
dist = self._installed_dists.get(candidate.project_name)
|
||||
if dist is None: # Not installed, no uninstallation required.
|
||||
|
|
@ -638,7 +636,7 @@ class Factory:
|
|||
return None
|
||||
|
||||
def _report_requires_python_error(
|
||||
self, causes: Sequence[ConflictCause]
|
||||
self, causes: Sequence["ConflictCause"]
|
||||
) -> UnsupportedPythonVersion:
|
||||
assert causes, "Requires-Python error reported with no cause"
|
||||
|
||||
|
|
@ -660,7 +658,7 @@ class Factory:
|
|||
return UnsupportedPythonVersion(message)
|
||||
|
||||
def _report_single_requirement_conflict(
|
||||
self, req: Requirement, parent: Candidate | None
|
||||
self, req: Requirement, parent: Optional[Candidate]
|
||||
) -> DistributionNotFound:
|
||||
if parent is None:
|
||||
req_disp = str(req)
|
||||
|
|
@ -670,8 +668,8 @@ class Factory:
|
|||
cands = self._finder.find_all_candidates(req.project_name)
|
||||
skipped_by_requires_python = self._finder.requires_python_skipped_reasons()
|
||||
|
||||
versions_set: set[Version] = set()
|
||||
yanked_versions_set: set[Version] = set()
|
||||
versions_set: Set[CandidateVersion] = set()
|
||||
yanked_versions_set: Set[CandidateVersion] = set()
|
||||
for c in cands:
|
||||
is_yanked = c.link.is_yanked if c.link else False
|
||||
if is_yanked:
|
||||
|
|
@ -711,25 +709,10 @@ class Factory:
|
|||
|
||||
return DistributionNotFound(f"No matching distribution found for {req}")
|
||||
|
||||
def _has_any_candidates(self, project_name: str) -> bool:
|
||||
"""
|
||||
Check if there are any candidates available for the project name.
|
||||
"""
|
||||
return any(
|
||||
self.find_candidates(
|
||||
project_name,
|
||||
requirements={project_name: []},
|
||||
incompatibilities={},
|
||||
constraint=Constraint.empty(),
|
||||
prefers_installed=True,
|
||||
is_satisfied_by=lambda r, c: True,
|
||||
)
|
||||
)
|
||||
|
||||
def get_installation_error(
|
||||
self,
|
||||
e: ResolutionImpossible[Requirement, Candidate],
|
||||
constraints: dict[str, Constraint],
|
||||
e: "ResolutionImpossible[Requirement, Candidate]",
|
||||
constraints: Dict[str, Constraint],
|
||||
) -> InstallationError:
|
||||
assert e.causes, "Installation error reported with no cause"
|
||||
|
||||
|
|
@ -754,7 +737,7 @@ class Factory:
|
|||
# The simplest case is when we have *one* cause that can't be
|
||||
# satisfied. We just report that case.
|
||||
if len(e.causes) == 1:
|
||||
req, parent = next(iter(e.causes))
|
||||
req, parent = e.causes[0]
|
||||
if req.name not in constraints:
|
||||
return self._report_single_requirement_conflict(req, parent)
|
||||
|
||||
|
|
@ -762,7 +745,7 @@ class Factory:
|
|||
# satisfied at once.
|
||||
|
||||
# A couple of formatting helpers
|
||||
def text_join(parts: list[str]) -> str:
|
||||
def text_join(parts: List[str]) -> str:
|
||||
if len(parts) == 1:
|
||||
return parts[0]
|
||||
|
||||
|
|
@ -811,28 +794,12 @@ class Factory:
|
|||
spec = constraints[key].specifier
|
||||
msg += f"\n The user requested (constraint) {key}{spec}"
|
||||
|
||||
# Check for causes that had no candidates
|
||||
causes = set()
|
||||
for req, _ in e.causes:
|
||||
causes.add(req.name)
|
||||
|
||||
no_candidates = {c for c in causes if not self._has_any_candidates(c)}
|
||||
if no_candidates:
|
||||
msg = (
|
||||
msg
|
||||
+ "\n\n"
|
||||
+ "Additionally, some packages in these conflicts have no "
|
||||
+ "matching distributions available for your environment:"
|
||||
+ "\n "
|
||||
+ "\n ".join(sorted(no_candidates))
|
||||
)
|
||||
|
||||
msg = (
|
||||
msg
|
||||
+ "\n\n"
|
||||
+ "To fix this you could try to:\n"
|
||||
+ "1. loosen the range of package versions you've specified\n"
|
||||
+ "2. remove package versions to allow pip to attempt to solve "
|
||||
+ "2. remove package versions to allow pip attempt to solve "
|
||||
+ "the dependency conflict\n"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,21 +8,30 @@ absolutely need, and not "download the world" when we only need one version of
|
|||
something.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Iterator, Sequence
|
||||
from typing import Any, Callable, Optional
|
||||
import functools
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Set, Tuple
|
||||
|
||||
from pip._vendor.packaging.version import _BaseVersion
|
||||
|
||||
from pip._internal.exceptions import MetadataInvalid
|
||||
|
||||
from .base import Candidate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]]
|
||||
|
||||
IndexCandidateInfo = tuple[_BaseVersion, Callable[[], Optional[Candidate]]]
|
||||
if TYPE_CHECKING:
|
||||
SequenceCandidate = Sequence[Candidate]
|
||||
else:
|
||||
# For compatibility: Python before 3.9 does not support using [] on the
|
||||
# Sequence class.
|
||||
#
|
||||
# >>> from collections.abc import Sequence
|
||||
# >>> Sequence[str]
|
||||
# Traceback (most recent call last):
|
||||
# File "<stdin>", line 1, in <module>
|
||||
# TypeError: 'ABCMeta' object is not subscriptable
|
||||
#
|
||||
# TODO: Remove this block after dropping Python 3.8 support.
|
||||
SequenceCandidate = Sequence
|
||||
|
||||
|
||||
def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
|
||||
|
|
@ -31,29 +40,15 @@ def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
|
|||
This iterator is used when the package is not already installed. Candidates
|
||||
from index come later in their normal ordering.
|
||||
"""
|
||||
versions_found: set[_BaseVersion] = set()
|
||||
versions_found: Set[_BaseVersion] = set()
|
||||
for version, func in infos:
|
||||
if version in versions_found:
|
||||
continue
|
||||
try:
|
||||
candidate = func()
|
||||
except MetadataInvalid as e:
|
||||
logger.warning(
|
||||
"Ignoring version %s of %s since it has invalid metadata:\n"
|
||||
"%s\n"
|
||||
"Please use pip<24.1 if you need to use this version.",
|
||||
version,
|
||||
e.ireq.name,
|
||||
e,
|
||||
)
|
||||
# Mark version as found to avoid trying other candidates with the same
|
||||
# version, since they most likely have invalid metadata as well.
|
||||
versions_found.add(version)
|
||||
else:
|
||||
if candidate is None:
|
||||
continue
|
||||
yield candidate
|
||||
versions_found.add(version)
|
||||
candidate = func()
|
||||
if candidate is None:
|
||||
continue
|
||||
yield candidate
|
||||
versions_found.add(version)
|
||||
|
||||
|
||||
def _iter_built_with_prepended(
|
||||
|
|
@ -67,7 +62,7 @@ def _iter_built_with_prepended(
|
|||
normal ordering, except skipped when the version is already installed.
|
||||
"""
|
||||
yield installed
|
||||
versions_found: set[_BaseVersion] = {installed.version}
|
||||
versions_found: Set[_BaseVersion] = {installed.version}
|
||||
for version, func in infos:
|
||||
if version in versions_found:
|
||||
continue
|
||||
|
|
@ -91,7 +86,7 @@ def _iter_built_with_inserted(
|
|||
the installed candidate exactly once before we start yielding older or
|
||||
equivalent candidates, or after all other candidates if they are all newer.
|
||||
"""
|
||||
versions_found: set[_BaseVersion] = set()
|
||||
versions_found: Set[_BaseVersion] = set()
|
||||
for version, func in infos:
|
||||
if version in versions_found:
|
||||
continue
|
||||
|
|
@ -110,7 +105,7 @@ def _iter_built_with_inserted(
|
|||
yield installed
|
||||
|
||||
|
||||
class FoundCandidates(Sequence[Candidate]):
|
||||
class FoundCandidates(SequenceCandidate):
|
||||
"""A lazy sequence to provide candidates to the resolver.
|
||||
|
||||
The intended usage is to return this from `find_matches()` so the resolver
|
||||
|
|
@ -122,15 +117,14 @@ class FoundCandidates(Sequence[Candidate]):
|
|||
def __init__(
|
||||
self,
|
||||
get_infos: Callable[[], Iterator[IndexCandidateInfo]],
|
||||
installed: Candidate | None,
|
||||
installed: Optional[Candidate],
|
||||
prefers_installed: bool,
|
||||
incompatible_ids: set[int],
|
||||
incompatible_ids: Set[int],
|
||||
):
|
||||
self._get_infos = get_infos
|
||||
self._installed = installed
|
||||
self._prefers_installed = prefers_installed
|
||||
self._incompatible_ids = incompatible_ids
|
||||
self._bool: bool | None = None
|
||||
|
||||
def __getitem__(self, index: Any) -> Any:
|
||||
# Implemented to satisfy the ABC check. This is not needed by the
|
||||
|
|
@ -154,13 +148,8 @@ class FoundCandidates(Sequence[Candidate]):
|
|||
# performance reasons).
|
||||
raise NotImplementedError("don't do this")
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def __bool__(self) -> bool:
|
||||
if self._bool is not None:
|
||||
return self._bool
|
||||
|
||||
if self._prefers_installed and self._installed:
|
||||
self._bool = True
|
||||
return True
|
||||
|
||||
self._bool = any(self)
|
||||
return self._bool
|
||||
return any(self)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import math
|
||||
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
||||
from functools import cache
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Mapping,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from pip._vendor.resolvelib.providers import AbstractProvider
|
||||
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
|
||||
from .base import Candidate, Constraint, Requirement
|
||||
from .candidates import REQUIRES_PYTHON_IDENTIFIER
|
||||
from .factory import Factory
|
||||
from .requirements import ExplicitRequirement
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._vendor.resolvelib.providers import Preference
|
||||
|
|
@ -54,7 +54,7 @@ def _get_with_identifier(
|
|||
mapping: Mapping[str, V],
|
||||
identifier: str,
|
||||
default: D,
|
||||
) -> D | V:
|
||||
) -> Union[D, V]:
|
||||
"""Get item from a package name lookup mapping with a resolver identifier.
|
||||
|
||||
This extra logic is needed when the target mapping is keyed by package
|
||||
|
|
@ -89,80 +89,29 @@ class PipProvider(_ProviderBase):
|
|||
def __init__(
|
||||
self,
|
||||
factory: Factory,
|
||||
constraints: dict[str, Constraint],
|
||||
constraints: Dict[str, Constraint],
|
||||
ignore_dependencies: bool,
|
||||
upgrade_strategy: str,
|
||||
user_requested: dict[str, int],
|
||||
user_requested: Dict[str, int],
|
||||
) -> None:
|
||||
self._factory = factory
|
||||
self._constraints = constraints
|
||||
self._ignore_dependencies = ignore_dependencies
|
||||
self._upgrade_strategy = upgrade_strategy
|
||||
self._user_requested = user_requested
|
||||
self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf)
|
||||
|
||||
@property
|
||||
def constraints(self) -> dict[str, Constraint]:
|
||||
"""Public view of user-specified constraints.
|
||||
|
||||
Exposes the provider's constraints mapping without encouraging
|
||||
external callers to reach into private attributes.
|
||||
"""
|
||||
return self._constraints
|
||||
|
||||
def identify(self, requirement_or_candidate: Requirement | Candidate) -> str:
|
||||
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
|
||||
return requirement_or_candidate.name
|
||||
|
||||
def narrow_requirement_selection(
|
||||
self,
|
||||
identifiers: Iterable[str],
|
||||
resolutions: Mapping[str, Candidate],
|
||||
candidates: Mapping[str, Iterator[Candidate]],
|
||||
information: Mapping[str, Iterator[PreferenceInformation]],
|
||||
backtrack_causes: Sequence[PreferenceInformation],
|
||||
) -> Iterable[str]:
|
||||
"""Produce a subset of identifiers that should be considered before others.
|
||||
|
||||
Currently pip narrows the following selection:
|
||||
* Requires-Python, if present is always returned by itself
|
||||
* Backtrack causes are considered next because they can be identified
|
||||
in linear time here, whereas because get_preference() is called
|
||||
for each identifier, it would be quadratic to check for them there.
|
||||
Further, the current backtrack causes likely need to be resolved
|
||||
before other requirements as a resolution can't be found while
|
||||
there is a conflict.
|
||||
"""
|
||||
backtrack_identifiers = set()
|
||||
for info in backtrack_causes:
|
||||
backtrack_identifiers.add(info.requirement.name)
|
||||
if info.parent is not None:
|
||||
backtrack_identifiers.add(info.parent.name)
|
||||
|
||||
current_backtrack_causes = []
|
||||
for identifier in identifiers:
|
||||
# Requires-Python has only one candidate and the check is basically
|
||||
# free, so we always do it first to avoid needless work if it fails.
|
||||
# This skips calling get_preference() for all other identifiers.
|
||||
if identifier == REQUIRES_PYTHON_IDENTIFIER:
|
||||
return [identifier]
|
||||
|
||||
# Check if this identifier is a backtrack cause
|
||||
if identifier in backtrack_identifiers:
|
||||
current_backtrack_causes.append(identifier)
|
||||
continue
|
||||
|
||||
if current_backtrack_causes:
|
||||
return current_backtrack_causes
|
||||
|
||||
return identifiers
|
||||
|
||||
def get_preference(
|
||||
self,
|
||||
identifier: str,
|
||||
resolutions: Mapping[str, Candidate],
|
||||
candidates: Mapping[str, Iterator[Candidate]],
|
||||
information: Mapping[str, Iterable[PreferenceInformation]],
|
||||
backtrack_causes: Sequence[PreferenceInformation],
|
||||
) -> Preference:
|
||||
information: Mapping[str, Iterable["PreferenceInformation"]],
|
||||
backtrack_causes: Sequence["PreferenceInformation"],
|
||||
) -> "Preference":
|
||||
"""Produce a sort key for given requirement based on preference.
|
||||
|
||||
The lower the return value is, the more preferred this group of
|
||||
|
|
@ -170,20 +119,18 @@ class PipProvider(_ProviderBase):
|
|||
|
||||
Currently pip considers the following in order:
|
||||
|
||||
* Any requirement that is "direct", e.g., points to an explicit URL.
|
||||
* Any requirement that is "pinned", i.e., contains the operator ``===``
|
||||
or ``==`` without a wildcard.
|
||||
* Any requirement that imposes an upper version limit, i.e., contains the
|
||||
operator ``<``, ``<=``, ``~=``, or ``==`` with a wildcard. Because
|
||||
pip prioritizes the latest version, preferring explicit upper bounds
|
||||
can rule out infeasible candidates sooner. This does not imply that
|
||||
upper bounds are good practice; they can make dependency management
|
||||
and resolution harder.
|
||||
* Order user-specified requirements as they are specified, placing
|
||||
other requirements afterward.
|
||||
* Any "non-free" requirement, i.e., one that contains at least one
|
||||
operator, such as ``>=`` or ``!=``.
|
||||
* Alphabetical order for consistency (aids debuggability).
|
||||
* Prefer if any of the known requirements is "direct", e.g. points to an
|
||||
explicit URL.
|
||||
* If equal, prefer if any requirement is "pinned", i.e. contains
|
||||
operator ``===`` or ``==``.
|
||||
* If equal, calculate an approximate "depth" and resolve requirements
|
||||
closer to the user-specified requirements first. If the depth cannot
|
||||
by determined (eg: due to no matching parents), it is considered
|
||||
infinite.
|
||||
* Order user-specified requirements by the order they are specified.
|
||||
* If equal, prefers "non-free" requirements, i.e. contains at least one
|
||||
operator, such as ``>=`` or ``<``.
|
||||
* If equal, order alphabetically for consistency (helps debuggability).
|
||||
"""
|
||||
try:
|
||||
next(iter(information[identifier]))
|
||||
|
|
@ -194,39 +141,55 @@ class PipProvider(_ProviderBase):
|
|||
else:
|
||||
has_information = True
|
||||
|
||||
if not has_information:
|
||||
direct = False
|
||||
ireqs: tuple[InstallRequirement | None, ...] = ()
|
||||
if has_information:
|
||||
lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
|
||||
candidate, ireqs = zip(*lookups)
|
||||
else:
|
||||
# Go through the information and for each requirement,
|
||||
# check if it's explicit (e.g., a direct link) and get the
|
||||
# InstallRequirement (the second element) from get_candidate_lookup()
|
||||
directs, ireqs = zip(
|
||||
*(
|
||||
(isinstance(r, ExplicitRequirement), r.get_candidate_lookup()[1])
|
||||
for r, _ in information[identifier]
|
||||
)
|
||||
)
|
||||
direct = any(directs)
|
||||
candidate, ireqs = None, ()
|
||||
|
||||
operators: list[tuple[str, str]] = [
|
||||
(specifier.operator, specifier.version)
|
||||
operators = [
|
||||
specifier.operator
|
||||
for specifier_set in (ireq.specifier for ireq in ireqs if ireq)
|
||||
for specifier in specifier_set
|
||||
]
|
||||
|
||||
pinned = any(((op[:2] == "==") and ("*" not in ver)) for op, ver in operators)
|
||||
upper_bounded = any(
|
||||
((op in ("<", "<=", "~=")) or (op == "==" and "*" in ver))
|
||||
for op, ver in operators
|
||||
)
|
||||
direct = candidate is not None
|
||||
pinned = any(op[:2] == "==" for op in operators)
|
||||
unfree = bool(operators)
|
||||
|
||||
try:
|
||||
requested_order: Union[int, float] = self._user_requested[identifier]
|
||||
except KeyError:
|
||||
requested_order = math.inf
|
||||
if has_information:
|
||||
parent_depths = (
|
||||
self._known_depths[parent.name] if parent is not None else 0.0
|
||||
for _, parent in information[identifier]
|
||||
)
|
||||
inferred_depth = min(d for d in parent_depths) + 1.0
|
||||
else:
|
||||
inferred_depth = math.inf
|
||||
else:
|
||||
inferred_depth = 1.0
|
||||
self._known_depths[identifier] = inferred_depth
|
||||
|
||||
requested_order = self._user_requested.get(identifier, math.inf)
|
||||
|
||||
# Requires-Python has only one candidate and the check is basically
|
||||
# free, so we always do it first to avoid needless work if it fails.
|
||||
requires_python = identifier == REQUIRES_PYTHON_IDENTIFIER
|
||||
|
||||
# Prefer the causes of backtracking on the assumption that the problem
|
||||
# resolving the dependency tree is related to the failures that caused
|
||||
# the backtracking
|
||||
backtrack_cause = self.is_backtrack_cause(identifier, backtrack_causes)
|
||||
|
||||
return (
|
||||
not requires_python,
|
||||
not direct,
|
||||
not pinned,
|
||||
not upper_bounded,
|
||||
not backtrack_cause,
|
||||
inferred_depth,
|
||||
requested_order,
|
||||
not unfree,
|
||||
identifier,
|
||||
|
|
@ -271,15 +234,22 @@ class PipProvider(_ProviderBase):
|
|||
constraint=constraint,
|
||||
prefers_installed=(not _eligible_for_upgrade(identifier)),
|
||||
incompatibilities=incompatibilities,
|
||||
is_satisfied_by=self.is_satisfied_by,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@cache
|
||||
def is_satisfied_by(requirement: Requirement, candidate: Candidate) -> bool:
|
||||
def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool:
|
||||
return requirement.is_satisfied_by(candidate)
|
||||
|
||||
def get_dependencies(self, candidate: Candidate) -> Iterable[Requirement]:
|
||||
def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]:
|
||||
with_requires = not self._ignore_dependencies
|
||||
# iter_dependencies() can perform nontrivial work so delay until needed.
|
||||
return (r for r in candidate.iter_dependencies(with_requires) if r is not None)
|
||||
return [r for r in candidate.iter_dependencies(with_requires) if r is not None]
|
||||
|
||||
@staticmethod
|
||||
def is_backtrack_cause(
|
||||
identifier: str, backtrack_causes: Sequence["PreferenceInformation"]
|
||||
) -> bool:
|
||||
for backtrack_cause in backtrack_causes:
|
||||
if identifier == backtrack_cause.requirement.name:
|
||||
return True
|
||||
if backtrack_cause.parent and identifier == backtrack_cause.parent.name:
|
||||
return True
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -1,21 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from collections.abc import Mapping
|
||||
from logging import getLogger
|
||||
from typing import Any
|
||||
from typing import Any, DefaultDict
|
||||
|
||||
from pip._vendor.resolvelib.reporters import BaseReporter
|
||||
|
||||
from .base import Candidate, Constraint, Requirement
|
||||
from .base import Candidate, Requirement
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class PipReporter(BaseReporter[Requirement, Candidate, str]):
|
||||
def __init__(self, constraints: Mapping[str, Constraint] | None = None) -> None:
|
||||
self.reject_count_by_package: defaultdict[str, int] = defaultdict(int)
|
||||
self._constraints = constraints or {}
|
||||
class PipReporter(BaseReporter):
|
||||
def __init__(self) -> None:
|
||||
self.reject_count_by_package: DefaultDict[str, int] = defaultdict(int)
|
||||
|
||||
self._messages_at_reject_count = {
|
||||
1: (
|
||||
|
|
@ -37,40 +33,29 @@ class PipReporter(BaseReporter[Requirement, Candidate, str]):
|
|||
}
|
||||
|
||||
def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
|
||||
"""Report a candidate being rejected.
|
||||
|
||||
Logs both the rejection count message (if applicable) and details about
|
||||
the requirements and constraints that caused the rejection.
|
||||
"""
|
||||
self.reject_count_by_package[candidate.name] += 1
|
||||
|
||||
count = self.reject_count_by_package[candidate.name]
|
||||
if count in self._messages_at_reject_count:
|
||||
message = self._messages_at_reject_count[count]
|
||||
logger.info("INFO: %s", message.format(package_name=candidate.name))
|
||||
if count not in self._messages_at_reject_count:
|
||||
return
|
||||
|
||||
message = self._messages_at_reject_count[count]
|
||||
logger.info("INFO: %s", message.format(package_name=candidate.name))
|
||||
|
||||
msg = "Will try a different candidate, due to conflict:"
|
||||
for req_info in criterion.information:
|
||||
req, parent = req_info.requirement, req_info.parent
|
||||
# Inspired by Factory.get_installation_error
|
||||
msg += "\n "
|
||||
if parent:
|
||||
msg += f"{parent.name} {parent.version} depends on "
|
||||
else:
|
||||
msg += "The user requested "
|
||||
msg += req.format_for_error()
|
||||
|
||||
# Add any relevant constraints
|
||||
if self._constraints:
|
||||
name = candidate.name
|
||||
constraint = self._constraints.get(name)
|
||||
if constraint and constraint.specifier:
|
||||
constraint_text = f"{name}{constraint.specifier}"
|
||||
msg += f"\n The user requested (constraint) {constraint_text}"
|
||||
|
||||
logger.debug(msg)
|
||||
|
||||
|
||||
class PipDebuggingReporter(BaseReporter[Requirement, Candidate, str]):
|
||||
class PipDebuggingReporter(BaseReporter):
|
||||
"""A reporter that does an info log for every event it sees."""
|
||||
|
||||
def starting(self) -> None:
|
||||
|
|
@ -81,14 +66,11 @@ class PipDebuggingReporter(BaseReporter[Requirement, Candidate, str]):
|
|||
|
||||
def ending_round(self, index: int, state: Any) -> None:
|
||||
logger.info("Reporter.ending_round(%r, state)", index)
|
||||
logger.debug("Reporter.ending_round(%r, %r)", index, state)
|
||||
|
||||
def ending(self, state: Any) -> None:
|
||||
logger.info("Reporter.ending(%r)", state)
|
||||
|
||||
def adding_requirement(
|
||||
self, requirement: Requirement, parent: Candidate | None
|
||||
) -> None:
|
||||
def adding_requirement(self, requirement: Requirement, parent: Candidate) -> None:
|
||||
logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent)
|
||||
|
||||
def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
|
||||
|
|
@ -21,14 +17,6 @@ class ExplicitRequirement(Requirement):
|
|||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.candidate!r})"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.candidate)
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, ExplicitRequirement):
|
||||
return False
|
||||
return self.candidate == other.candidate
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
# No need to canonicalize - the candidate did this
|
||||
|
|
@ -53,36 +41,14 @@ class SpecifierRequirement(Requirement):
|
|||
def __init__(self, ireq: InstallRequirement) -> None:
|
||||
assert ireq.link is None, "This is a link, not a specifier"
|
||||
self._ireq = ireq
|
||||
self._equal_cache: str | None = None
|
||||
self._hash: int | None = None
|
||||
self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
|
||||
|
||||
@property
|
||||
def _equal(self) -> str:
|
||||
if self._equal_cache is not None:
|
||||
return self._equal_cache
|
||||
|
||||
self._equal_cache = str(self._ireq)
|
||||
return self._equal_cache
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self._ireq.req)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({str(self._ireq.req)!r})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, SpecifierRequirement):
|
||||
return NotImplemented
|
||||
return self._equal == other._equal
|
||||
|
||||
def __hash__(self) -> int:
|
||||
if self._hash is not None:
|
||||
return self._hash
|
||||
|
||||
self._hash = hash(self._equal)
|
||||
return self._hash
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
|
||||
|
|
@ -130,38 +96,14 @@ class SpecifierWithoutExtrasRequirement(SpecifierRequirement):
|
|||
def __init__(self, ireq: InstallRequirement) -> None:
|
||||
assert ireq.link is None, "This is a link, not a specifier"
|
||||
self._ireq = install_req_drop_extras(ireq)
|
||||
self._equal_cache: str | None = None
|
||||
self._hash: int | None = None
|
||||
self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
|
||||
|
||||
@property
|
||||
def _equal(self) -> str:
|
||||
if self._equal_cache is not None:
|
||||
return self._equal_cache
|
||||
|
||||
self._equal_cache = str(self._ireq)
|
||||
return self._equal_cache
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, SpecifierWithoutExtrasRequirement):
|
||||
return NotImplemented
|
||||
return self._equal == other._equal
|
||||
|
||||
def __hash__(self) -> int:
|
||||
if self._hash is not None:
|
||||
return self._hash
|
||||
|
||||
self._hash = hash(self._equal)
|
||||
return self._hash
|
||||
|
||||
|
||||
class RequiresPythonRequirement(Requirement):
|
||||
"""A requirement representing Requires-Python metadata."""
|
||||
|
||||
def __init__(self, specifier: SpecifierSet, match: Candidate) -> None:
|
||||
self.specifier = specifier
|
||||
self._specifier_string = str(specifier) # for faster __eq__
|
||||
self._hash: int | None = None
|
||||
self._candidate = match
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
@ -170,21 +112,6 @@ class RequiresPythonRequirement(Requirement):
|
|||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({str(self.specifier)!r})"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
if self._hash is not None:
|
||||
return self._hash
|
||||
|
||||
self._hash = hash((self._specifier_string, self._candidate))
|
||||
return self._hash
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, RequiresPythonRequirement):
|
||||
return False
|
||||
return (
|
||||
self._specifier_string == other._specifier_string
|
||||
and self._candidate == other._candidate
|
||||
)
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
return self._candidate.project_name
|
||||
|
|
@ -221,14 +148,6 @@ class UnsatisfiableRequirement(Requirement):
|
|||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({str(self._name)!r})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, UnsatisfiableRequirement):
|
||||
return NotImplemented
|
||||
return self._name == other._name
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._name)
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
return self._name
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, cast
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible, ResolutionTooDeep
|
||||
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible
|
||||
from pip._vendor.resolvelib import Resolver as RLResolver
|
||||
from pip._vendor.resolvelib.structs import DirectedGraph
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.exceptions import ResolutionTooDeepError
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.operations.prepare import RequirementPreparer
|
||||
from pip._internal.req.constructors import install_req_extend_extras
|
||||
|
|
@ -45,7 +42,7 @@ class Resolver(BaseResolver):
|
|||
self,
|
||||
preparer: RequirementPreparer,
|
||||
finder: PackageFinder,
|
||||
wheel_cache: WheelCache | None,
|
||||
wheel_cache: Optional[WheelCache],
|
||||
make_install_req: InstallRequirementProvider,
|
||||
use_user_site: bool,
|
||||
ignore_dependencies: bool,
|
||||
|
|
@ -53,7 +50,7 @@ class Resolver(BaseResolver):
|
|||
ignore_requires_python: bool,
|
||||
force_reinstall: bool,
|
||||
upgrade_strategy: str,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
):
|
||||
super().__init__()
|
||||
assert upgrade_strategy in self._allowed_strategies
|
||||
|
|
@ -71,10 +68,10 @@ class Resolver(BaseResolver):
|
|||
)
|
||||
self.ignore_dependencies = ignore_dependencies
|
||||
self.upgrade_strategy = upgrade_strategy
|
||||
self._result: Result | None = None
|
||||
self._result: Optional[Result] = None
|
||||
|
||||
def resolve(
|
||||
self, root_reqs: list[InstallRequirement], check_supported_wheels: bool
|
||||
self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
|
||||
) -> RequirementSet:
|
||||
collected = self.factory.collect_root_requirements(root_reqs)
|
||||
provider = PipProvider(
|
||||
|
|
@ -85,10 +82,9 @@ class Resolver(BaseResolver):
|
|||
user_requested=collected.user_requested,
|
||||
)
|
||||
if "PIP_RESOLVER_DEBUG" in os.environ:
|
||||
reporter: BaseReporter[Requirement, Candidate, str] = PipDebuggingReporter()
|
||||
reporter: BaseReporter = PipDebuggingReporter()
|
||||
else:
|
||||
reporter = PipReporter(constraints=provider.constraints)
|
||||
|
||||
reporter = PipReporter()
|
||||
resolver: RLResolver[Requirement, Candidate, str] = RLResolver(
|
||||
provider,
|
||||
reporter,
|
||||
|
|
@ -106,8 +102,6 @@ class Resolver(BaseResolver):
|
|||
collected.constraints,
|
||||
)
|
||||
raise error from e
|
||||
except ResolutionTooDeep:
|
||||
raise ResolutionTooDeepError from None
|
||||
|
||||
req_set = RequirementSet(check_supported_wheels=check_supported_wheels)
|
||||
# process candidates with extras last to ensure their base equivalent is
|
||||
|
|
@ -181,11 +175,16 @@ class Resolver(BaseResolver):
|
|||
|
||||
req_set.add_named_requirement(ireq)
|
||||
|
||||
reqs = req_set.all_requirements
|
||||
self.factory.preparer.prepare_linked_requirements_more(reqs)
|
||||
for req in reqs:
|
||||
req.prepared = True
|
||||
req.needs_more_preparation = False
|
||||
return req_set
|
||||
|
||||
def get_installation_order(
|
||||
self, req_set: RequirementSet
|
||||
) -> list[InstallRequirement]:
|
||||
) -> List[InstallRequirement]:
|
||||
"""Get order for installation of requirements in RequirementSet.
|
||||
|
||||
The returned list contains a requirement before another that depends on
|
||||
|
|
@ -216,8 +215,8 @@ class Resolver(BaseResolver):
|
|||
|
||||
|
||||
def get_topological_weights(
|
||||
graph: DirectedGraph[str | None], requirement_keys: set[str]
|
||||
) -> dict[str | None, int]:
|
||||
graph: "DirectedGraph[Optional[str]]", requirement_keys: Set[str]
|
||||
) -> Dict[Optional[str], int]:
|
||||
"""Assign weights to each node based on how "deep" they are.
|
||||
|
||||
This implementation may change at any point in the future without prior
|
||||
|
|
@ -243,25 +242,14 @@ def get_topological_weights(
|
|||
We are only interested in the weights of packages that are in the
|
||||
requirement_keys.
|
||||
"""
|
||||
path: set[str | None] = set()
|
||||
weights: dict[str | None, list[int]] = {}
|
||||
path: Set[Optional[str]] = set()
|
||||
weights: Dict[Optional[str], int] = {}
|
||||
|
||||
def visit(node: str | None) -> None:
|
||||
def visit(node: Optional[str]) -> None:
|
||||
if node in path:
|
||||
# We hit a cycle, so we'll break it here.
|
||||
return
|
||||
|
||||
# The walk is exponential and for pathologically connected graphs (which
|
||||
# are the ones most likely to contain cycles in the first place) it can
|
||||
# take until the heat-death of the universe. To counter this we limit
|
||||
# the number of attempts to visit (i.e. traverse through) any given
|
||||
# node. We choose a value here which gives decent enough coverage for
|
||||
# fairly well behaved graphs, and still limits the walk complexity to be
|
||||
# linear in nature.
|
||||
cur_weights = weights.get(node, [])
|
||||
if len(cur_weights) >= 5:
|
||||
return
|
||||
|
||||
# Time to visit the children!
|
||||
path.add(node)
|
||||
for child in graph.iter_children(node):
|
||||
|
|
@ -271,14 +259,14 @@ def get_topological_weights(
|
|||
if node not in requirement_keys:
|
||||
return
|
||||
|
||||
cur_weights.append(len(path))
|
||||
weights[node] = cur_weights
|
||||
last_known_parent_count = weights.get(node, 0)
|
||||
weights[node] = max(last_known_parent_count, len(path))
|
||||
|
||||
# Simplify the graph, pruning leaves that have no dependencies. This is
|
||||
# needed for large graphs (say over 200 packages) because the `visit`
|
||||
# function is slower for large/densely connected graphs, taking minutes.
|
||||
# Simplify the graph, pruning leaves that have no dependencies.
|
||||
# This is needed for large graphs (say over 200 packages) because the
|
||||
# `visit` function is exponentially slower then, taking minutes.
|
||||
# See https://github.com/pypa/pip/issues/10557
|
||||
# We repeat the pruning step until we have no more leaves to remove.
|
||||
# We will loop until we explicitly break the loop.
|
||||
while True:
|
||||
leaves = set()
|
||||
for key in graph:
|
||||
|
|
@ -298,13 +286,12 @@ def get_topological_weights(
|
|||
for leaf in leaves:
|
||||
if leaf not in requirement_keys:
|
||||
continue
|
||||
weights[leaf] = [weight]
|
||||
weights[leaf] = weight
|
||||
# Remove the leaves from the graph, making it simpler.
|
||||
for leaf in leaves:
|
||||
graph.remove(leaf)
|
||||
|
||||
# Visit the remaining graph, this will only have nodes to handle if the
|
||||
# graph had a cycle in it, which the pruning step above could not handle.
|
||||
# Visit the remaining graph.
|
||||
# `None` is guaranteed to be the root node by resolvelib.
|
||||
visit(None)
|
||||
|
||||
|
|
@ -313,15 +300,13 @@ def get_topological_weights(
|
|||
difference = set(weights.keys()).difference(requirement_keys)
|
||||
assert not difference, difference
|
||||
|
||||
# Now give back all the weights, choosing the largest ones from what we
|
||||
# accumulated.
|
||||
return {node: max(wgts) for (node, wgts) in weights.items()}
|
||||
return weights
|
||||
|
||||
|
||||
def _req_set_item_sorter(
|
||||
item: tuple[str, InstallRequirement],
|
||||
weights: dict[str | None, int],
|
||||
) -> tuple[int, str]:
|
||||
item: Tuple[str, InstallRequirement],
|
||||
weights: Dict[Optional[str], int],
|
||||
) -> Tuple[int, str]:
|
||||
"""Key function used to sort install requirements for installation.
|
||||
|
||||
Based on the "weight" mapping calculated in ``get_installation_order()``.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue