Skip to content

Commit 76335ad

Browse files
committed
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.
1 parent b9066d4 commit 76335ad

File tree

2 files changed

+51
-23
lines changed

2 files changed

+51
-23
lines changed

src/pip/_internal/resolution/resolvelib/candidates.py

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,11 @@ class ExtrasCandidate(Candidate):
423423
def __init__(
424424
self,
425425
base: BaseCandidate,
426-
extras: FrozenSet[NormalizedName],
426+
extras: FrozenSet[str],
427427
) -> None:
428428
self.base = base
429-
self.extras = extras
429+
self.extras = frozenset(canonicalize_name(e) for e in extras)
430+
self._unnormalized_extras = extras.difference(self.extras)
430431

431432
def __str__(self) -> str:
432433
name, rest = str(self.base).split(" ", 1)
@@ -477,6 +478,44 @@ def is_editable(self) -> bool:
477478
def source_link(self) -> Optional[Link]:
478479
return self.base.source_link
479480

481+
def _warn_invalid_extras(
482+
self,
483+
requested: FrozenSet[str],
484+
provided: FrozenSet[str],
485+
) -> None:
486+
"""Emit warnings for invalid extras being requested.
487+
488+
This emits a warning for each requested extra that is not in the
489+
candidate's ``Provides-Extra`` list.
490+
"""
491+
invalid_extras_to_warn = requested.difference(
492+
provided,
493+
# If an extra is requested in an unnormalized form, skip warning
494+
# about the normalized form being missing.
495+
(canonicalize_name(e) for e in self._unnormalized_extras),
496+
)
497+
if not invalid_extras_to_warn:
498+
return
499+
for extra in sorted(invalid_extras_to_warn):
500+
logger.warning(
501+
"%s %s does not provide the extra '%s'",
502+
self.base.name,
503+
self.version,
504+
extra,
505+
)
506+
507+
def _calculate_valid_requested_extras(self) -> FrozenSet[str]:
508+
"""Get a list of valid extras requested by this candidate.
509+
510+
The user (or upstream dependant) may have specified extras that the
511+
candidate doesn't support. Any unsupported extras are dropped, and each
512+
cause a warning to be logged here.
513+
"""
514+
requested_extras = self.extras.union(self._unnormalized_extras)
515+
provided_extras = frozenset(self.base.dist.iter_provided_extras())
516+
self._warn_invalid_extras(requested_extras, provided_extras)
517+
return requested_extras.intersection(provided_extras)
518+
480519
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
481520
factory = self.base._factory
482521

@@ -486,18 +525,7 @@ def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requiremen
486525
if not with_requires:
487526
return
488527

489-
# The user may have specified extras that the candidate doesn't
490-
# support. We ignore any unsupported extras here.
491-
valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras())
492-
invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras())
493-
for extra in sorted(invalid_extras):
494-
logger.warning(
495-
"%s %s does not provide the extra '%s'",
496-
self.base.name,
497-
self.version,
498-
extra,
499-
)
500-
528+
valid_extras = self._calculate_valid_requested_extras()
501529
for r in self.base.dist.iter_dependencies(valid_extras):
502530
requirement = factory.make_requirement_from_spec(
503531
str(r), self.base._ireq, valid_extras

src/pip/_internal/resolution/resolvelib/factory.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,9 @@ def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None:
140140
def _make_extras_candidate(
141141
self,
142142
base: BaseCandidate,
143-
extras: FrozenSet[NormalizedName],
143+
extras: FrozenSet[str],
144144
) -> ExtrasCandidate:
145-
cache_key = (id(base), extras)
145+
cache_key = (id(base), frozenset(canonicalize_name(e) for e in extras))
146146
try:
147147
candidate = self._extras_candidate_cache[cache_key]
148148
except KeyError:
@@ -153,7 +153,7 @@ def _make_extras_candidate(
153153
def _make_candidate_from_dist(
154154
self,
155155
dist: BaseDistribution,
156-
extras: FrozenSet[NormalizedName],
156+
extras: FrozenSet[str],
157157
template: InstallRequirement,
158158
) -> Candidate:
159159
try:
@@ -168,7 +168,7 @@ def _make_candidate_from_dist(
168168
def _make_candidate_from_link(
169169
self,
170170
link: Link,
171-
extras: FrozenSet[NormalizedName],
171+
extras: FrozenSet[str],
172172
template: InstallRequirement,
173173
name: Optional[NormalizedName],
174174
version: Optional[CandidateVersion],
@@ -246,12 +246,12 @@ def _iter_found_candidates(
246246
assert template.req, "Candidates found on index must be PEP 508"
247247
name = canonicalize_name(template.req.name)
248248

249-
extras: FrozenSet[NormalizedName] = frozenset()
249+
extras: FrozenSet[str] = frozenset()
250250
for ireq in ireqs:
251251
assert ireq.req, "Candidates found on index must be PEP 508"
252252
specifier &= ireq.req.specifier
253253
hashes &= ireq.hashes(trust_internet=False)
254-
extras |= frozenset(canonicalize_name(e) for e in ireq.extras)
254+
extras |= frozenset(ireq.extras)
255255

256256
def _get_installed_candidate() -> Optional[Candidate]:
257257
"""Get the candidate for the currently-installed version."""
@@ -327,7 +327,7 @@ def is_pinned(specifier: SpecifierSet) -> bool:
327327
def _iter_explicit_candidates_from_base(
328328
self,
329329
base_requirements: Iterable[Requirement],
330-
extras: FrozenSet[NormalizedName],
330+
extras: FrozenSet[str],
331331
) -> Iterator[Candidate]:
332332
"""Produce explicit candidates from the base given an extra-ed package.
333333
@@ -394,7 +394,7 @@ def find_candidates(
394394
explicit_candidates.update(
395395
self._iter_explicit_candidates_from_base(
396396
requirements.get(parsed_requirement.name, ()),
397-
frozenset(canonicalize_name(e) for e in parsed_requirement.extras),
397+
frozenset(parsed_requirement.extras),
398398
),
399399
)
400400

@@ -454,7 +454,7 @@ def _make_requirement_from_install_req(
454454
self._fail_if_link_is_unsupported_wheel(ireq.link)
455455
cand = self._make_candidate_from_link(
456456
ireq.link,
457-
extras=frozenset(canonicalize_name(e) for e in ireq.extras),
457+
extras=frozenset(ireq.extras),
458458
template=ireq,
459459
name=canonicalize_name(ireq.name) if ireq.name else None,
460460
version=None,

0 commit comments

Comments
 (0)