update
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,9 +10,6 @@ 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
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
@@ -52,7 +49,7 @@ from pip._internal.utils.packaging import check_requires_python
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]]
|
||||
DiscoveredDependencies = DefaultDict[Optional[str], List[InstallRequirement]]
|
||||
|
||||
|
||||
def _check_dist_requires_python(
|
||||
@@ -104,9 +101,8 @@ def _check_dist_requires_python(
|
||||
return
|
||||
|
||||
raise UnsupportedPythonVersion(
|
||||
"Package {!r} requires a different Python: {} not in {!r}".format(
|
||||
dist.raw_name, version, requires_python
|
||||
)
|
||||
f"Package {dist.raw_name!r} requires a different Python: "
|
||||
f"{version} not in {requires_python!r}"
|
||||
)
|
||||
|
||||
|
||||
@@ -231,9 +227,7 @@ class Resolver(BaseResolver):
|
||||
tags = compatibility_tags.get_supported()
|
||||
if requirement_set.check_supported_wheels and not wheel.supported(tags):
|
||||
raise InstallationError(
|
||||
"{} is not a supported wheel on this platform.".format(
|
||||
wheel.filename
|
||||
)
|
||||
f"{wheel.filename} is not a supported wheel on this platform."
|
||||
)
|
||||
|
||||
# This next bit is really a sanity check.
|
||||
@@ -248,9 +242,9 @@ class Resolver(BaseResolver):
|
||||
return [install_req], None
|
||||
|
||||
try:
|
||||
existing_req: Optional[
|
||||
InstallRequirement
|
||||
] = requirement_set.get_requirement(install_req.name)
|
||||
existing_req: Optional[InstallRequirement] = (
|
||||
requirement_set.get_requirement(install_req.name)
|
||||
)
|
||||
except KeyError:
|
||||
existing_req = None
|
||||
|
||||
@@ -265,9 +259,8 @@ class Resolver(BaseResolver):
|
||||
)
|
||||
if has_conflicting_requirement:
|
||||
raise InstallationError(
|
||||
"Double requirement given: {} (already in {}, name={!r})".format(
|
||||
install_req, existing_req, install_req.name
|
||||
)
|
||||
f"Double requirement given: {install_req} "
|
||||
f"(already in {existing_req}, name={install_req.name!r})"
|
||||
)
|
||||
|
||||
# When no existing requirement exists, add the requirement as a
|
||||
@@ -287,9 +280,9 @@ class Resolver(BaseResolver):
|
||||
)
|
||||
if does_not_satisfy_constraint:
|
||||
raise InstallationError(
|
||||
"Could not satisfy constraints for '{}': "
|
||||
f"Could not satisfy constraints for '{install_req.name}': "
|
||||
"installation from path or url cannot be "
|
||||
"constrained to a version".format(install_req.name)
|
||||
"constrained to a version"
|
||||
)
|
||||
# If we're now installing a constraint, mark the existing
|
||||
# object for real installation.
|
||||
@@ -325,6 +318,7 @@ 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
|
||||
@@ -398,9 +392,9 @@ class Resolver(BaseResolver):
|
||||
# "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}"
|
||||
).format(candidate=best_candidate, reason=reason)
|
||||
f"yanked version: {best_candidate}\n"
|
||||
f"Reason for being yanked: {reason}"
|
||||
)
|
||||
logger.warning(msg)
|
||||
|
||||
return link
|
||||
@@ -423,6 +417,8 @@ 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,
|
||||
@@ -536,6 +532,7 @@ 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
|
||||
|
||||
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,31 +1,29 @@
|
||||
from typing import FrozenSet, Iterable, Optional, Tuple, Union
|
||||
from dataclasses import dataclass
|
||||
from typing import FrozenSet, Iterable, Optional, Tuple
|
||||
|
||||
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 pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.packaging.version import 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]]
|
||||
CandidateVersion = Union[LegacyVersion, Version]
|
||||
|
||||
|
||||
def format_name(project: str, extras: FrozenSet[str]) -> str:
|
||||
def format_name(project: NormalizedName, extras: FrozenSet[NormalizedName]) -> str:
|
||||
if not extras:
|
||||
return project
|
||||
canonical_extras = sorted(canonicalize_name(e) for e in extras)
|
||||
return "{}[{}]".format(project, ",".join(canonical_extras))
|
||||
extras_expr = ",".join(sorted(extras))
|
||||
return f"{project}[{extras_expr}]"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Constraint:
|
||||
def __init__(
|
||||
self, specifier: SpecifierSet, hashes: Hashes, links: FrozenSet[Link]
|
||||
) -> None:
|
||||
self.specifier = specifier
|
||||
self.hashes = hashes
|
||||
self.links = links
|
||||
specifier: SpecifierSet
|
||||
hashes: Hashes
|
||||
links: FrozenSet[Link]
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> "Constraint":
|
||||
@@ -116,7 +114,7 @@ class Candidate:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
|
||||
@property
|
||||
def version(self) -> CandidateVersion:
|
||||
def version(self) -> Version:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
|
||||
@property
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import sys
|
||||
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
|
||||
|
||||
@@ -9,6 +10,7 @@ from pip._internal.exceptions import (
|
||||
HashError,
|
||||
InstallationSubprocessError,
|
||||
MetadataInconsistent,
|
||||
MetadataInvalid,
|
||||
)
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.models.link import Link, links_equivalent
|
||||
@@ -21,7 +23,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, CandidateVersion, Requirement, format_name
|
||||
from .base import Candidate, Requirement, format_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .factory import Factory
|
||||
@@ -145,7 +147,7 @@ class _InstallRequirementBackedCandidate(Candidate):
|
||||
ireq: InstallRequirement,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
version: Optional[Version] = None,
|
||||
) -> None:
|
||||
self._link = link
|
||||
self._source_link = source_link
|
||||
@@ -154,18 +156,20 @@ class _InstallRequirementBackedCandidate(Candidate):
|
||||
self._name = name
|
||||
self._version = version
|
||||
self.dist = self._prepare()
|
||||
self._hash: Optional[int] = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name} {self.version}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({link!r})".format(
|
||||
class_name=self.__class__.__name__,
|
||||
link=str(self._link),
|
||||
)
|
||||
return f"{self.__class__.__name__}({str(self._link)!r})"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.__class__, self._link))
|
||||
if self._hash is not None:
|
||||
return self._hash
|
||||
|
||||
self._hash = hash((self.__class__, self._link))
|
||||
return self._hash
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
@@ -188,16 +192,15 @@ class _InstallRequirementBackedCandidate(Candidate):
|
||||
return self.project_name
|
||||
|
||||
@property
|
||||
def version(self) -> CandidateVersion:
|
||||
def version(self) -> Version:
|
||||
if self._version is None:
|
||||
self._version = self.dist.version
|
||||
return self._version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return "{} {} (from {})".format(
|
||||
self.name,
|
||||
self.version,
|
||||
self._link.file_path if self._link.is_file else self._link,
|
||||
return (
|
||||
f"{self.name} {self.version} "
|
||||
f"(from {self._link.file_path if self._link.is_file else self._link})"
|
||||
)
|
||||
|
||||
def _prepare_distribution(self) -> BaseDistribution:
|
||||
@@ -219,6 +222,13 @@ 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:
|
||||
@@ -240,7 +250,7 @@ class _InstallRequirementBackedCandidate(Candidate):
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
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 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) -> Optional[InstallRequirement]:
|
||||
@@ -256,7 +266,7 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
|
||||
template: InstallRequirement,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
version: Optional[Version] = None,
|
||||
) -> None:
|
||||
source_link = link
|
||||
cache_entry = factory.get_wheel_cache_entry(source_link, name)
|
||||
@@ -272,9 +282,9 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
|
||||
# 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
|
||||
), f"{version!r} != {wheel_version!r} for wheel {name}"
|
||||
|
||||
if cache_entry is not None:
|
||||
assert ireq.link.is_wheel
|
||||
@@ -313,7 +323,7 @@ class EditableCandidate(_InstallRequirementBackedCandidate):
|
||||
template: InstallRequirement,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
version: Optional[Version] = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
link=link,
|
||||
@@ -354,18 +364,15 @@ class AlreadyInstalledCandidate(Candidate):
|
||||
return str(self.dist)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({distribution!r})".format(
|
||||
class_name=self.__class__.__name__,
|
||||
distribution=self.dist,
|
||||
)
|
||||
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.__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
|
||||
return hash((self.name, self.version))
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
@@ -376,7 +383,7 @@ class AlreadyInstalledCandidate(Candidate):
|
||||
return self.project_name
|
||||
|
||||
@property
|
||||
def version(self) -> CandidateVersion:
|
||||
def version(self) -> Version:
|
||||
if self._version is None:
|
||||
self._version = self.dist.version
|
||||
return self._version
|
||||
@@ -392,7 +399,7 @@ class AlreadyInstalledCandidate(Candidate):
|
||||
if not with_requires:
|
||||
return
|
||||
for r in self.dist.iter_dependencies():
|
||||
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
|
||||
yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
return None
|
||||
@@ -427,20 +434,27 @@ class ExtrasCandidate(Candidate):
|
||||
self,
|
||||
base: BaseCandidate,
|
||||
extras: FrozenSet[str],
|
||||
*,
|
||||
comes_from: Optional[InstallRequirement] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param comes_from: the InstallRequirement that led to this candidate if it
|
||||
differs from the base's InstallRequirement. This will often be the
|
||||
case in the sense that this candidate's requirement has the extras
|
||||
while the base's does not. Unlike the InstallRequirement backed
|
||||
candidates, this requirement is used solely for reporting purposes,
|
||||
it does not do any leg work.
|
||||
"""
|
||||
self.base = base
|
||||
self.extras = extras
|
||||
self.extras = frozenset(canonicalize_name(e) for e in extras)
|
||||
self._comes_from = comes_from if comes_from is not None else self.base._ireq
|
||||
|
||||
def __str__(self) -> str:
|
||||
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(
|
||||
class_name=self.__class__.__name__,
|
||||
base=self.base,
|
||||
extras=self.extras,
|
||||
)
|
||||
return f"{self.__class__.__name__}(base={self.base!r}, extras={self.extras!r})"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.base, self.extras))
|
||||
@@ -460,7 +474,7 @@ class ExtrasCandidate(Candidate):
|
||||
return format_name(self.base.project_name, self.extras)
|
||||
|
||||
@property
|
||||
def version(self) -> CandidateVersion:
|
||||
def version(self) -> Version:
|
||||
return self.base.version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
@@ -502,11 +516,11 @@ 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
|
||||
yield from factory.make_requirements_from_spec(
|
||||
str(r),
|
||||
self._comes_from,
|
||||
valid_extras,
|
||||
)
|
||||
if requirement:
|
||||
yield requirement
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
# We don't return anything here, because we always
|
||||
@@ -542,7 +556,7 @@ class RequiresPythonCandidate(Candidate):
|
||||
return REQUIRES_PYTHON_IDENTIFIER
|
||||
|
||||
@property
|
||||
def version(self) -> CandidateVersion:
|
||||
def version(self) -> Version:
|
||||
return self._version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
|
||||
@@ -3,6 +3,7 @@ import functools
|
||||
import logging
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
FrozenSet,
|
||||
Iterable,
|
||||
@@ -11,6 +12,7 @@ from typing import (
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Protocol,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
@@ -21,6 +23,7 @@ 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 Version
|
||||
from pip._vendor.resolvelib import ResolutionImpossible
|
||||
|
||||
from pip._internal.cache import CacheEntry, WheelCache
|
||||
@@ -28,6 +31,7 @@ from pip._internal.exceptions import (
|
||||
DistributionNotFound,
|
||||
InstallationError,
|
||||
MetadataInconsistent,
|
||||
MetadataInvalid,
|
||||
UnsupportedPythonVersion,
|
||||
UnsupportedWheel,
|
||||
)
|
||||
@@ -36,7 +40,10 @@ from pip._internal.metadata import BaseDistribution, 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.constructors import (
|
||||
install_req_drop_extras,
|
||||
install_req_from_link_and_ireq,
|
||||
)
|
||||
from pip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
check_invalid_constraint_type,
|
||||
@@ -47,7 +54,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, CandidateVersion, Constraint, Requirement
|
||||
from .base import Candidate, Constraint, Requirement
|
||||
from .candidates import (
|
||||
AlreadyInstalledCandidate,
|
||||
BaseCandidate,
|
||||
@@ -62,11 +69,11 @@ from .requirements import (
|
||||
ExplicitRequirement,
|
||||
RequiresPythonRequirement,
|
||||
SpecifierRequirement,
|
||||
SpecifierWithoutExtrasRequirement,
|
||||
UnsatisfiableRequirement,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
|
||||
class ConflictCause(Protocol):
|
||||
requirement: RequiresPythonRequirement
|
||||
@@ -112,8 +119,9 @@ class Factory:
|
||||
self._editable_candidate_cache: Cache[EditableCandidate] = {}
|
||||
self._installed_candidate_cache: Dict[str, AlreadyInstalledCandidate] = {}
|
||||
self._extras_candidate_cache: Dict[
|
||||
Tuple[int, FrozenSet[str]], ExtrasCandidate
|
||||
Tuple[int, FrozenSet[NormalizedName]], ExtrasCandidate
|
||||
] = {}
|
||||
self._supported_tags_cache = get_supported()
|
||||
|
||||
if not ignore_installed:
|
||||
env = get_default_environment()
|
||||
@@ -132,19 +140,23 @@ class Factory:
|
||||
if not link.is_wheel:
|
||||
return
|
||||
wheel = Wheel(link.filename)
|
||||
if wheel.supported(self._finder.target_python.get_tags()):
|
||||
if wheel.supported(self._finder.target_python.get_unsorted_tags()):
|
||||
return
|
||||
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],
|
||||
*,
|
||||
comes_from: Optional[InstallRequirement] = None,
|
||||
) -> ExtrasCandidate:
|
||||
cache_key = (id(base), extras)
|
||||
cache_key = (id(base), frozenset(canonicalize_name(e) for e in extras))
|
||||
try:
|
||||
candidate = self._extras_candidate_cache[cache_key]
|
||||
except KeyError:
|
||||
candidate = ExtrasCandidate(base, extras)
|
||||
candidate = ExtrasCandidate(base, extras, comes_from=comes_from)
|
||||
self._extras_candidate_cache[cache_key] = candidate
|
||||
return candidate
|
||||
|
||||
@@ -161,7 +173,7 @@ class Factory:
|
||||
self._installed_candidate_cache[dist.canonical_name] = base
|
||||
if not extras:
|
||||
return base
|
||||
return self._make_extras_candidate(base, extras)
|
||||
return self._make_extras_candidate(base, extras, comes_from=template)
|
||||
|
||||
def _make_candidate_from_link(
|
||||
self,
|
||||
@@ -169,8 +181,22 @@ class Factory:
|
||||
extras: FrozenSet[str],
|
||||
template: InstallRequirement,
|
||||
name: Optional[NormalizedName],
|
||||
version: Optional[CandidateVersion],
|
||||
version: Optional[Version],
|
||||
) -> Optional[Candidate]:
|
||||
base: Optional[BaseCandidate] = self._make_base_candidate_from_link(
|
||||
link, template, name, version
|
||||
)
|
||||
if not extras or base is None:
|
||||
return base
|
||||
return self._make_extras_candidate(base, extras, comes_from=template)
|
||||
|
||||
def _make_base_candidate_from_link(
|
||||
self,
|
||||
link: Link,
|
||||
template: InstallRequirement,
|
||||
name: Optional[NormalizedName],
|
||||
version: Optional[Version],
|
||||
) -> Optional[BaseCandidate]:
|
||||
# TODO: Check already installed candidate, and use it if the link and
|
||||
# editable flag match.
|
||||
|
||||
@@ -189,7 +215,7 @@ class Factory:
|
||||
name=name,
|
||||
version=version,
|
||||
)
|
||||
except MetadataInconsistent as e:
|
||||
except (MetadataInconsistent, MetadataInvalid) as e:
|
||||
logger.info(
|
||||
"Discarding [blue underline]%s[/]: [yellow]%s[reset]",
|
||||
link,
|
||||
@@ -199,7 +225,7 @@ class Factory:
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
|
||||
base: BaseCandidate = self._editable_candidate_cache[link]
|
||||
return self._editable_candidate_cache[link]
|
||||
else:
|
||||
if link not in self._link_candidate_cache:
|
||||
try:
|
||||
@@ -219,11 +245,7 @@ class Factory:
|
||||
)
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
base = self._link_candidate_cache[link]
|
||||
|
||||
if not extras:
|
||||
return base
|
||||
return self._make_extras_candidate(base, extras)
|
||||
return self._link_candidate_cache[link]
|
||||
|
||||
def _iter_found_candidates(
|
||||
self,
|
||||
@@ -357,9 +379,8 @@ class Factory:
|
||||
"""
|
||||
for link in constraint.links:
|
||||
self._fail_if_link_is_unsupported_wheel(link)
|
||||
candidate = self._make_candidate_from_link(
|
||||
candidate = self._make_base_candidate_from_link(
|
||||
link,
|
||||
extras=frozenset(),
|
||||
template=install_req_from_link_and_ireq(link, template),
|
||||
name=canonicalize_name(identifier),
|
||||
version=None,
|
||||
@@ -374,6 +395,7 @@ 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()
|
||||
@@ -385,16 +407,21 @@ class Factory:
|
||||
if ireq is not None:
|
||||
ireqs.append(ireq)
|
||||
|
||||
# If the current identifier contains extras, add explicit candidates
|
||||
# from entries from extra-less identifier.
|
||||
# If the current identifier contains extras, add requires and explicit
|
||||
# candidates from entries from extra-less identifier.
|
||||
with contextlib.suppress(InvalidRequirement):
|
||||
parsed_requirement = get_requirement(identifier)
|
||||
explicit_candidates.update(
|
||||
self._iter_explicit_candidates_from_base(
|
||||
requirements.get(parsed_requirement.name, ()),
|
||||
frozenset(parsed_requirement.extras),
|
||||
),
|
||||
)
|
||||
if parsed_requirement.name != identifier:
|
||||
explicit_candidates.update(
|
||||
self._iter_explicit_candidates_from_base(
|
||||
requirements.get(parsed_requirement.name, ()),
|
||||
frozenset(parsed_requirement.extras),
|
||||
),
|
||||
)
|
||||
for req in requirements.get(parsed_requirement.name, []):
|
||||
_, ireq = req.get_candidate_lookup()
|
||||
if ireq is not None:
|
||||
ireqs.append(ireq)
|
||||
|
||||
# Add explicit candidates from constraints. We only do this if there are
|
||||
# known ireqs, which represent requirements not already explicit. If
|
||||
@@ -434,40 +461,61 @@ class Factory:
|
||||
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])
|
||||
and all(is_satisfied_by(req, c) for req in requirements[identifier])
|
||||
)
|
||||
|
||||
def _make_requirement_from_install_req(
|
||||
def _make_requirements_from_install_req(
|
||||
self, ireq: InstallRequirement, requested_extras: Iterable[str]
|
||||
) -> Optional[Requirement]:
|
||||
) -> Iterator[Requirement]:
|
||||
"""
|
||||
Returns requirement objects associated with the given InstallRequirement. In
|
||||
most cases this will be a single object but the following special cases exist:
|
||||
- the InstallRequirement has markers that do not apply -> result is empty
|
||||
- the InstallRequirement has both a constraint (or link) and extras
|
||||
-> result is split in two requirement objects: one with the constraint
|
||||
(or link) and one with the extra. This allows centralized constraint
|
||||
handling for the base, resulting in fewer candidate rejections.
|
||||
"""
|
||||
if not ireq.match_markers(requested_extras):
|
||||
logger.info(
|
||||
"Ignoring %s: markers '%s' don't match your environment",
|
||||
ireq.name,
|
||||
ireq.markers,
|
||||
)
|
||||
return None
|
||||
if not ireq.link:
|
||||
return SpecifierRequirement(ireq)
|
||||
self._fail_if_link_is_unsupported_wheel(ireq.link)
|
||||
cand = self._make_candidate_from_link(
|
||||
ireq.link,
|
||||
extras=frozenset(ireq.extras),
|
||||
template=ireq,
|
||||
name=canonicalize_name(ireq.name) if ireq.name else None,
|
||||
version=None,
|
||||
)
|
||||
if cand is None:
|
||||
# There's no way we can satisfy a URL requirement if the underlying
|
||||
# candidate fails to build. An unnamed URL must be user-supplied, so
|
||||
# we fail eagerly. If the URL is named, an unsatisfiable requirement
|
||||
# can make the resolver do the right thing, either backtrack (and
|
||||
# maybe find some other requirement that's buildable) or raise a
|
||||
# ResolutionImpossible eventually.
|
||||
if not ireq.name:
|
||||
raise self._build_failures[ireq.link]
|
||||
return UnsatisfiableRequirement(canonicalize_name(ireq.name))
|
||||
return self.make_requirement_from_candidate(cand)
|
||||
elif not ireq.link:
|
||||
if ireq.extras and ireq.req is not None and ireq.req.specifier:
|
||||
yield SpecifierWithoutExtrasRequirement(ireq)
|
||||
yield SpecifierRequirement(ireq)
|
||||
else:
|
||||
self._fail_if_link_is_unsupported_wheel(ireq.link)
|
||||
# Always make the link candidate for the base requirement to make it
|
||||
# available to `find_candidates` for explicit candidate lookup for any
|
||||
# set of extras.
|
||||
# The extras are required separately via a second requirement.
|
||||
cand = self._make_base_candidate_from_link(
|
||||
ireq.link,
|
||||
template=install_req_drop_extras(ireq) if ireq.extras else ireq,
|
||||
name=canonicalize_name(ireq.name) if ireq.name else None,
|
||||
version=None,
|
||||
)
|
||||
if cand is None:
|
||||
# There's no way we can satisfy a URL requirement if the underlying
|
||||
# candidate fails to build. An unnamed URL must be user-supplied, so
|
||||
# we fail eagerly. If the URL is named, an unsatisfiable requirement
|
||||
# can make the resolver do the right thing, either backtrack (and
|
||||
# maybe find some other requirement that's buildable) or raise a
|
||||
# ResolutionImpossible eventually.
|
||||
if not ireq.name:
|
||||
raise self._build_failures[ireq.link]
|
||||
yield UnsatisfiableRequirement(canonicalize_name(ireq.name))
|
||||
else:
|
||||
# require the base from the link
|
||||
yield self.make_requirement_from_candidate(cand)
|
||||
if ireq.extras:
|
||||
# require the extras on top of the base candidate
|
||||
yield self.make_requirement_from_candidate(
|
||||
self._make_extras_candidate(cand, frozenset(ireq.extras))
|
||||
)
|
||||
|
||||
def collect_root_requirements(
|
||||
self, root_ireqs: List[InstallRequirement]
|
||||
@@ -488,15 +536,27 @@ class Factory:
|
||||
else:
|
||||
collected.constraints[name] = Constraint.from_ireq(ireq)
|
||||
else:
|
||||
req = self._make_requirement_from_install_req(
|
||||
ireq,
|
||||
requested_extras=(),
|
||||
reqs = list(
|
||||
self._make_requirements_from_install_req(
|
||||
ireq,
|
||||
requested_extras=(),
|
||||
)
|
||||
)
|
||||
if req is None:
|
||||
if not reqs:
|
||||
continue
|
||||
if ireq.user_supplied and req.name not in collected.user_requested:
|
||||
collected.user_requested[req.name] = i
|
||||
collected.requirements.append(req)
|
||||
template = reqs[0]
|
||||
if ireq.user_supplied and template.name not in collected.user_requested:
|
||||
collected.user_requested[template.name] = i
|
||||
collected.requirements.extend(reqs)
|
||||
# Put requirements with extras at the end of the root requires. This does not
|
||||
# affect resolvelib's picking preference but it does affect its initial criteria
|
||||
# population: by putting extras at the end we enable the candidate finder to
|
||||
# present resolvelib with a smaller set of candidates to resolvelib, already
|
||||
# taking into account any non-transient constraints on the associated base. This
|
||||
# means resolvelib will have fewer candidates to visit and reject.
|
||||
# Python's list sort is stable, meaning relative order is kept for objects with
|
||||
# the same key.
|
||||
collected.requirements.sort(key=lambda r: r.name != r.project_name)
|
||||
return collected
|
||||
|
||||
def make_requirement_from_candidate(
|
||||
@@ -504,14 +564,23 @@ class Factory:
|
||||
) -> ExplicitRequirement:
|
||||
return ExplicitRequirement(candidate)
|
||||
|
||||
def make_requirement_from_spec(
|
||||
def make_requirements_from_spec(
|
||||
self,
|
||||
specifier: str,
|
||||
comes_from: Optional[InstallRequirement],
|
||||
requested_extras: Iterable[str] = (),
|
||||
) -> Optional[Requirement]:
|
||||
) -> Iterator[Requirement]:
|
||||
"""
|
||||
Returns requirement objects associated with the given specifier. In most cases
|
||||
this will be a single object but the following special cases exist:
|
||||
- the specifier has markers that do not apply -> result is empty
|
||||
- the specifier has both a constraint and extras -> result is split
|
||||
in two requirement objects: one with the constraint and one with the
|
||||
extra. This allows centralized constraint handling for the base,
|
||||
resulting in fewer candidate rejections.
|
||||
"""
|
||||
ireq = self._make_install_req_from_spec(specifier, comes_from)
|
||||
return self._make_requirement_from_install_req(ireq, requested_extras)
|
||||
return self._make_requirements_from_install_req(ireq, requested_extras)
|
||||
|
||||
def make_requires_python_requirement(
|
||||
self,
|
||||
@@ -540,7 +609,7 @@ class Factory:
|
||||
return self._wheel_cache.get_cache_entry(
|
||||
link=link,
|
||||
package_name=name,
|
||||
supported_tags=get_supported(),
|
||||
supported_tags=self._supported_tags_cache,
|
||||
)
|
||||
|
||||
def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[BaseDistribution]:
|
||||
@@ -603,8 +672,26 @@ class Factory:
|
||||
|
||||
cands = self._finder.find_all_candidates(req.project_name)
|
||||
skipped_by_requires_python = self._finder.requires_python_skipped_reasons()
|
||||
versions = [str(v) for v in sorted({c.version for c in cands})]
|
||||
|
||||
versions_set: Set[Version] = set()
|
||||
yanked_versions_set: Set[Version] = set()
|
||||
for c in cands:
|
||||
is_yanked = c.link.is_yanked if c.link else False
|
||||
if is_yanked:
|
||||
yanked_versions_set.add(c.version)
|
||||
else:
|
||||
versions_set.add(c.version)
|
||||
|
||||
versions = [str(v) for v in sorted(versions_set)]
|
||||
yanked_versions = [str(v) for v in sorted(yanked_versions_set)]
|
||||
|
||||
if yanked_versions:
|
||||
# Saying "version X is yanked" isn't entirely accurate.
|
||||
# https://github.com/pypa/pip/issues/11745#issuecomment-1402805842
|
||||
logger.critical(
|
||||
"Ignored the following yanked versions: %s",
|
||||
", ".join(yanked_versions) or "none",
|
||||
)
|
||||
if skipped_by_requires_python:
|
||||
logger.critical(
|
||||
"Ignored the following versions that require a different python "
|
||||
@@ -692,8 +779,8 @@ class Factory:
|
||||
info = "the requested packages"
|
||||
|
||||
msg = (
|
||||
"Cannot install {} because these package versions "
|
||||
"have conflicting dependencies.".format(info)
|
||||
f"Cannot install {info} because these package versions "
|
||||
"have conflicting dependencies."
|
||||
)
|
||||
logger.critical(msg)
|
||||
msg = "\nThe conflict is caused by:"
|
||||
@@ -717,7 +804,7 @@ class Factory:
|
||||
+ "\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 "
|
||||
+ "2. remove package versions to allow pip to attempt to solve "
|
||||
+ "the dependency conflict\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,13 +9,18 @@ something.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import logging
|
||||
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]]]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -44,11 +49,25 @@ def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
|
||||
for version, func in infos:
|
||||
if version in versions_found:
|
||||
continue
|
||||
candidate = func()
|
||||
if candidate is None:
|
||||
continue
|
||||
yield candidate
|
||||
versions_found.add(version)
|
||||
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)
|
||||
|
||||
|
||||
def _iter_built_with_prepended(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import collections
|
||||
import math
|
||||
from functools import lru_cache
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
@@ -234,8 +235,10 @@ class PipProvider(_ProviderBase):
|
||||
constraint=constraint,
|
||||
prefers_installed=(not _eligible_for_upgrade(identifier)),
|
||||
incompatibilities=incompatibilities,
|
||||
is_satisfied_by=self.is_satisfied_by,
|
||||
)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool:
|
||||
return requirement.is_satisfied_by(candidate)
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ class PipDebuggingReporter(BaseReporter):
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
|
||||
from pip._internal.req.constructors import install_req_drop_extras
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
|
||||
from .base import Candidate, CandidateLookup, Requirement, format_name
|
||||
@@ -14,10 +17,15 @@ class ExplicitRequirement(Requirement):
|
||||
return str(self.candidate)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({candidate!r})".format(
|
||||
class_name=self.__class__.__name__,
|
||||
candidate=self.candidate,
|
||||
)
|
||||
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:
|
||||
@@ -43,16 +51,35 @@ 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._extras = frozenset(ireq.extras)
|
||||
self._equal_cache: Optional[str] = None
|
||||
self._hash: Optional[int] = 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 "{class_name}({requirement!r})".format(
|
||||
class_name=self.__class__.__name__,
|
||||
requirement=str(self._ireq.req),
|
||||
)
|
||||
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:
|
||||
@@ -92,20 +119,68 @@ class SpecifierRequirement(Requirement):
|
||||
return spec.contains(candidate.version, prereleases=True)
|
||||
|
||||
|
||||
class SpecifierWithoutExtrasRequirement(SpecifierRequirement):
|
||||
"""
|
||||
Requirement backed by an install requirement on a base package.
|
||||
Trims extras from its install requirement if there are any.
|
||||
"""
|
||||
|
||||
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: Optional[str] = None
|
||||
self._hash: Optional[int] = 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: Optional[int] = None
|
||||
self._candidate = match
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Python {self.specifier}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({specifier!r})".format(
|
||||
class_name=self.__class__.__name__,
|
||||
specifier=str(self.specifier),
|
||||
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
|
||||
@@ -142,10 +217,15 @@ class UnsatisfiableRequirement(Requirement):
|
||||
return f"{self._name} (unavailable)"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({name!r})".format(
|
||||
class_name=self.__class__.__name__,
|
||||
name=str(self._name),
|
||||
)
|
||||
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:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import contextlib
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
@@ -11,6 +12,7 @@ from pip._vendor.resolvelib.structs import DirectedGraph
|
||||
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.constructors import install_req_extend_extras
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_set import RequirementSet
|
||||
from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
|
||||
@@ -19,6 +21,7 @@ from pip._internal.resolution.resolvelib.reporter import (
|
||||
PipDebuggingReporter,
|
||||
PipReporter,
|
||||
)
|
||||
from pip._internal.utils.packaging import get_requirement
|
||||
|
||||
from .base import Candidate, Requirement
|
||||
from .factory import Factory
|
||||
@@ -101,9 +104,24 @@ class Resolver(BaseResolver):
|
||||
raise error from e
|
||||
|
||||
req_set = RequirementSet(check_supported_wheels=check_supported_wheels)
|
||||
for candidate in result.mapping.values():
|
||||
# process candidates with extras last to ensure their base equivalent is
|
||||
# already in the req_set if appropriate.
|
||||
# Python's sort is stable so using a binary key function keeps relative order
|
||||
# within both subsets.
|
||||
for candidate in sorted(
|
||||
result.mapping.values(), key=lambda c: c.name != c.project_name
|
||||
):
|
||||
ireq = candidate.get_install_requirement()
|
||||
if ireq is None:
|
||||
if candidate.name != candidate.project_name:
|
||||
# extend existing req's extras
|
||||
with contextlib.suppress(KeyError):
|
||||
req = req_set.get_requirement(candidate.project_name)
|
||||
req_set.add_named_requirement(
|
||||
install_req_extend_extras(
|
||||
req, get_requirement(candidate.name).extras
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
# Check if there is already an installation under the same name,
|
||||
|
||||
Reference in New Issue
Block a user