Skip to content

Commit b4fb4f2

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 e66ca04 commit b4fb4f2

File tree

3 files changed

+36
-0
lines changed

3 files changed

+36
-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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@
3333
]
3434

3535

36+
def is_base_candidate(candidate: Candidate) -> bool:
37+
"""Runtime check for BaseCandidate."""
38+
return isinstance(
39+
candidate,
40+
(AlreadyInstalledCandidate, EditableCandidate, LinkCandidate),
41+
)
42+
43+
3644
def make_install_req_from_link(link, template):
3745
# type: (Link, InstallRequirement) -> InstallRequirement
3846
assert not template.editable, "template is editable"

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

Lines changed: 24 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
@@ -53,6 +55,7 @@
5355
ExtrasCandidate,
5456
LinkCandidate,
5557
RequiresPythonCandidate,
58+
is_base_candidate,
5659
)
5760
from .found_candidates import FoundCandidates, IndexCandidateInfo
5861
from .requirements import (
@@ -296,6 +299,27 @@ def find_candidates(
296299
if ireq is not None:
297300
ireqs.append(ireq)
298301

302+
# If the current identifier contains extras, also add explicit
303+
# candidates from entries from extra-less identifier.
304+
try:
305+
identifier_req = PackagingRequirement(identifier)
306+
except InvalidRequirement:
307+
base_identifier = None
308+
extras: FrozenSet[str] = frozenset()
309+
else:
310+
base_identifier = identifier_req.name
311+
extras = frozenset(identifier_req.extras)
312+
if base_identifier and base_identifier in requirements:
313+
for req in requirements[base_identifier]:
314+
base_cand, _ = req.get_candidate_lookup()
315+
if base_cand is None or not is_base_candidate(base_cand):
316+
continue
317+
candidate = self._make_extras_candidate(
318+
cast(BaseCandidate, base_cand),
319+
extras,
320+
)
321+
explicit_candidates.add(candidate)
322+
299323
# If none of the requirements want an explicit candidate, we can ask
300324
# the finder for candidates.
301325
if not explicit_candidates:

0 commit comments

Comments
 (0)