Skip to content

Commit 7bc0e92

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix-direct-preference
2 parents 94fdffd + d4195e4 commit 7bc0e92

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+327
-317
lines changed

.github/workflows/update-rtd-redirects.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ name: Update documentation redirects
33
on:
44
push:
55
branches: [main]
6+
paths:
7+
- ".readthedocs-custom-redirects.yml"
8+
- ".readthedocs.yml"
69
schedule:
710
- cron: 0 0 * * MON # Run every Monday at 00:00 UTC
811

@@ -22,7 +25,6 @@ jobs:
2225
- uses: actions/setup-python@v5
2326
with:
2427
python-version: "3.11"
25-
- run: pip install httpx pyyaml rich
26-
- run: python tools/update-rtd-redirects.py
28+
- run: pipx run tools/update-rtd-redirects.py
2729
env:
2830
RTD_API_TOKEN: ${{ secrets.RTD_API_TOKEN }}

build-requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pyproject-hooks==1.2.0 \
1818
# via build
1919

2020
# The following packages are considered to be unsafe in a requirements file:
21-
setuptools==75.8.0 \
22-
--hash=sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6 \
23-
--hash=sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3
21+
setuptools==76.0.0 \
22+
--hash=sha256:199466a166ff664970d0ee145839f5582cb9bca7a0a3a2e795b6a9cb2308e9c6 \
23+
--hash=sha256:43b4ee60e10b0d0ee98ad11918e114c70701bc6051662a9a675a0496c1a158f4
2424
# via -r build-requirements.in

docs/html/topics/more-dependency-resolution.md

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,22 @@ Pip's current implementation of the provider implements
160160

161161
* If Requires-Python is present only consider that
162162
* If there are causes of resolution conflict (backtrack causes) then
163-
only consider them until there are no longer any resolution conflicts
164-
165-
Pip's current implementation of the provider implements `get_preference` as
166-
follows:
167-
168-
* Prefer if any of the known requirements is "direct", e.g. points to an
169-
explicit URL.
170-
* If equal, prefer if any requirement is "pinned", i.e. contains
171-
operator ``===`` or ``==``.
172-
* Order user-specified requirements by the order they are specified.
173-
* If equal, prefers "non-free" requirements, i.e. contains at least one
174-
operator, such as ``>=`` or ``<``.
175-
* If equal, order alphabetically for consistency (helps debuggability).
163+
only consider them until there are no longer any resolution conflicts
164+
165+
Pip's current implementation of the provider implements `get_preference`
166+
for known requirements with the following preferences in the following order:
167+
168+
* Any requirement that is "direct", e.g., points to an explicit URL.
169+
* Any requirement that is "pinned", i.e., contains the operator ``===``
170+
or ``==`` without a wildcard.
171+
* Any requirement that imposes an upper version limit, i.e., contains the
172+
operator ``<``, ``<=``, ``~=``, or ``==`` with a wildcard. Because
173+
pip prioritizes the latest version, preferring explicit upper bounds
174+
can rule out infeasible candidates sooner. This does not imply that
175+
upper bounds are good practice; they can make dependency management
176+
and resolution harder.
177+
* Order user-specified requirements as they are specified, placing
178+
other requirements afterward.
179+
* Any "non-free" requirement, i.e., one that contains at least one
180+
operator, such as ``>=`` or ``!=``.
181+
* Alphabetical order for consistency (aids debuggability).

news/13270.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a regression that causes dependencies to be checked *before* ``Requires-Python``
2+
project metadata is checked, leading to wasted cycles when the Python version is
3+
unsupported.

news/13273.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved heuristics for determining the order of dependency resolution.

news/CacheControl.vendor.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Upgrade CacheControl to 0.14.2

news/certifi.vendor.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Upgrade certifi to 2025.1.31

news/pygments.vendor.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Upgrade pygments to 2.19.1

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,12 @@ def _prepare(self) -> BaseDistribution:
249249
return dist
250250

251251
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
252+
# Emit the Requires-Python requirement first to fail fast on
253+
# unsupported candidates and avoid pointless downloads/preparation.
254+
yield self._factory.make_requires_python_requirement(self.dist.requires_python)
252255
requires = self.dist.iter_dependencies() if with_requires else ()
253256
for r in requires:
254257
yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
255-
yield self._factory.make_requires_python_requirement(self.dist.requires_python)
256258

257259
def get_install_requirement(self) -> Optional[InstallRequirement]:
258260
return self._ireq

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

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,20 @@ def get_preference(
166166
167167
Currently pip considers the following in order:
168168
169-
* Prefer if any of the known requirements is "direct", e.g. points to an
170-
explicit URL.
171-
* If equal, prefer if any requirement is "pinned", i.e. contains
172-
operator ``===`` or ``==``.
173-
* Order user-specified requirements by the order they are specified.
174-
* If equal, prefers "non-free" requirements, i.e. contains at least one
175-
operator, such as ``>=`` or ``<``.
176-
* If equal, order alphabetically for consistency (helps debuggability).
169+
* Any requirement that is "direct", e.g., points to an explicit URL.
170+
* Any requirement that is "pinned", i.e., contains the operator ``===``
171+
or ``==`` without a wildcard.
172+
* Any requirement that imposes an upper version limit, i.e., contains the
173+
operator ``<``, ``<=``, ``~=``, or ``==`` with a wildcard. Because
174+
pip prioritizes the latest version, preferring explicit upper bounds
175+
can rule out infeasible candidates sooner. This does not imply that
176+
upper bounds are good practice; they can make dependency management
177+
and resolution harder.
178+
* Order user-specified requirements as they are specified, placing
179+
other requirements afterward.
180+
* Any "non-free" requirement, i.e., one that contains at least one
181+
operator, such as ``>=`` or ``!=``.
182+
* Alphabetical order for consistency (aids debuggability).
177183
"""
178184
try:
179185
next(iter(information[identifier]))
@@ -206,12 +212,17 @@ def get_preference(
206212
]
207213

208214
pinned = any(((op[:2] == "==") and ("*" not in ver)) for op, ver in operators)
215+
upper_bounded = any(
216+
((op in ("<", "<=", "~=")) or (op == "==" and "*" in ver))
217+
for op, ver in operators
218+
)
209219
unfree = bool(operators)
210220
requested_order = self._user_requested.get(identifier, math.inf)
211221

212222
return (
213223
not direct,
214224
not pinned,
225+
not upper_bounded,
215226
requested_order,
216227
not unfree,
217228
identifier,
@@ -263,6 +274,7 @@ def _eligible_for_upgrade(identifier: str) -> bool:
263274
def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool:
264275
return requirement.is_satisfied_by(candidate)
265276

266-
def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]:
277+
def get_dependencies(self, candidate: Candidate) -> Iterable[Requirement]:
267278
with_requires = not self._ignore_dependencies
268-
return [r for r in candidate.iter_dependencies(with_requires) if r is not None]
279+
# iter_dependencies() can perform nontrivial work so delay until needed.
280+
return (r for r in candidate.iter_dependencies(with_requires) if r is not None)

0 commit comments

Comments
 (0)