Skip to content

Commit 0305e0d

Browse files
committed
Implement extra-ed requirement merging
When a requirement is requested multiple times, some via a direct URL ("req @ URL") and some not but with extras ("req[extra] VERSION"), the resolver previous could not correctly find "req[extra]" if "req" is available in an index. This additional logic makes the resolver, when encountering a requirement with identifier "req[extra]", to also look for explicit candidates listed under "req", and add them as found matches for "req[extra]".
1 parent a31f8fd commit 0305e0d

File tree

3 files changed

+41
-0
lines changed

3 files changed

+41
-0
lines changed

news/8785.bugfix.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
New resolver: When a requirement is requested both via a direct URL
2+
(``req @ URL``) and via version specifier with extras (``req[extra]``), the
3+
resolver will now be able to use the URL to correctly resolve the requirement
4+
with extras.

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@
3333
]
3434

3535

36+
def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
37+
"""The runtime version of BaseCandidate."""
38+
base_candidate_classes = (
39+
AlreadyInstalledCandidate,
40+
EditableCandidate,
41+
LinkCandidate,
42+
)
43+
if isinstance(candidate, base_candidate_classes):
44+
return candidate
45+
return None
46+
47+
3648
def make_install_req_from_link(link, template):
3749
# type: (Link, InstallRequirement) -> InstallRequirement
3850
assert not template.editable, "template is editable"

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
cast,
1717
)
1818

19+
from pip._vendor.packaging.requirements import InvalidRequirement
20+
from pip._vendor.packaging.requirements import Requirement as PackagingRequirement
1921
from pip._vendor.packaging.specifiers import SpecifierSet
2022
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
2123
from pip._vendor.pkg_resources import Distribution
@@ -54,6 +56,7 @@
5456
ExtrasCandidate,
5557
LinkCandidate,
5658
RequiresPythonCandidate,
59+
as_base_candidate,
5760
)
5861
from .found_candidates import FoundCandidates, IndexCandidateInfo
5962
from .requirements import (
@@ -337,6 +340,28 @@ def find_candidates(
337340

338341
explicit_candidates.add(candidate)
339342

343+
# If the current identifier contains extras, also add explicit
344+
# candidates from entries from extra-less identifier.
345+
try:
346+
identifier_req = PackagingRequirement(identifier)
347+
except InvalidRequirement:
348+
base_identifier = None
349+
extras: FrozenSet[str] = frozenset()
350+
else:
351+
base_identifier = identifier_req.name
352+
extras = frozenset(identifier_req.extras)
353+
if base_identifier and base_identifier in requirements:
354+
for req in requirements[base_identifier]:
355+
lookup_cand, _ = req.get_candidate_lookup()
356+
if lookup_cand is None: # Not explicit.
357+
continue
358+
# We've stripped extras from the identifier, and should always
359+
# get a BaseCandidate here, unless there's a bug elsewhere.
360+
base_cand = as_base_candidate(lookup_cand)
361+
assert base_cand is not None
362+
candidate = self._make_extras_candidate(base_cand, extras)
363+
explicit_candidates.add(candidate)
364+
340365
# If none of the requirements want an explicit candidate, we can ask
341366
# the finder for candidates.
342367
if not explicit_candidates:

0 commit comments

Comments
 (0)