Skip to content

Commit 26eaddc

Browse files
committed
Extract identifier cleanup for user_requested
1 parent c4d9f56 commit 26eaddc

File tree

2 files changed

+55
-19
lines changed

2 files changed

+55
-19
lines changed

news/10613.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
When a package is requested by the user for upgrade, correctly identify that
2+
the extra-ed variant of that same package depended by another user-requested
3+
package is requesting the same package, and upgrade it accordingly.

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

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import collections
22
import math
3-
from typing import TYPE_CHECKING, Dict, Iterable, Iterator, Mapping, Sequence, Union
3+
from typing import (
4+
TYPE_CHECKING,
5+
Dict,
6+
Iterable,
7+
Iterator,
8+
Mapping,
9+
Sequence,
10+
TypeVar,
11+
Union,
12+
)
413

514
from pip._vendor.resolvelib.providers import AbstractProvider
615

@@ -37,6 +46,35 @@
3746
# services to those objects (access to pip's finder and preparer).
3847

3948

49+
D = TypeVar("D")
50+
V = TypeVar("V")
51+
52+
53+
def _get_with_identifier(
54+
mapping: Mapping[str, V],
55+
identifier: str,
56+
default: D,
57+
) -> Union[D, V]:
58+
"""Get item from a package name lookup mapping with a resolver identifier.
59+
60+
This extra logic is needed when the target mapping is keyed by package
61+
name, which cannot be directly looked up with an identifier (which may
62+
contain requested extras). Additional logic is added to also look up a value
63+
by "cleaning up" the extras from the identifier.
64+
"""
65+
if identifier in mapping:
66+
return mapping[identifier]
67+
# HACK: Theoretically we should check whether this identifier is a valid
68+
# "NAME[EXTRAS]" format, and parse out the name part with packaging or
69+
# some regular expression. But since pip's resolver only spits out three
70+
# kinds of identifiers: normalized PEP 503 names, normalized names plus
71+
# extras, and Requires-Python, we can cheat a bit here.
72+
name, open_bracket, _ = identifier.partition("[")
73+
if open_bracket and name in mapping:
74+
return mapping[name]
75+
return default
76+
77+
4078
class PipProvider(_ProviderBase):
4179
"""Pip's provider implementation for resolvelib.
4280
@@ -150,28 +188,13 @@ def get_preference( # type: ignore
150188
identifier,
151189
)
152190

153-
def _get_constraint(self, identifier: str) -> Constraint:
154-
if identifier in self._constraints:
155-
return self._constraints[identifier]
156-
157-
# HACK: Theoretically we should check whether this identifier is a valid
158-
# "NAME[EXTRAS]" format, and parse out the name part with packaging or
159-
# some regular expression. But since pip's resolver only spits out
160-
# three kinds of identifiers: normalized PEP 503 names, normalized names
161-
# plus extras, and Requires-Python, we can cheat a bit here.
162-
name, open_bracket, _ = identifier.partition("[")
163-
if open_bracket and name in self._constraints:
164-
return self._constraints[name]
165-
166-
return Constraint.empty()
167-
168191
def find_matches(
169192
self,
170193
identifier: str,
171194
requirements: Mapping[str, Iterator[Requirement]],
172195
incompatibilities: Mapping[str, Iterator[Candidate]],
173196
) -> Iterable[Candidate]:
174-
def _eligible_for_upgrade(name: str) -> bool:
197+
def _eligible_for_upgrade(identifier: str) -> bool:
175198
"""Are upgrades allowed for this project?
176199
177200
This checks the upgrade strategy, and whether the project was one
@@ -185,13 +208,23 @@ def _eligible_for_upgrade(name: str) -> bool:
185208
if self._upgrade_strategy == "eager":
186209
return True
187210
elif self._upgrade_strategy == "only-if-needed":
188-
return name in self._user_requested
211+
user_order = _get_with_identifier(
212+
self._user_requested,
213+
identifier,
214+
default=None,
215+
)
216+
return user_order is not None
189217
return False
190218

219+
constraint = _get_with_identifier(
220+
self._constraints,
221+
identifier,
222+
default=Constraint.empty(),
223+
)
191224
return self._factory.find_candidates(
192225
identifier=identifier,
193226
requirements=requirements,
194-
constraint=self._get_constraint(identifier),
227+
constraint=constraint,
195228
prefers_installed=(not _eligible_for_upgrade(identifier)),
196229
incompatibilities=incompatibilities,
197230
)

0 commit comments

Comments
 (0)