mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-04-14 06:34:44 +00:00
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
This commit is contained in:
parent
72ad6dc953
commit
f4cd1ba0d6
813 changed files with 66015 additions and 58839 deletions
|
|
@ -1,20 +1,24 @@
|
|||
from typing import Callable, List, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_set import RequirementSet
|
||||
|
||||
InstallRequirementProvider = Callable[
|
||||
[str, Optional[InstallRequirement]], InstallRequirement
|
||||
[str, Optional[InstallRequirement]], InstallRequirement,
|
||||
]
|
||||
|
||||
|
||||
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]:
|
||||
self, req_set: RequirementSet,
|
||||
) -> list[InstallRequirement]:
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -9,42 +9,43 @@ for top-level requirements:
|
|||
for sub-dependencies
|
||||
a. "first found, wins" (where the order is breadth first)
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
from typing import DefaultDict, Iterable, List, Optional, Set, Tuple
|
||||
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from typing import DefaultDict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.exceptions import (
|
||||
BestVersionAlreadyInstalled,
|
||||
DistributionNotFound,
|
||||
HashError,
|
||||
HashErrors,
|
||||
NoneMetadataError,
|
||||
UnsupportedPythonVersion,
|
||||
)
|
||||
from pip._internal.exceptions import BestVersionAlreadyInstalled
|
||||
from pip._internal.exceptions import DistributionNotFound
|
||||
from pip._internal.exceptions import HashError
|
||||
from pip._internal.exceptions import HashErrors
|
||||
from pip._internal.exceptions import NoneMetadataError
|
||||
from pip._internal.exceptions import UnsupportedPythonVersion
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.operations.prepare import RequirementPreparer
|
||||
from pip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
check_invalid_constraint_type,
|
||||
)
|
||||
from pip._internal.req.req_install import check_invalid_constraint_type
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_set import RequirementSet
|
||||
from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
|
||||
from pip._internal.resolution.base import BaseResolver
|
||||
from pip._internal.resolution.base import InstallRequirementProvider
|
||||
from pip._internal.utils.compatibility_tags import get_supported
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import normalize_version_info
|
||||
from pip._internal.utils.packaging import check_requires_python
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ 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:
|
||||
"""
|
||||
|
|
@ -82,17 +83,17 @@ def _check_dist_requires_python(
|
|||
)
|
||||
except specifiers.InvalidSpecifier as exc:
|
||||
logger.warning(
|
||||
"Package %r has an invalid Requires-Python: %s", dist.raw_name, exc
|
||||
'Package %r has an invalid Requires-Python: %s', dist.raw_name, exc,
|
||||
)
|
||||
return
|
||||
|
||||
if is_compatible:
|
||||
return
|
||||
|
||||
version = ".".join(map(str, version_info))
|
||||
version = '.'.join(map(str, version_info))
|
||||
if ignore_requires_python:
|
||||
logger.debug(
|
||||
"Ignoring failed Requires-Python check for package %r: %s not in %r",
|
||||
'Ignoring failed Requires-Python check for package %r: %s not in %r',
|
||||
dist.raw_name,
|
||||
version,
|
||||
requires_python,
|
||||
|
|
@ -100,9 +101,9 @@ def _check_dist_requires_python(
|
|||
return
|
||||
|
||||
raise UnsupportedPythonVersion(
|
||||
"Package {!r} requires a different Python: {} not in {!r}".format(
|
||||
dist.raw_name, version, requires_python
|
||||
)
|
||||
'Package {!r} requires a different Python: {} not in {!r}'.format(
|
||||
dist.raw_name, version, requires_python,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -111,13 +112,13 @@ class Resolver(BaseResolver):
|
|||
the requested operation without breaking the requirements of any package.
|
||||
"""
|
||||
|
||||
_allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"}
|
||||
_allowed_strategies = {'eager', 'only-if-needed', 'to-satisfy-only'}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
preparer: RequirementPreparer,
|
||||
finder: PackageFinder,
|
||||
wheel_cache: Optional[WheelCache],
|
||||
wheel_cache: WheelCache | None,
|
||||
make_install_req: InstallRequirementProvider,
|
||||
use_user_site: bool,
|
||||
ignore_dependencies: bool,
|
||||
|
|
@ -125,7 +126,7 @@ class Resolver(BaseResolver):
|
|||
ignore_requires_python: bool,
|
||||
force_reinstall: bool,
|
||||
upgrade_strategy: str,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
assert upgrade_strategy in self._allowed_strategies
|
||||
|
|
@ -152,7 +153,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
|
||||
|
||||
|
|
@ -174,7 +175,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:
|
||||
|
|
@ -189,12 +190,12 @@ class Resolver(BaseResolver):
|
|||
return requirement_set
|
||||
|
||||
def _is_upgrade_allowed(self, req: InstallRequirement) -> bool:
|
||||
if self.upgrade_strategy == "to-satisfy-only":
|
||||
if self.upgrade_strategy == 'to-satisfy-only':
|
||||
return False
|
||||
elif self.upgrade_strategy == "eager":
|
||||
elif self.upgrade_strategy == 'eager':
|
||||
return True
|
||||
else:
|
||||
assert self.upgrade_strategy == "only-if-needed"
|
||||
assert self.upgrade_strategy == 'only-if-needed'
|
||||
return req.user_supplied or req.constraint
|
||||
|
||||
def _set_req_to_reinstall(self, req: InstallRequirement) -> None:
|
||||
|
|
@ -208,8 +209,8 @@ class Resolver(BaseResolver):
|
|||
req.satisfied_by = None
|
||||
|
||||
def _check_skip_installed(
|
||||
self, req_to_install: InstallRequirement
|
||||
) -> Optional[str]:
|
||||
self, req_to_install: InstallRequirement,
|
||||
) -> str | None:
|
||||
"""Check if req_to_install should be skipped.
|
||||
|
||||
This will check if the req is installed, and whether we should upgrade
|
||||
|
|
@ -239,9 +240,9 @@ class Resolver(BaseResolver):
|
|||
return None
|
||||
|
||||
if not self._is_upgrade_allowed(req_to_install):
|
||||
if self.upgrade_strategy == "only-if-needed":
|
||||
return "already satisfied, skipping upgrade"
|
||||
return "already satisfied"
|
||||
if self.upgrade_strategy == 'only-if-needed':
|
||||
return 'already satisfied, skipping upgrade'
|
||||
return 'already satisfied'
|
||||
|
||||
# Check for the possibility of an upgrade. For link-based
|
||||
# requirements we have to pull the tree down and inspect to assess
|
||||
|
|
@ -251,7 +252,7 @@ class Resolver(BaseResolver):
|
|||
self.finder.find_requirement(req_to_install, upgrade=True)
|
||||
except BestVersionAlreadyInstalled:
|
||||
# Then the best version is installed.
|
||||
return "already up-to-date"
|
||||
return 'already up-to-date'
|
||||
except DistributionNotFound:
|
||||
# No distribution found, so we squash the error. It will
|
||||
# be raised later when we re-try later to do the install.
|
||||
|
|
@ -261,7 +262,7 @@ class Resolver(BaseResolver):
|
|||
self._set_req_to_reinstall(req_to_install)
|
||||
return None
|
||||
|
||||
def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]:
|
||||
def _find_requirement_link(self, req: InstallRequirement) -> Link | None:
|
||||
upgrade = self._is_upgrade_allowed(req)
|
||||
best_candidate = self.finder.find_requirement(req, upgrade)
|
||||
if not best_candidate:
|
||||
|
|
@ -270,14 +271,14 @@ class Resolver(BaseResolver):
|
|||
# Log a warning per PEP 592 if necessary before returning.
|
||||
link = best_candidate.link
|
||||
if link.is_yanked:
|
||||
reason = link.yanked_reason or "<none given>"
|
||||
reason = link.yanked_reason or '<none given>'
|
||||
msg = (
|
||||
# Mark this as a unicode string to prevent
|
||||
# "UnicodeEncodeError: 'ascii' codec can't encode character"
|
||||
# in Python 2 when the reason contains non-ascii characters.
|
||||
"The candidate selected for download or install is a "
|
||||
"yanked version: {candidate}\n"
|
||||
"Reason for being yanked: {reason}"
|
||||
'The candidate selected for download or install is a '
|
||||
'yanked version: {candidate}\n'
|
||||
'Reason for being yanked: {reason}'
|
||||
).format(candidate=best_candidate, reason=reason)
|
||||
logger.warning(msg)
|
||||
|
||||
|
|
@ -307,7 +308,7 @@ class Resolver(BaseResolver):
|
|||
supported_tags=get_supported(),
|
||||
)
|
||||
if cache_entry is not None:
|
||||
logger.debug("Using cached wheel link: %s", cache_entry.link)
|
||||
logger.debug('Using cached wheel link: %s', cache_entry.link)
|
||||
if req.link is req.original_link and cache_entry.persistent:
|
||||
req.original_link_is_in_wheel_cache = True
|
||||
req.link = cache_entry.link
|
||||
|
|
@ -344,16 +345,16 @@ class Resolver(BaseResolver):
|
|||
|
||||
if req.satisfied_by:
|
||||
should_modify = (
|
||||
self.upgrade_strategy != "to-satisfy-only"
|
||||
or self.force_reinstall
|
||||
or self.ignore_installed
|
||||
or req.link.scheme == "file"
|
||||
self.upgrade_strategy != 'to-satisfy-only' or
|
||||
self.force_reinstall or
|
||||
self.ignore_installed or
|
||||
req.link.scheme == 'file'
|
||||
)
|
||||
if should_modify:
|
||||
self._set_req_to_reinstall(req)
|
||||
else:
|
||||
logger.info(
|
||||
"Requirement already satisfied (use --upgrade to upgrade): %s",
|
||||
'Requirement already satisfied (use --upgrade to upgrade): %s',
|
||||
req,
|
||||
)
|
||||
return dist
|
||||
|
|
@ -362,7 +363,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.
|
||||
|
|
@ -385,7 +386,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
|
||||
|
|
@ -415,11 +416,11 @@ class Resolver(BaseResolver):
|
|||
if not self.ignore_dependencies:
|
||||
if req_to_install.extras:
|
||||
logger.debug(
|
||||
"Installing extra requirements: %r",
|
||||
",".join(req_to_install.extras),
|
||||
'Installing extra requirements: %r',
|
||||
','.join(req_to_install.extras),
|
||||
)
|
||||
missing_requested = sorted(
|
||||
set(req_to_install.extras) - set(dist.iter_provided_extras())
|
||||
set(req_to_install.extras) - set(dist.iter_provided_extras()),
|
||||
)
|
||||
for missing in missing_requested:
|
||||
logger.warning(
|
||||
|
|
@ -430,7 +431,7 @@ class Resolver(BaseResolver):
|
|||
)
|
||||
|
||||
available_requested = sorted(
|
||||
set(dist.iter_provided_extras()) & set(req_to_install.extras)
|
||||
set(dist.iter_provided_extras()) & set(req_to_install.extras),
|
||||
)
|
||||
for subreq in dist.iter_dependencies(available_requested):
|
||||
add_req(subreq, extras_requested=available_requested)
|
||||
|
|
@ -438,8 +439,8 @@ class Resolver(BaseResolver):
|
|||
return more_reqs
|
||||
|
||||
def get_installation_order(
|
||||
self, req_set: RequirementSet
|
||||
) -> List[InstallRequirement]:
|
||||
self, req_set: RequirementSet,
|
||||
) -> list[InstallRequirement]:
|
||||
"""Create the installation order.
|
||||
|
||||
The installation order is topological - requirements are installed
|
||||
|
|
@ -450,7 +451,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:
|
||||
|
|
|
|||
|
|
@ -1,45 +1,53 @@
|
|||
from typing import FrozenSet, Iterable, Optional, Tuple, Union
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.version import LegacyVersion, Version
|
||||
from typing import FrozenSet
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.models.link import Link, links_equivalent
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.link import links_equivalent
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.packaging.version import LegacyVersion
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]]
|
||||
CandidateLookup = Tuple[Optional['Candidate'], Optional[InstallRequirement]]
|
||||
CandidateVersion = Union[LegacyVersion, Version]
|
||||
|
||||
|
||||
def format_name(project: str, extras: FrozenSet[str]) -> str:
|
||||
def format_name(project: str, extras: frozenset[str]) -> str:
|
||||
if not extras:
|
||||
return project
|
||||
canonical_extras = sorted(canonicalize_name(e) for e in extras)
|
||||
return "{}[{}]".format(project, ",".join(canonical_extras))
|
||||
return '{}[{}]'.format(project, ','.join(canonical_extras))
|
||||
|
||||
|
||||
class Constraint:
|
||||
def __init__(
|
||||
self, specifier: SpecifierSet, hashes: Hashes, links: FrozenSet[Link]
|
||||
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
|
||||
|
|
@ -49,7 +57,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
|
||||
|
|
@ -68,7 +76,7 @@ class Requirement:
|
|||
in which case ``name`` would contain the ``[...]`` part, while this
|
||||
refers to the name of the project.
|
||||
"""
|
||||
raise NotImplementedError("Subclass should override")
|
||||
raise NotImplementedError('Subclass should override')
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
@ -77,19 +85,19 @@ class Requirement:
|
|||
This is different from ``project_name`` if this requirement contains
|
||||
extras, where ``project_name`` would not contain the ``[...]`` part.
|
||||
"""
|
||||
raise NotImplementedError("Subclass should override")
|
||||
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:
|
||||
raise NotImplementedError("Subclass should override")
|
||||
raise NotImplementedError('Subclass should override')
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
raise NotImplementedError("Subclass should override")
|
||||
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
|
||||
|
|
@ -104,7 +112,7 @@ class Candidate:
|
|||
in which case ``name`` would contain the ``[...]`` part, while this
|
||||
refers to the name of the project.
|
||||
"""
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
@ -113,29 +121,29 @@ class Candidate:
|
|||
This is different from ``project_name`` if this candidate contains
|
||||
extras, where ``project_name`` would not contain the ``[...]`` part.
|
||||
"""
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def version(self) -> CandidateVersion:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def is_installed(self) -> bool:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def is_editable(self) -> bool:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def source_link(self) -> Optional[Link]:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
def source_link(self) -> Link | None:
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
raise NotImplementedError("Subclass should override")
|
||||
raise NotImplementedError('Subclass should override')
|
||||
|
|
|
|||
|
|
@ -1,26 +1,35 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
from typing import FrozenSet
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
from pip._internal.exceptions import (
|
||||
HashError,
|
||||
InstallationSubprocessError,
|
||||
MetadataInconsistent,
|
||||
)
|
||||
from pip._internal.exceptions import HashError
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import MetadataInconsistent
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.models.link import Link, links_equivalent
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.link import links_equivalent
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.req.constructors import (
|
||||
install_req_from_editable,
|
||||
install_req_from_line,
|
||||
)
|
||||
from pip._internal.req.constructors import install_req_from_editable
|
||||
from pip._internal.req.constructors import install_req_from_line
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.misc import normalize_version_info
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
from .base import Candidate, CandidateVersion, Requirement, format_name
|
||||
from .base import Candidate
|
||||
from .base import CandidateVersion
|
||||
from .base import format_name
|
||||
from .base import Requirement
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .factory import Factory
|
||||
|
|
@ -28,16 +37,16 @@ if TYPE_CHECKING:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
BaseCandidate = Union[
|
||||
"AlreadyInstalledCandidate",
|
||||
"EditableCandidate",
|
||||
"LinkCandidate",
|
||||
'AlreadyInstalledCandidate',
|
||||
'EditableCandidate',
|
||||
'LinkCandidate',
|
||||
]
|
||||
|
||||
# Avoid conflicting with the PyPI package "Python".
|
||||
REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")
|
||||
REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, '<Python from Requires-Python>')
|
||||
|
||||
|
||||
def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
|
||||
def as_base_candidate(candidate: Candidate) -> BaseCandidate | None:
|
||||
"""The runtime version of BaseCandidate."""
|
||||
base_candidate_classes = (
|
||||
AlreadyInstalledCandidate,
|
||||
|
|
@ -50,9 +59,9 @@ def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
|
|||
|
||||
|
||||
def make_install_req_from_link(
|
||||
link: Link, template: InstallRequirement
|
||||
link: Link, template: InstallRequirement,
|
||||
) -> InstallRequirement:
|
||||
assert not template.editable, "template is editable"
|
||||
assert not template.editable, 'template is editable'
|
||||
if template.req:
|
||||
line = str(template.req)
|
||||
else:
|
||||
|
|
@ -76,9 +85,9 @@ def make_install_req_from_link(
|
|||
|
||||
|
||||
def make_install_req_from_editable(
|
||||
link: Link, template: InstallRequirement
|
||||
link: Link, template: InstallRequirement,
|
||||
) -> InstallRequirement:
|
||||
assert template.editable, "template not editable"
|
||||
assert template.editable, 'template not editable'
|
||||
return install_req_from_editable(
|
||||
link.url,
|
||||
user_supplied=template.user_supplied,
|
||||
|
|
@ -96,14 +105,14 @@ def make_install_req_from_editable(
|
|||
|
||||
|
||||
def _make_install_req_from_dist(
|
||||
dist: BaseDistribution, template: InstallRequirement
|
||||
dist: BaseDistribution, template: InstallRequirement,
|
||||
) -> InstallRequirement:
|
||||
if template.req:
|
||||
line = str(template.req)
|
||||
elif template.link:
|
||||
line = f"{dist.canonical_name} @ {template.link.url}"
|
||||
line = f'{dist.canonical_name} @ {template.link.url}'
|
||||
else:
|
||||
line = f"{dist.canonical_name}=={dist.version}"
|
||||
line = f'{dist.canonical_name}=={dist.version}'
|
||||
ireq = install_req_from_line(
|
||||
line,
|
||||
user_supplied=template.user_supplied,
|
||||
|
|
@ -145,9 +154,9 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
link: Link,
|
||||
source_link: Link,
|
||||
ireq: InstallRequirement,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
factory: Factory,
|
||||
name: NormalizedName | None = None,
|
||||
version: CandidateVersion | None = None,
|
||||
) -> None:
|
||||
self._link = link
|
||||
self._source_link = source_link
|
||||
|
|
@ -158,10 +167,10 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
self.dist = self._prepare()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name} {self.version}"
|
||||
return f'{self.name} {self.version}'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({link!r})".format(
|
||||
return '{class_name}({link!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
link=str(self._link),
|
||||
)
|
||||
|
|
@ -175,7 +184,7 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
return False
|
||||
|
||||
@property
|
||||
def source_link(self) -> Optional[Link]:
|
||||
def source_link(self) -> Link | None:
|
||||
return self._source_link
|
||||
|
||||
@property
|
||||
|
|
@ -196,28 +205,28 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
return self._version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return "{} {} (from {})".format(
|
||||
return '{} {} (from {})'.format(
|
||||
self.name,
|
||||
self.version,
|
||||
self._link.file_path if self._link.is_file else self._link,
|
||||
)
|
||||
|
||||
def _prepare_distribution(self) -> BaseDistribution:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
def _check_metadata_consistency(self, dist: BaseDistribution) -> None:
|
||||
"""Check for consistency of project name and version of dist."""
|
||||
if self._name is not None and self._name != dist.canonical_name:
|
||||
raise MetadataInconsistent(
|
||||
self._ireq,
|
||||
"name",
|
||||
'name',
|
||||
self._name,
|
||||
dist.canonical_name,
|
||||
)
|
||||
if self._version is not None and self._version != dist.version:
|
||||
raise MetadataInconsistent(
|
||||
self._ireq,
|
||||
"version",
|
||||
'version',
|
||||
str(self._version),
|
||||
str(dist.version),
|
||||
)
|
||||
|
|
@ -233,19 +242,19 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
raise
|
||||
except InstallationSubprocessError as exc:
|
||||
# The output has been presented already, so don't duplicate it.
|
||||
exc.context = "See above for output."
|
||||
exc.context = 'See above for output.'
|
||||
raise
|
||||
|
||||
self._check_metadata_consistency(dist)
|
||||
return dist
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
requires = self.dist.iter_dependencies() if with_requires else ()
|
||||
for r in requires:
|
||||
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
|
||||
yield self._factory.make_requires_python_requirement(self.dist.requires_python)
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
return self._ireq
|
||||
|
||||
|
||||
|
|
@ -256,32 +265,32 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
|
|||
self,
|
||||
link: Link,
|
||||
template: InstallRequirement,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
factory: Factory,
|
||||
name: NormalizedName | None = None,
|
||||
version: CandidateVersion | None = None,
|
||||
) -> None:
|
||||
source_link = link
|
||||
cache_entry = factory.get_wheel_cache_entry(link, name)
|
||||
if cache_entry is not None:
|
||||
logger.debug("Using cached wheel link: %s", cache_entry.link)
|
||||
logger.debug('Using cached wheel link: %s', cache_entry.link)
|
||||
link = cache_entry.link
|
||||
ireq = make_install_req_from_link(link, template)
|
||||
assert ireq.link == link
|
||||
if ireq.link.is_wheel and not ireq.link.is_file:
|
||||
wheel = Wheel(ireq.link.filename)
|
||||
wheel_name = canonicalize_name(wheel.name)
|
||||
assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel"
|
||||
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, "{!r} != {!r} for wheel {}".format(
|
||||
version, wheel_version, name
|
||||
assert version == wheel_version, '{!r} != {!r} for wheel {}'.format(
|
||||
version, wheel_version, name,
|
||||
)
|
||||
|
||||
if (
|
||||
cache_entry is not None
|
||||
and cache_entry.persistent
|
||||
and template.link is template.original_link
|
||||
cache_entry is not None and
|
||||
cache_entry.persistent and
|
||||
template.link is template.original_link
|
||||
):
|
||||
ireq.original_link_is_in_wheel_cache = True
|
||||
|
||||
|
|
@ -306,9 +315,9 @@ class EditableCandidate(_InstallRequirementBackedCandidate):
|
|||
self,
|
||||
link: Link,
|
||||
template: InstallRequirement,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
factory: Factory,
|
||||
name: NormalizedName | None = None,
|
||||
version: CandidateVersion | None = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
link=link,
|
||||
|
|
@ -331,7 +340,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)
|
||||
|
|
@ -341,14 +350,14 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
# The returned dist would be exactly the same as self.dist because we
|
||||
# set satisfied_by in _make_install_req_from_dist.
|
||||
# TODO: Supply reason based on force_reinstall and upgrade_strategy.
|
||||
skip_reason = "already satisfied"
|
||||
skip_reason = 'already satisfied'
|
||||
factory.preparer.prepare_installed_requirement(self._ireq, skip_reason)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.dist)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({distribution!r})".format(
|
||||
return '{class_name}({distribution!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
distribution=self.dist,
|
||||
)
|
||||
|
|
@ -378,15 +387,15 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
return self.dist.editable
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return f"{self.name} {self.version} (Installed)"
|
||||
return f'{self.name} {self.version} (Installed)'
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
if not with_requires:
|
||||
return
|
||||
for r in self.dist.iter_dependencies():
|
||||
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -418,17 +427,17 @@ class ExtrasCandidate(Candidate):
|
|||
def __init__(
|
||||
self,
|
||||
base: BaseCandidate,
|
||||
extras: FrozenSet[str],
|
||||
extras: frozenset[str],
|
||||
) -> None:
|
||||
self.base = base
|
||||
self.extras = extras
|
||||
|
||||
def __str__(self) -> str:
|
||||
name, rest = str(self.base).split(" ", 1)
|
||||
return "{}[{}] {}".format(name, ",".join(self.extras), rest)
|
||||
name, rest = str(self.base).split(' ', 1)
|
||||
return '{}[{}] {}'.format(name, ','.join(self.extras), rest)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}(base={base!r}, extras={extras!r})".format(
|
||||
return '{class_name}(base={base!r}, extras={extras!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
base=self.base,
|
||||
extras=self.extras,
|
||||
|
|
@ -456,8 +465,8 @@ class ExtrasCandidate(Candidate):
|
|||
return self.base.version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return "{} [{}]".format(
|
||||
self.base.format_for_error(), ", ".join(sorted(self.extras))
|
||||
return '{} [{}]'.format(
|
||||
self.base.format_for_error(), ', '.join(sorted(self.extras)),
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
@ -469,10 +478,10 @@ class ExtrasCandidate(Candidate):
|
|||
return self.base.is_editable
|
||||
|
||||
@property
|
||||
def source_link(self) -> Optional[Link]:
|
||||
def source_link(self) -> Link | None:
|
||||
return self.base.source_link
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
factory = self.base._factory
|
||||
|
||||
# Add a dependency on the exact base
|
||||
|
|
@ -495,12 +504,12 @@ class ExtrasCandidate(Candidate):
|
|||
|
||||
for r in self.base.dist.iter_dependencies(valid_extras):
|
||||
requirement = factory.make_requirement_from_spec(
|
||||
str(r), self.base._ireq, valid_extras
|
||||
str(r), self.base._ireq, valid_extras,
|
||||
)
|
||||
if requirement:
|
||||
yield requirement
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
# We don't return anything here, because we always
|
||||
# depend on the base candidate, and we'll get the
|
||||
# install requirement from that.
|
||||
|
|
@ -511,19 +520,19 @@ class RequiresPythonCandidate(Candidate):
|
|||
is_installed = False
|
||||
source_link = None
|
||||
|
||||
def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None:
|
||||
def __init__(self, py_version_info: tuple[int, ...] | None) -> None:
|
||||
if py_version_info is not None:
|
||||
version_info = normalize_version_info(py_version_info)
|
||||
else:
|
||||
version_info = sys.version_info[:3]
|
||||
self._version = Version(".".join(str(c) for c in version_info))
|
||||
self._version = Version('.'.join(str(c) for c in version_info))
|
||||
|
||||
# We don't need to implement __eq__() and __ne__() since there is always
|
||||
# only one RequiresPythonCandidate in a resolution, i.e. the host Python.
|
||||
# The built-in object.__eq__() and object.__ne__() do exactly what we want.
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Python {self._version}"
|
||||
return f'Python {self._version}'
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
|
|
@ -538,10 +547,10 @@ class RequiresPythonCandidate(Candidate):
|
|||
return self._version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return f"Python {self.version}"
|
||||
return f'Python {self.version}'
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
return ()
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,70 +1,68 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import logging
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
FrozenSet,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
cast,
|
||||
)
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import FrozenSet
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
|
||||
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.resolvelib import ResolutionImpossible
|
||||
|
||||
from pip._internal.cache import CacheEntry, WheelCache
|
||||
from pip._internal.exceptions import (
|
||||
DistributionNotFound,
|
||||
InstallationError,
|
||||
InstallationSubprocessError,
|
||||
MetadataInconsistent,
|
||||
UnsupportedPythonVersion,
|
||||
UnsupportedWheel,
|
||||
)
|
||||
from pip._internal.cache import CacheEntry
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.exceptions import DistributionNotFound
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import MetadataInconsistent
|
||||
from pip._internal.exceptions import UnsupportedPythonVersion
|
||||
from pip._internal.exceptions import UnsupportedWheel
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution, get_default_environment
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.operations.prepare import RequirementPreparer
|
||||
from pip._internal.req.constructors import install_req_from_link_and_ireq
|
||||
from pip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
check_invalid_constraint_type,
|
||||
)
|
||||
from pip._internal.req.req_install import check_invalid_constraint_type
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.resolution.base import InstallRequirementProvider
|
||||
from pip._internal.utils.compatibility_tags import get_supported
|
||||
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 pip._vendor.packaging.requirements import InvalidRequirement
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.resolvelib import ResolutionImpossible
|
||||
|
||||
from .base import Candidate, CandidateVersion, Constraint, Requirement
|
||||
from .candidates import (
|
||||
AlreadyInstalledCandidate,
|
||||
BaseCandidate,
|
||||
EditableCandidate,
|
||||
ExtrasCandidate,
|
||||
LinkCandidate,
|
||||
RequiresPythonCandidate,
|
||||
as_base_candidate,
|
||||
)
|
||||
from .found_candidates import FoundCandidates, IndexCandidateInfo
|
||||
from .requirements import (
|
||||
ExplicitRequirement,
|
||||
RequiresPythonRequirement,
|
||||
SpecifierRequirement,
|
||||
UnsatisfiableRequirement,
|
||||
)
|
||||
from .base import Candidate
|
||||
from .base import CandidateVersion
|
||||
from .base import Constraint
|
||||
from .base import Requirement
|
||||
from .candidates import AlreadyInstalledCandidate
|
||||
from .candidates import as_base_candidate
|
||||
from .candidates import BaseCandidate
|
||||
from .candidates import EditableCandidate
|
||||
from .candidates import ExtrasCandidate
|
||||
from .candidates import LinkCandidate
|
||||
from .candidates import RequiresPythonCandidate
|
||||
from .found_candidates import FoundCandidates
|
||||
from .found_candidates import IndexCandidateInfo
|
||||
from .requirements import ExplicitRequirement
|
||||
from .requirements import RequiresPythonRequirement
|
||||
from .requirements import SpecifierRequirement
|
||||
from .requirements import UnsatisfiableRequirement
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
|
|
@ -76,14 +74,14 @@ if TYPE_CHECKING:
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
C = TypeVar("C")
|
||||
C = TypeVar('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,13 +90,13 @@ class Factory:
|
|||
finder: PackageFinder,
|
||||
preparer: RequirementPreparer,
|
||||
make_install_req: InstallRequirementProvider,
|
||||
wheel_cache: Optional[WheelCache],
|
||||
wheel_cache: WheelCache | None,
|
||||
use_user_site: bool,
|
||||
force_reinstall: bool,
|
||||
ignore_installed: bool,
|
||||
ignore_requires_python: bool,
|
||||
suppress_build_failures: bool,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
) -> None:
|
||||
self._finder = finder
|
||||
self.preparer = preparer
|
||||
|
|
@ -113,9 +111,9 @@ 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[str]], ExtrasCandidate
|
||||
self._installed_candidate_cache: dict[str, AlreadyInstalledCandidate] = {}
|
||||
self._extras_candidate_cache: dict[
|
||||
tuple[int, frozenset[str]], ExtrasCandidate,
|
||||
] = {}
|
||||
|
||||
if not ignore_installed:
|
||||
|
|
@ -137,11 +135,11 @@ class Factory:
|
|||
wheel = Wheel(link.filename)
|
||||
if wheel.supported(self._finder.target_python.get_tags()):
|
||||
return
|
||||
msg = f"{link.filename} is not a supported wheel on this platform."
|
||||
msg = f'{link.filename} is not a supported wheel on this platform.'
|
||||
raise UnsupportedWheel(msg)
|
||||
|
||||
def _make_extras_candidate(
|
||||
self, base: BaseCandidate, extras: FrozenSet[str]
|
||||
self, base: BaseCandidate, extras: frozenset[str],
|
||||
) -> ExtrasCandidate:
|
||||
cache_key = (id(base), extras)
|
||||
try:
|
||||
|
|
@ -154,7 +152,7 @@ class Factory:
|
|||
def _make_candidate_from_dist(
|
||||
self,
|
||||
dist: BaseDistribution,
|
||||
extras: FrozenSet[str],
|
||||
extras: frozenset[str],
|
||||
template: InstallRequirement,
|
||||
) -> Candidate:
|
||||
try:
|
||||
|
|
@ -169,11 +167,11 @@ class Factory:
|
|||
def _make_candidate_from_link(
|
||||
self,
|
||||
link: Link,
|
||||
extras: FrozenSet[str],
|
||||
extras: frozenset[str],
|
||||
template: InstallRequirement,
|
||||
name: Optional[NormalizedName],
|
||||
version: Optional[CandidateVersion],
|
||||
) -> Optional[Candidate]:
|
||||
name: NormalizedName | None,
|
||||
version: CandidateVersion | None,
|
||||
) -> Candidate | None:
|
||||
# TODO: Check already installed candidate, and use it if the link and
|
||||
# editable flag match.
|
||||
|
||||
|
|
@ -194,17 +192,17 @@ class Factory:
|
|||
)
|
||||
except MetadataInconsistent as e:
|
||||
logger.info(
|
||||
"Discarding [blue underline]%s[/]: [yellow]%s[reset]",
|
||||
'Discarding [blue underline]%s[/]: [yellow]%s[reset]',
|
||||
link,
|
||||
e,
|
||||
extra={"markup": True},
|
||||
extra={'markup': True},
|
||||
)
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
except InstallationSubprocessError as e:
|
||||
if not self._suppress_build_failures:
|
||||
raise
|
||||
logger.warning("Discarding %s due to build failure: %s", link, e)
|
||||
logger.warning('Discarding %s due to build failure: %s', link, e)
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
|
||||
|
|
@ -221,17 +219,17 @@ class Factory:
|
|||
)
|
||||
except MetadataInconsistent as e:
|
||||
logger.info(
|
||||
"Discarding [blue underline]%s[/]: [yellow]%s[reset]",
|
||||
'Discarding [blue underline]%s[/]: [yellow]%s[reset]',
|
||||
link,
|
||||
e,
|
||||
extra={"markup": True},
|
||||
extra={'markup': True},
|
||||
)
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
except InstallationSubprocessError as e:
|
||||
if not self._suppress_build_failures:
|
||||
raise
|
||||
logger.warning("Discarding %s due to build failure: %s", link, e)
|
||||
logger.warning('Discarding %s due to build failure: %s', link, e)
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
base = self._link_candidate_cache[link]
|
||||
|
|
@ -246,7 +244,7 @@ class Factory:
|
|||
specifier: SpecifierSet,
|
||||
hashes: Hashes,
|
||||
prefers_installed: bool,
|
||||
incompatible_ids: Set[int],
|
||||
incompatible_ids: set[int],
|
||||
) -> Iterable[Candidate]:
|
||||
if not ireqs:
|
||||
return ()
|
||||
|
|
@ -256,17 +254,17 @@ class Factory:
|
|||
# all of them.
|
||||
# Hopefully the Project model can correct this mismatch in the future.
|
||||
template = ireqs[0]
|
||||
assert template.req, "Candidates found on index must be PEP 508"
|
||||
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"
|
||||
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() -> Optional[Candidate]:
|
||||
def _get_installed_candidate() -> Candidate | None:
|
||||
"""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.
|
||||
|
|
@ -305,11 +303,11 @@ class Factory:
|
|||
|
||||
def is_pinned(specifier: SpecifierSet) -> bool:
|
||||
for sp in specifier:
|
||||
if sp.operator == "===":
|
||||
if sp.operator == '===':
|
||||
return True
|
||||
if sp.operator != "==":
|
||||
if sp.operator != '==':
|
||||
continue
|
||||
if sp.version.endswith(".*"):
|
||||
if sp.version.endswith('.*'):
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
|
@ -340,7 +338,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.
|
||||
|
||||
|
|
@ -356,7 +354,7 @@ class Factory:
|
|||
# We've stripped extras from the identifier, and should always
|
||||
# get a BaseCandidate here, unless there's a bug elsewhere.
|
||||
base_cand = as_base_candidate(lookup_cand)
|
||||
assert base_cand is not None, "no extras here"
|
||||
assert base_cand is not None, 'no extras here'
|
||||
yield self._make_extras_candidate(base_cand, extras)
|
||||
|
||||
def _iter_candidates_from_constraints(
|
||||
|
|
@ -391,8 +389,8 @@ class Factory:
|
|||
prefers_installed: 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:
|
||||
|
|
@ -447,14 +445,14 @@ class Factory:
|
|||
return (
|
||||
c
|
||||
for c in explicit_candidates
|
||||
if id(c) not in incompat_ids
|
||||
and constraint.is_satisfied_by(c)
|
||||
and all(req.is_satisfied_by(c) for req in requirements[identifier])
|
||||
if id(c) not in incompat_ids and
|
||||
constraint.is_satisfied_by(c) and
|
||||
all(req.is_satisfied_by(c) for req in requirements[identifier])
|
||||
)
|
||||
|
||||
def _make_requirement_from_install_req(
|
||||
self, ireq: InstallRequirement, requested_extras: Iterable[str]
|
||||
) -> Optional[Requirement]:
|
||||
self, ireq: InstallRequirement, requested_extras: Iterable[str],
|
||||
) -> Requirement | None:
|
||||
if not ireq.match_markers(requested_extras):
|
||||
logger.info(
|
||||
"Ignoring %s: markers '%s' don't match your environment",
|
||||
|
|
@ -485,7 +483,7 @@ class Factory:
|
|||
return self.make_requirement_from_candidate(cand)
|
||||
|
||||
def collect_root_requirements(
|
||||
self, root_ireqs: List[InstallRequirement]
|
||||
self, root_ireqs: list[InstallRequirement],
|
||||
) -> CollectedRootRequirements:
|
||||
collected = CollectedRootRequirements([], {}, {})
|
||||
for i, ireq in enumerate(root_ireqs):
|
||||
|
|
@ -496,7 +494,7 @@ class Factory:
|
|||
raise InstallationError(problem)
|
||||
if not ireq.match_markers():
|
||||
continue
|
||||
assert ireq.name, "Constraint must be named"
|
||||
assert ireq.name, 'Constraint must be named'
|
||||
name = canonicalize_name(ireq.name)
|
||||
if name in collected.constraints:
|
||||
collected.constraints[name] &= ireq
|
||||
|
|
@ -515,23 +513,23 @@ class Factory:
|
|||
return collected
|
||||
|
||||
def make_requirement_from_candidate(
|
||||
self, candidate: Candidate
|
||||
self, candidate: Candidate,
|
||||
) -> ExplicitRequirement:
|
||||
return ExplicitRequirement(candidate)
|
||||
|
||||
def make_requirement_from_spec(
|
||||
self,
|
||||
specifier: str,
|
||||
comes_from: Optional[InstallRequirement],
|
||||
comes_from: InstallRequirement | None,
|
||||
requested_extras: Iterable[str] = (),
|
||||
) -> Optional[Requirement]:
|
||||
) -> Requirement | None:
|
||||
ireq = self._make_install_req_from_spec(specifier, comes_from)
|
||||
return self._make_requirement_from_install_req(ireq, requested_extras)
|
||||
|
||||
def make_requires_python_requirement(
|
||||
self,
|
||||
specifier: SpecifierSet,
|
||||
) -> Optional[Requirement]:
|
||||
) -> Requirement | None:
|
||||
if self._ignore_requires_python:
|
||||
return None
|
||||
# Don't bother creating a dependency for an empty Requires-Python.
|
||||
|
|
@ -540,8 +538,8 @@ class Factory:
|
|||
return RequiresPythonRequirement(specifier, self._python_candidate)
|
||||
|
||||
def get_wheel_cache_entry(
|
||||
self, link: Link, name: Optional[str]
|
||||
) -> Optional[CacheEntry]:
|
||||
self, link: Link, name: str | None,
|
||||
) -> CacheEntry | None:
|
||||
"""Look up the link in the wheel cache.
|
||||
|
||||
If ``preparer.require_hashes`` is True, don't use the wheel cache,
|
||||
|
|
@ -558,7 +556,7 @@ class Factory:
|
|||
supported_tags=get_supported(),
|
||||
)
|
||||
|
||||
def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[BaseDistribution]:
|
||||
def get_dist_to_uninstall(self, candidate: Candidate) -> BaseDistribution | None:
|
||||
# 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.
|
||||
|
|
@ -580,82 +578,82 @@ class Factory:
|
|||
# in virtual environments, so we error out.
|
||||
if running_under_virtualenv() and dist.in_site_packages:
|
||||
message = (
|
||||
f"Will not install to the user site because it will lack "
|
||||
f"sys.path precedence to {dist.raw_name} in {dist.location}"
|
||||
f'Will not install to the user site because it will lack '
|
||||
f'sys.path precedence to {dist.raw_name} in {dist.location}'
|
||||
)
|
||||
raise InstallationError(message)
|
||||
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"
|
||||
assert causes, 'Requires-Python error reported with no cause'
|
||||
|
||||
version = self._python_candidate.version
|
||||
|
||||
if len(causes) == 1:
|
||||
specifier = str(causes[0].requirement.specifier)
|
||||
message = (
|
||||
f"Package {causes[0].parent.name!r} requires a different "
|
||||
f"Python: {version} not in {specifier!r}"
|
||||
f'Package {causes[0].parent.name!r} requires a different '
|
||||
f'Python: {version} not in {specifier!r}'
|
||||
)
|
||||
return UnsupportedPythonVersion(message)
|
||||
|
||||
message = f"Packages require a different Python. {version} not in:"
|
||||
message = f'Packages require a different Python. {version} not in:'
|
||||
for cause in causes:
|
||||
package = cause.parent.format_for_error()
|
||||
specifier = str(cause.requirement.specifier)
|
||||
message += f"\n{specifier!r} (required by {package})"
|
||||
message += f'\n{specifier!r} (required by {package})'
|
||||
return UnsupportedPythonVersion(message)
|
||||
|
||||
def _report_single_requirement_conflict(
|
||||
self, req: Requirement, parent: Optional[Candidate]
|
||||
self, req: Requirement, parent: Candidate | None,
|
||||
) -> DistributionNotFound:
|
||||
if parent is None:
|
||||
req_disp = str(req)
|
||||
else:
|
||||
req_disp = f"{req} (from {parent.name})"
|
||||
req_disp = f'{req} (from {parent.name})'
|
||||
|
||||
cands = self._finder.find_all_candidates(req.project_name)
|
||||
versions = [str(v) for v in sorted({c.version for c in cands})]
|
||||
|
||||
logger.critical(
|
||||
"Could not find a version that satisfies the requirement %s "
|
||||
"(from versions: %s)",
|
||||
'Could not find a version that satisfies the requirement %s '
|
||||
'(from versions: %s)',
|
||||
req_disp,
|
||||
", ".join(versions) or "none",
|
||||
', '.join(versions) or 'none',
|
||||
)
|
||||
if str(req) == "requirements.txt":
|
||||
if str(req) == 'requirements.txt':
|
||||
logger.info(
|
||||
"HINT: You are attempting to install a package literally "
|
||||
'HINT: You are attempting to install a package literally '
|
||||
'named "requirements.txt" (which cannot exist). Consider '
|
||||
"using the '-r' flag to install the packages listed in "
|
||||
"requirements.txt"
|
||||
'requirements.txt',
|
||||
)
|
||||
|
||||
return DistributionNotFound(f"No matching distribution found for {req}")
|
||||
return DistributionNotFound(f'No matching distribution found for {req}')
|
||||
|
||||
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"
|
||||
assert e.causes, 'Installation error reported with no cause'
|
||||
|
||||
# If one of the things we can't solve is "we need Python X.Y",
|
||||
# that is what we report.
|
||||
requires_python_causes = [
|
||||
cause
|
||||
for cause in e.causes
|
||||
if isinstance(cause.requirement, RequiresPythonRequirement)
|
||||
and not cause.requirement.is_satisfied_by(self._python_candidate)
|
||||
if isinstance(cause.requirement, RequiresPythonRequirement) and
|
||||
not cause.requirement.is_satisfied_by(self._python_candidate)
|
||||
]
|
||||
if requires_python_causes:
|
||||
# The comprehension above makes sure all Requirement instances are
|
||||
# RequiresPythonRequirement, so let's cast for convenience.
|
||||
return self._report_requires_python_error(
|
||||
cast("Sequence[ConflictCause]", requires_python_causes),
|
||||
cast('Sequence[ConflictCause]', requires_python_causes),
|
||||
)
|
||||
|
||||
# Otherwise, we have a set of causes which can't all be satisfied
|
||||
|
|
@ -672,16 +670,16 @@ 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]
|
||||
|
||||
return ", ".join(parts[:-1]) + " and " + parts[-1]
|
||||
return ', '.join(parts[:-1]) + ' and ' + parts[-1]
|
||||
|
||||
def describe_trigger(parent: Candidate) -> str:
|
||||
ireq = parent.get_install_requirement()
|
||||
if not ireq or not ireq.comes_from:
|
||||
return f"{parent.name}=={parent.version}"
|
||||
return f'{parent.name}=={parent.version}'
|
||||
if isinstance(ireq.comes_from, InstallRequirement):
|
||||
return str(ireq.comes_from.name)
|
||||
return str(ireq.comes_from)
|
||||
|
|
@ -698,42 +696,42 @@ class Factory:
|
|||
if triggers:
|
||||
info = text_join(sorted(triggers))
|
||||
else:
|
||||
info = "the requested packages"
|
||||
info = 'the requested packages'
|
||||
|
||||
msg = (
|
||||
"Cannot install {} because these package versions "
|
||||
"have conflicting dependencies.".format(info)
|
||||
'Cannot install {} because these package versions '
|
||||
'have conflicting dependencies.'.format(info)
|
||||
)
|
||||
logger.critical(msg)
|
||||
msg = "\nThe conflict is caused by:"
|
||||
msg = '\nThe conflict is caused by:'
|
||||
|
||||
relevant_constraints = set()
|
||||
for req, parent in e.causes:
|
||||
if req.name in constraints:
|
||||
relevant_constraints.add(req.name)
|
||||
msg = msg + "\n "
|
||||
msg = msg + '\n '
|
||||
if parent:
|
||||
msg = msg + f"{parent.name} {parent.version} depends on "
|
||||
msg = msg + f'{parent.name} {parent.version} depends on '
|
||||
else:
|
||||
msg = msg + "The user requested "
|
||||
msg = msg + 'The user requested '
|
||||
msg = msg + req.format_for_error()
|
||||
for key in relevant_constraints:
|
||||
spec = constraints[key].specifier
|
||||
msg += f"\n The user requested (constraint) {key}{spec}"
|
||||
msg += f'\n The user requested (constraint) {key}{spec}'
|
||||
|
||||
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 attempt to solve "
|
||||
+ "the dependency conflict\n"
|
||||
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 attempt to solve ' +
|
||||
'the dependency conflict\n'
|
||||
)
|
||||
|
||||
logger.info(msg)
|
||||
|
||||
return DistributionNotFound(
|
||||
"ResolutionImpossible: for help visit "
|
||||
"https://pip.pypa.io/en/latest/topics/dependency-resolution/"
|
||||
"#dealing-with-dependency-conflicts"
|
||||
'ResolutionImpossible: for help visit '
|
||||
'https://pip.pypa.io/en/latest/topics/dependency-resolution/'
|
||||
'#dealing-with-dependency-conflicts',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,17 @@ everything here lazy all the way down, so we only touch candidates that we
|
|||
absolutely need, and not "download the world" when we only need one version of
|
||||
something.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Set, Tuple
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Iterator
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip._vendor.packaging.version import _BaseVersion
|
||||
|
||||
|
|
@ -40,7 +47,7 @@ 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
|
||||
|
|
@ -52,7 +59,7 @@ def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
|
|||
|
||||
|
||||
def _iter_built_with_prepended(
|
||||
installed: Candidate, infos: Iterator[IndexCandidateInfo]
|
||||
installed: Candidate, infos: Iterator[IndexCandidateInfo],
|
||||
) -> Iterator[Candidate]:
|
||||
"""Iterator for ``FoundCandidates``.
|
||||
|
||||
|
|
@ -62,7 +69,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
|
||||
|
|
@ -74,7 +81,7 @@ def _iter_built_with_prepended(
|
|||
|
||||
|
||||
def _iter_built_with_inserted(
|
||||
installed: Candidate, infos: Iterator[IndexCandidateInfo]
|
||||
installed: Candidate, infos: Iterator[IndexCandidateInfo],
|
||||
) -> Iterator[Candidate]:
|
||||
"""Iterator for ``FoundCandidates``.
|
||||
|
||||
|
|
@ -86,7 +93,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
|
||||
|
|
@ -117,9 +124,9 @@ class FoundCandidates(SequenceCandidate):
|
|||
def __init__(
|
||||
self,
|
||||
get_infos: Callable[[], Iterator[IndexCandidateInfo]],
|
||||
installed: Optional[Candidate],
|
||||
installed: Candidate | None,
|
||||
prefers_installed: bool,
|
||||
incompatible_ids: Set[int],
|
||||
incompatible_ids: set[int],
|
||||
):
|
||||
self._get_infos = get_infos
|
||||
self._installed = installed
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import math
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Mapping,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import Mapping
|
||||
from typing import Sequence
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from pip._vendor.resolvelib.providers import AbstractProvider
|
||||
|
||||
from .base import Candidate, Constraint, Requirement
|
||||
from .base import Candidate
|
||||
from .base import Constraint
|
||||
from .base import Requirement
|
||||
from .candidates import REQUIRES_PYTHON_IDENTIFIER
|
||||
from .factory import Factory
|
||||
|
||||
|
|
@ -46,15 +48,15 @@ else:
|
|||
# services to those objects (access to pip's finder and preparer).
|
||||
|
||||
|
||||
D = TypeVar("D")
|
||||
V = TypeVar("V")
|
||||
D = TypeVar('D')
|
||||
V = TypeVar('V')
|
||||
|
||||
|
||||
def _get_with_identifier(
|
||||
mapping: Mapping[str, V],
|
||||
identifier: str,
|
||||
default: D,
|
||||
) -> Union[D, V]:
|
||||
) -> 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
|
||||
|
|
@ -69,7 +71,7 @@ def _get_with_identifier(
|
|||
# some regular expression. But since pip's resolver only spits out three
|
||||
# kinds of identifiers: normalized PEP 503 names, normalized names plus
|
||||
# extras, and Requires-Python, we can cheat a bit here.
|
||||
name, open_bracket, _ = identifier.partition("[")
|
||||
name, open_bracket, _ = identifier.partition('[')
|
||||
if open_bracket and name in mapping:
|
||||
return mapping[name]
|
||||
return default
|
||||
|
|
@ -89,19 +91,19 @@ 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)
|
||||
self._known_depths: dict[str, float] = collections.defaultdict(lambda: math.inf)
|
||||
|
||||
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
|
||||
def identify(self, requirement_or_candidate: Requirement | Candidate) -> str:
|
||||
return requirement_or_candidate.name
|
||||
|
||||
def get_preference( # type: ignore
|
||||
|
|
@ -109,9 +111,9 @@ class PipProvider(_ProviderBase):
|
|||
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
|
||||
|
|
@ -139,11 +141,11 @@ class PipProvider(_ProviderBase):
|
|||
]
|
||||
|
||||
direct = candidate is not None
|
||||
pinned = any(op[:2] == "==" for op in operators)
|
||||
pinned = any(op[:2] == '==' for op in operators)
|
||||
unfree = bool(operators)
|
||||
|
||||
try:
|
||||
requested_order: Union[int, float] = self._user_requested[identifier]
|
||||
requested_order: int | float = self._user_requested[identifier]
|
||||
except KeyError:
|
||||
requested_order = math.inf
|
||||
parent_depths = (
|
||||
|
|
@ -169,7 +171,7 @@ class PipProvider(_ProviderBase):
|
|||
# delaying Setuptools helps reduce branches the resolver has to check.
|
||||
# This serves as a temporary fix for issues like "apache-airflow[all]"
|
||||
# while we work on "proper" branch pruning techniques.
|
||||
delay_this = identifier == "setuptools"
|
||||
delay_this = identifier == 'setuptools'
|
||||
|
||||
# Prefer the causes of backtracking on the assumption that the problem
|
||||
# resolving the dependency tree is related to the failures that caused
|
||||
|
|
@ -205,9 +207,9 @@ class PipProvider(_ProviderBase):
|
|||
an upgrade strategy of "to-satisfy-only" means that `--upgrade`
|
||||
was not specified).
|
||||
"""
|
||||
if self._upgrade_strategy == "eager":
|
||||
if self._upgrade_strategy == 'eager':
|
||||
return True
|
||||
elif self._upgrade_strategy == "only-if-needed":
|
||||
elif self._upgrade_strategy == 'only-if-needed':
|
||||
user_order = _get_with_identifier(
|
||||
self._user_requested,
|
||||
identifier,
|
||||
|
|
@ -238,7 +240,7 @@ class PipProvider(_ProviderBase):
|
|||
|
||||
@staticmethod
|
||||
def is_backtrack_cause(
|
||||
identifier: str, backtrack_causes: Sequence["PreferenceInformation"]
|
||||
identifier: str, backtrack_causes: Sequence[PreferenceInformation],
|
||||
) -> bool:
|
||||
for backtrack_cause in backtrack_causes:
|
||||
if identifier == backtrack_cause.requirement.name:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from logging import getLogger
|
||||
from typing import Any, DefaultDict
|
||||
from typing import Any
|
||||
from typing import DefaultDict
|
||||
|
||||
from pip._vendor.resolvelib.reporters import BaseReporter
|
||||
|
||||
from .base import Candidate, Requirement
|
||||
from .base import Candidate
|
||||
from .base import Requirement
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
|
@ -15,20 +19,20 @@ class PipReporter(BaseReporter):
|
|||
|
||||
self._messages_at_backtrack = {
|
||||
1: (
|
||||
"pip is looking at multiple versions of {package_name} to "
|
||||
"determine which version is compatible with other "
|
||||
"requirements. This could take a while."
|
||||
'pip is looking at multiple versions of {package_name} to '
|
||||
'determine which version is compatible with other '
|
||||
'requirements. This could take a while.'
|
||||
),
|
||||
8: (
|
||||
"pip is looking at multiple versions of {package_name} to "
|
||||
"determine which version is compatible with other "
|
||||
"requirements. This could take a while."
|
||||
'pip is looking at multiple versions of {package_name} to '
|
||||
'determine which version is compatible with other '
|
||||
'requirements. This could take a while.'
|
||||
),
|
||||
13: (
|
||||
"This is taking longer than usual. You might need to provide "
|
||||
"the dependency resolver with stricter constraints to reduce "
|
||||
"runtime. See https://pip.pypa.io/warnings/backtracking for "
|
||||
"guidance. If you want to abort this run, press Ctrl + C."
|
||||
'This is taking longer than usual. You might need to provide '
|
||||
'the dependency resolver with stricter constraints to reduce '
|
||||
'runtime. See https://pip.pypa.io/warnings/backtracking for '
|
||||
'guidance. If you want to abort this run, press Ctrl + C.'
|
||||
),
|
||||
}
|
||||
|
||||
|
|
@ -40,29 +44,29 @@ class PipReporter(BaseReporter):
|
|||
return
|
||||
|
||||
message = self._messages_at_backtrack[count]
|
||||
logger.info("INFO: %s", message.format(package_name=candidate.name))
|
||||
logger.info('INFO: %s', message.format(package_name=candidate.name))
|
||||
|
||||
|
||||
class PipDebuggingReporter(BaseReporter):
|
||||
"""A reporter that does an info log for every event it sees."""
|
||||
|
||||
def starting(self) -> None:
|
||||
logger.info("Reporter.starting()")
|
||||
logger.info('Reporter.starting()')
|
||||
|
||||
def starting_round(self, index: int) -> None:
|
||||
logger.info("Reporter.starting_round(%r)", index)
|
||||
logger.info('Reporter.starting_round(%r)', index)
|
||||
|
||||
def ending_round(self, index: int, state: Any) -> None:
|
||||
logger.info("Reporter.ending_round(%r, state)", index)
|
||||
logger.info('Reporter.ending_round(%r, state)', index)
|
||||
|
||||
def ending(self, state: Any) -> None:
|
||||
logger.info("Reporter.ending(%r)", state)
|
||||
logger.info('Reporter.ending(%r)', state)
|
||||
|
||||
def adding_requirement(self, requirement: Requirement, parent: Candidate) -> None:
|
||||
logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent)
|
||||
logger.info('Reporter.adding_requirement(%r, %r)', requirement, parent)
|
||||
|
||||
def backtracking(self, candidate: Candidate) -> None:
|
||||
logger.info("Reporter.backtracking(%r)", candidate)
|
||||
logger.info('Reporter.backtracking(%r)', candidate)
|
||||
|
||||
def pinning(self, candidate: Candidate) -> None:
|
||||
logger.info("Reporter.pinning(%r)", candidate)
|
||||
logger.info('Reporter.pinning(%r)', candidate)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
|
||||
from .base import Candidate, CandidateLookup, Requirement, format_name
|
||||
from .base import Candidate
|
||||
from .base import CandidateLookup
|
||||
from .base import format_name
|
||||
from .base import Requirement
|
||||
|
||||
|
||||
class ExplicitRequirement(Requirement):
|
||||
|
|
@ -14,7 +19,7 @@ class ExplicitRequirement(Requirement):
|
|||
return str(self.candidate)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({candidate!r})".format(
|
||||
return '{class_name}({candidate!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
candidate=self.candidate,
|
||||
)
|
||||
|
|
@ -41,7 +46,7 @@ class ExplicitRequirement(Requirement):
|
|||
|
||||
class SpecifierRequirement(Requirement):
|
||||
def __init__(self, ireq: InstallRequirement) -> None:
|
||||
assert ireq.link is None, "This is a link, not a specifier"
|
||||
assert ireq.link is None, 'This is a link, not a specifier'
|
||||
self._ireq = ireq
|
||||
self._extras = frozenset(ireq.extras)
|
||||
|
||||
|
|
@ -49,14 +54,14 @@ class SpecifierRequirement(Requirement):
|
|||
return str(self._ireq.req)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({requirement!r})".format(
|
||||
return '{class_name}({requirement!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
requirement=str(self._ireq.req),
|
||||
)
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
|
||||
assert self._ireq.req, 'Specifier-backed ireq is always PEP 508'
|
||||
return canonicalize_name(self._ireq.req.name)
|
||||
|
||||
@property
|
||||
|
|
@ -69,26 +74,26 @@ class SpecifierRequirement(Requirement):
|
|||
# This makes the specifier a bit more "human readable", without
|
||||
# risking a change in meaning. (Hopefully! Not all edge cases have
|
||||
# been checked)
|
||||
parts = [s.strip() for s in str(self).split(",")]
|
||||
parts = [s.strip() for s in str(self).split(',')]
|
||||
if len(parts) == 0:
|
||||
return ""
|
||||
return ''
|
||||
elif len(parts) == 1:
|
||||
return parts[0]
|
||||
|
||||
return ", ".join(parts[:-1]) + " and " + parts[-1]
|
||||
return ', '.join(parts[:-1]) + ' and ' + parts[-1]
|
||||
|
||||
def get_candidate_lookup(self) -> CandidateLookup:
|
||||
return None, self._ireq
|
||||
|
||||
def is_satisfied_by(self, candidate: Candidate) -> bool:
|
||||
assert candidate.name == self.name, (
|
||||
f"Internal issue: Candidate is not for this requirement "
|
||||
f"{candidate.name} vs {self.name}"
|
||||
f'Internal issue: Candidate is not for this requirement '
|
||||
f'{candidate.name} vs {self.name}'
|
||||
)
|
||||
# We can safely always allow prereleases here since PackageFinder
|
||||
# already implements the prerelease logic, and would have filtered out
|
||||
# prerelease candidates if the user does not expect them.
|
||||
assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
|
||||
assert self._ireq.req, 'Specifier-backed ireq is always PEP 508'
|
||||
spec = self._ireq.req.specifier
|
||||
return spec.contains(candidate.version, prereleases=True)
|
||||
|
||||
|
|
@ -101,10 +106,10 @@ class RequiresPythonRequirement(Requirement):
|
|||
self._candidate = match
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Python {self.specifier}"
|
||||
return f'Python {self.specifier}'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({specifier!r})".format(
|
||||
return '{class_name}({specifier!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
specifier=str(self.specifier),
|
||||
)
|
||||
|
|
@ -126,7 +131,7 @@ class RequiresPythonRequirement(Requirement):
|
|||
return None, None
|
||||
|
||||
def is_satisfied_by(self, candidate: Candidate) -> bool:
|
||||
assert candidate.name == self._candidate.name, "Not Python candidate"
|
||||
assert candidate.name == self._candidate.name, 'Not Python candidate'
|
||||
# We can safely always allow prereleases here since PackageFinder
|
||||
# already implements the prerelease logic, and would have filtered out
|
||||
# prerelease candidates if the user does not expect them.
|
||||
|
|
@ -140,10 +145,10 @@ class UnsatisfiableRequirement(Requirement):
|
|||
self._name = name
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self._name} (unavailable)"
|
||||
return f'{self._name} (unavailable)'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({name!r})".format(
|
||||
return '{class_name}({name!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
name=str(self._name),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,26 +1,34 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
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
|
||||
from pip._vendor.resolvelib import Resolver as RLResolver
|
||||
from pip._vendor.resolvelib.structs import DirectedGraph
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.operations.prepare import RequirementPreparer
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_set import RequirementSet
|
||||
from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
|
||||
from pip._internal.resolution.base import BaseResolver
|
||||
from pip._internal.resolution.base import InstallRequirementProvider
|
||||
from pip._internal.resolution.resolvelib.provider import PipProvider
|
||||
from pip._internal.resolution.resolvelib.reporter import (
|
||||
PipDebuggingReporter,
|
||||
PipReporter,
|
||||
)
|
||||
from pip._internal.resolution.resolvelib.reporter import PipDebuggingReporter
|
||||
from pip._internal.resolution.resolvelib.reporter import PipReporter
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.resolvelib import BaseReporter
|
||||
from pip._vendor.resolvelib import ResolutionImpossible
|
||||
from pip._vendor.resolvelib import Resolver as RLResolver
|
||||
from pip._vendor.resolvelib.structs import DirectedGraph
|
||||
|
||||
from .base import Candidate, Requirement
|
||||
from .base import Candidate
|
||||
from .base import Requirement
|
||||
from .factory import Factory
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -33,13 +41,13 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class Resolver(BaseResolver):
|
||||
_allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"}
|
||||
_allowed_strategies = {'eager', 'only-if-needed', 'to-satisfy-only'}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
preparer: RequirementPreparer,
|
||||
finder: PackageFinder,
|
||||
wheel_cache: Optional[WheelCache],
|
||||
wheel_cache: WheelCache | None,
|
||||
make_install_req: InstallRequirementProvider,
|
||||
use_user_site: bool,
|
||||
ignore_dependencies: bool,
|
||||
|
|
@ -48,7 +56,7 @@ class Resolver(BaseResolver):
|
|||
force_reinstall: bool,
|
||||
upgrade_strategy: str,
|
||||
suppress_build_failures: bool,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
):
|
||||
super().__init__()
|
||||
assert upgrade_strategy in self._allowed_strategies
|
||||
|
|
@ -67,10 +75,10 @@ class Resolver(BaseResolver):
|
|||
)
|
||||
self.ignore_dependencies = ignore_dependencies
|
||||
self.upgrade_strategy = upgrade_strategy
|
||||
self._result: Optional[Result] = None
|
||||
self._result: Result | None = 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(
|
||||
|
|
@ -80,7 +88,7 @@ class Resolver(BaseResolver):
|
|||
upgrade_strategy=self.upgrade_strategy,
|
||||
user_requested=collected.user_requested,
|
||||
)
|
||||
if "PIP_RESOLVER_DEBUG" in os.environ:
|
||||
if 'PIP_RESOLVER_DEBUG' in os.environ:
|
||||
reporter: BaseReporter = PipDebuggingReporter()
|
||||
else:
|
||||
reporter = PipReporter()
|
||||
|
|
@ -92,12 +100,12 @@ class Resolver(BaseResolver):
|
|||
try:
|
||||
try_to_avoid_resolution_too_deep = 2000000
|
||||
result = self._result = resolver.resolve(
|
||||
collected.requirements, max_rounds=try_to_avoid_resolution_too_deep
|
||||
collected.requirements, max_rounds=try_to_avoid_resolution_too_deep,
|
||||
)
|
||||
|
||||
except ResolutionImpossible as e:
|
||||
error = self.factory.get_installation_error(
|
||||
cast("ResolutionImpossible[Requirement, Candidate]", e),
|
||||
cast('ResolutionImpossible[Requirement, Candidate]', e),
|
||||
collected.constraints,
|
||||
)
|
||||
raise error from e
|
||||
|
|
@ -129,9 +137,9 @@ class Resolver(BaseResolver):
|
|||
if candidate.source_link.is_wheel:
|
||||
# is a local wheel -- do nothing.
|
||||
logger.info(
|
||||
"%s is already installed with the same version as the "
|
||||
"provided wheel. Use --force-reinstall to force an "
|
||||
"installation of the wheel.",
|
||||
'%s is already installed with the same version as the '
|
||||
'provided wheel. Use --force-reinstall to force an '
|
||||
'installation of the wheel.',
|
||||
ireq.name,
|
||||
)
|
||||
continue
|
||||
|
|
@ -146,14 +154,14 @@ class Resolver(BaseResolver):
|
|||
# The reason can contain non-ASCII characters, Unicode
|
||||
# is required for Python 2.
|
||||
msg = (
|
||||
"The candidate selected for download or install is a "
|
||||
"yanked version: {name!r} candidate (version {version} "
|
||||
"at {link})\nReason for being yanked: {reason}"
|
||||
'The candidate selected for download or install is a '
|
||||
'yanked version: {name!r} candidate (version {version} '
|
||||
'at {link})\nReason for being yanked: {reason}'
|
||||
).format(
|
||||
name=candidate.name,
|
||||
version=candidate.version,
|
||||
link=link,
|
||||
reason=link.yanked_reason or "<none given>",
|
||||
reason=link.yanked_reason or '<none given>',
|
||||
)
|
||||
logger.warning(msg)
|
||||
|
||||
|
|
@ -164,8 +172,8 @@ class Resolver(BaseResolver):
|
|||
return req_set
|
||||
|
||||
def get_installation_order(
|
||||
self, req_set: RequirementSet
|
||||
) -> List[InstallRequirement]:
|
||||
self, req_set: RequirementSet,
|
||||
) -> list[InstallRequirement]:
|
||||
"""Get order for installation of requirements in RequirementSet.
|
||||
|
||||
The returned list contains a requirement before another that depends on
|
||||
|
|
@ -178,7 +186,7 @@ class Resolver(BaseResolver):
|
|||
arbitrary points. We make no guarantees about where the cycle
|
||||
would be broken, other than it *would* be broken.
|
||||
"""
|
||||
assert self._result is not None, "must call resolve() first"
|
||||
assert self._result is not None, 'must call resolve() first'
|
||||
|
||||
if not req_set.requirements:
|
||||
# Nothing is left to install, so we do not need an order.
|
||||
|
|
@ -196,8 +204,8 @@ class Resolver(BaseResolver):
|
|||
|
||||
|
||||
def get_topological_weights(
|
||||
graph: "DirectedGraph[Optional[str]]", requirement_keys: Set[str]
|
||||
) -> Dict[Optional[str], int]:
|
||||
graph: DirectedGraph[Optional[str]], requirement_keys: set[str],
|
||||
) -> dict[str | None, int]:
|
||||
"""Assign weights to each node based on how "deep" they are.
|
||||
|
||||
This implementation may change at any point in the future without prior
|
||||
|
|
@ -223,10 +231,10 @@ def get_topological_weights(
|
|||
We are only interested in the weights of packages that are in the
|
||||
requirement_keys.
|
||||
"""
|
||||
path: Set[Optional[str]] = set()
|
||||
weights: Dict[Optional[str], int] = {}
|
||||
path: set[str | None] = set()
|
||||
weights: dict[str | None, int] = {}
|
||||
|
||||
def visit(node: Optional[str]) -> None:
|
||||
def visit(node: str | None) -> None:
|
||||
if node in path:
|
||||
# We hit a cycle, so we'll break it here.
|
||||
return
|
||||
|
|
@ -285,9 +293,9 @@ def get_topological_weights(
|
|||
|
||||
|
||||
def _req_set_item_sorter(
|
||||
item: Tuple[str, InstallRequirement],
|
||||
weights: Dict[Optional[str], int],
|
||||
) -> Tuple[int, str]:
|
||||
item: tuple[str, InstallRequirement],
|
||||
weights: dict[str | None, 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