1
1
Fork 0
mirror of https://github.com/pypa/pip synced 2023-12-13 21:30:23 +01:00

Try to find dependencies from unnormalized extras

When an unnormalized extra is requested, try to look up dependencies
with both its raw and normalized forms, to maintain compatibility when
an extra is both specified and requested in a non-standard form.
This commit is contained in:
Tzu-ping Chung 2023-05-11 14:32:41 +08:00
parent b9066d4b00
commit d64190c5fb
2 changed files with 57 additions and 23 deletions

View file

@ -423,10 +423,17 @@ class ExtrasCandidate(Candidate):
def __init__(
self,
base: BaseCandidate,
extras: FrozenSet[NormalizedName],
extras: FrozenSet[str],
) -> None:
self.base = base
self.extras = extras
self.extras = frozenset(canonicalize_name(e) for e in extras)
# If any extras are requested in their non-normalized forms, keep track
# of their raw values. This is needed when we look up dependencies
# since PEP 685 has not been implemented for marker-matching, and using
# the non-normalized extra for lookup ensures the user can select a
# non-normalized extra in a package with its non-normalized form.
# TODO: Remove this when packaging is upgraded to support PEP 685.
self._unnormalized_extras = extras.difference(self.extras)
def __str__(self) -> str:
name, rest = str(self.base).split(" ", 1)
@ -477,6 +484,44 @@ class ExtrasCandidate(Candidate):
def source_link(self) -> Optional[Link]:
return self.base.source_link
def _warn_invalid_extras(
self,
requested: FrozenSet[str],
provided: FrozenSet[str],
) -> None:
"""Emit warnings for invalid extras being requested.
This emits a warning for each requested extra that is not in the
candidate's ``Provides-Extra`` list.
"""
invalid_extras_to_warn = requested.difference(
provided,
# If an extra is requested in an unnormalized form, skip warning
# about the normalized form being missing.
(canonicalize_name(e) for e in self._unnormalized_extras),
)
if not invalid_extras_to_warn:
return
for extra in sorted(invalid_extras_to_warn):
logger.warning(
"%s %s does not provide the extra '%s'",
self.base.name,
self.version,
extra,
)
def _calculate_valid_requested_extras(self) -> FrozenSet[str]:
"""Get a list of valid extras requested by this candidate.
The user (or upstream dependant) may have specified extras that the
candidate doesn't support. Any unsupported extras are dropped, and each
cause a warning to be logged here.
"""
requested_extras = self.extras.union(self._unnormalized_extras)
provided_extras = frozenset(self.base.dist.iter_provided_extras())
self._warn_invalid_extras(requested_extras, provided_extras)
return requested_extras.intersection(provided_extras)
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
factory = self.base._factory
@ -486,18 +531,7 @@ class ExtrasCandidate(Candidate):
if not with_requires:
return
# The user may have specified extras that the candidate doesn't
# support. We ignore any unsupported extras here.
valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras())
invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras())
for extra in sorted(invalid_extras):
logger.warning(
"%s %s does not provide the extra '%s'",
self.base.name,
self.version,
extra,
)
valid_extras = self._calculate_valid_requested_extras()
for r in self.base.dist.iter_dependencies(valid_extras):
requirement = factory.make_requirement_from_spec(
str(r), self.base._ireq, valid_extras

View file

@ -140,9 +140,9 @@ class Factory:
def _make_extras_candidate(
self,
base: BaseCandidate,
extras: FrozenSet[NormalizedName],
extras: FrozenSet[str],
) -> 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:
@ -153,7 +153,7 @@ class Factory:
def _make_candidate_from_dist(
self,
dist: BaseDistribution,
extras: FrozenSet[NormalizedName],
extras: FrozenSet[str],
template: InstallRequirement,
) -> Candidate:
try:
@ -168,7 +168,7 @@ class Factory:
def _make_candidate_from_link(
self,
link: Link,
extras: FrozenSet[NormalizedName],
extras: FrozenSet[str],
template: InstallRequirement,
name: Optional[NormalizedName],
version: Optional[CandidateVersion],
@ -246,12 +246,12 @@ class Factory:
assert template.req, "Candidates found on index must be PEP 508"
name = canonicalize_name(template.req.name)
extras: FrozenSet[NormalizedName] = frozenset()
extras: FrozenSet[str] = frozenset()
for ireq in ireqs:
assert ireq.req, "Candidates found on index must be PEP 508"
specifier &= ireq.req.specifier
hashes &= ireq.hashes(trust_internet=False)
extras |= frozenset(canonicalize_name(e) for e in ireq.extras)
extras |= frozenset(ireq.extras)
def _get_installed_candidate() -> Optional[Candidate]:
"""Get the candidate for the currently-installed version."""
@ -327,7 +327,7 @@ class Factory:
def _iter_explicit_candidates_from_base(
self,
base_requirements: Iterable[Requirement],
extras: FrozenSet[NormalizedName],
extras: FrozenSet[str],
) -> Iterator[Candidate]:
"""Produce explicit candidates from the base given an extra-ed package.
@ -394,7 +394,7 @@ class Factory:
explicit_candidates.update(
self._iter_explicit_candidates_from_base(
requirements.get(parsed_requirement.name, ()),
frozenset(canonicalize_name(e) for e in parsed_requirement.extras),
frozenset(parsed_requirement.extras),
),
)
@ -454,7 +454,7 @@ class Factory:
self._fail_if_link_is_unsupported_wheel(ireq.link)
cand = self._make_candidate_from_link(
ireq.link,
extras=frozenset(canonicalize_name(e) for e in ireq.extras),
extras=frozenset(ireq.extras),
template=ireq,
name=canonicalize_name(ireq.name) if ireq.name else None,
version=None,