Skip to content

Commit a2c40d4

Browse files
committed
Move Requires Python candidate check to narrow_requirement_selection
1 parent 5aeda3b commit a2c40d4

File tree

2 files changed

+74
-19
lines changed

2 files changed

+74
-19
lines changed

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

+22-5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,28 @@ def __init__(
103103
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
104104
return requirement_or_candidate.name
105105

106+
def narrow_requirement_selection(
107+
self,
108+
identifiers: Iterable[str],
109+
resolutions: Mapping[str, Candidate],
110+
candidates: Mapping[str, Iterator[Candidate]],
111+
information: Mapping[str, Iterator["PreferenceInformation"]],
112+
backtrack_causes: Sequence["PreferenceInformation"],
113+
) -> Iterable[str]:
114+
"""Produce a subset of identifiers that should be considered before others.
115+
116+
Currently pip narrows the following selection:
117+
* Requires-Python is always considered first.
118+
"""
119+
for identifier in identifiers:
120+
# Requires-Python has only one candidate and the check is basically
121+
# free, so we always do it first to avoid needless work if it fails.
122+
# This skips calling get_preference() for all other identifiers.
123+
if identifier == REQUIRES_PYTHON_IDENTIFIER:
124+
return [identifier]
125+
126+
return identifiers
127+
106128
def get_preference(
107129
self,
108130
identifier: str,
@@ -153,17 +175,12 @@ def get_preference(
153175
unfree = bool(operators)
154176
requested_order = self._user_requested.get(identifier, math.inf)
155177

156-
# Requires-Python has only one candidate and the check is basically
157-
# free, so we always do it first to avoid needless work if it fails.
158-
requires_python = identifier == REQUIRES_PYTHON_IDENTIFIER
159-
160178
# Prefer the causes of backtracking on the assumption that the problem
161179
# resolving the dependency tree is related to the failures that caused
162180
# the backtracking
163181
backtrack_cause = self.is_backtrack_cause(identifier, backtrack_causes)
164182

165183
return (
166-
not requires_python,
167184
not direct,
168185
not pinned,
169186
not backtrack_cause,

tests/unit/resolution_resolvelib/test_provider.py

+52-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import math
2-
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Sequence
2+
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Sequence
33

44
import pytest
55

@@ -36,53 +36,45 @@ def build_req_info(
3636
@pytest.mark.parametrize(
3737
"identifier, information, backtrack_causes, user_requested, expected",
3838
[
39-
# Test case for REQUIRES_PYTHON_IDENTIFIER
40-
(
41-
REQUIRES_PYTHON_IDENTIFIER,
42-
{REQUIRES_PYTHON_IDENTIFIER: [build_req_info("python")]},
43-
[],
44-
{},
45-
(False, False, True, True, math.inf, True, REQUIRES_PYTHON_IDENTIFIER),
46-
),
4739
# Pinned package with "=="
4840
(
4941
"pinned-package",
5042
{"pinned-package": [build_req_info("pinned-package==1.0")]},
5143
[],
5244
{},
53-
(True, False, False, True, math.inf, False, "pinned-package"),
45+
(False, False, True, math.inf, False, "pinned-package"),
5446
),
5547
# Package that caused backtracking
5648
(
5749
"backtrack-package",
5850
{"backtrack-package": [build_req_info("backtrack-package")]},
5951
[build_req_info("backtrack-package")],
6052
{},
61-
(True, False, True, False, math.inf, True, "backtrack-package"),
53+
(False, True, False, math.inf, True, "backtrack-package"),
6254
),
6355
# Root package requested by user
6456
(
6557
"root-package",
6658
{"root-package": [build_req_info("root-package")]},
6759
[],
6860
{"root-package": 1},
69-
(True, False, True, True, 1, True, "root-package"),
61+
(False, True, True, 1, True, "root-package"),
7062
),
7163
# Unfree package (with specifier operator)
7264
(
7365
"unfree-package",
7466
{"unfree-package": [build_req_info("unfree-package<1")]},
7567
[],
7668
{},
77-
(True, False, True, True, math.inf, False, "unfree-package"),
69+
(False, True, True, math.inf, False, "unfree-package"),
7870
),
7971
# Free package (no operator)
8072
(
8173
"free-package",
8274
{"free-package": [build_req_info("free-package")]},
8375
[],
8476
{},
85-
(True, False, True, True, math.inf, True, "free-package"),
77+
(False, True, True, math.inf, True, "free-package"),
8678
),
8779
],
8880
)
@@ -107,3 +99,49 @@ def test_get_preference(
10799
)
108100

109101
assert preference == expected, f"Expected {expected}, got {preference}"
102+
103+
104+
@pytest.mark.parametrize(
105+
"identifiers, expected",
106+
[
107+
# Case 1: REQUIRES_PYTHON_IDENTIFIER is present at the beginning
108+
(
109+
[REQUIRES_PYTHON_IDENTIFIER, "package1", "package2"],
110+
[REQUIRES_PYTHON_IDENTIFIER],
111+
),
112+
# Case 2: REQUIRES_PYTHON_IDENTIFIER is present in the middle
113+
(
114+
["package1", REQUIRES_PYTHON_IDENTIFIER, "package2"],
115+
[REQUIRES_PYTHON_IDENTIFIER],
116+
),
117+
# Case 3: REQUIRES_PYTHON_IDENTIFIER is not present
118+
(
119+
["package1", "package2"],
120+
["package1", "package2"],
121+
),
122+
# Case 4: Empty list of identifiers
123+
(
124+
[],
125+
[],
126+
),
127+
],
128+
)
129+
def test_narrow_requirement_selection(
130+
identifiers: List[str],
131+
expected: List[str],
132+
factory: Factory,
133+
) -> None:
134+
"""Test that narrow_requirement_selection correctly prioritizes
135+
REQUIRES_PYTHON_IDENTIFIER when present in the list of identifiers.
136+
"""
137+
provider = PipProvider(
138+
factory=factory,
139+
constraints={},
140+
ignore_dependencies=False,
141+
upgrade_strategy="to-satisfy-only",
142+
user_requested={},
143+
)
144+
145+
result = provider.narrow_requirement_selection(identifiers, {}, {}, {}, [])
146+
147+
assert list(result) == expected, f"Expected {expected}, got {list(result)}"

0 commit comments

Comments
 (0)