Skip to content

Commit 87b1334

Browse files
authored
Merge branch 'main' into fix-direct-preference
2 parents 16af4af + 2846077 commit 87b1334

File tree

14 files changed

+151
-15
lines changed

14 files changed

+151
-15
lines changed

docs/html/topics/dependency-resolution.md

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ of how dependency resolution for Python packages works.
3939
The user requests `pip install tea`. The package `tea` declares a dependency on
4040
`hot-water`, `spoon`, `cup`, amongst others.
4141
42-
pip starts by picking the most recent version of `tea` and get the list of
42+
pip starts by picking the most recent version of `tea` and gets the list of
4343
dependencies of that version of `tea`. It will then repeat the process for
4444
those packages, picking the most recent version of `spoon` and then `cup`. Now,
4545
pip notices that the version of `cup` it has chosen is not compatible with the
@@ -245,7 +245,7 @@ To find a version of both `package_coffee` and `package_tea` that depend on
245245
the same version of `package_water`, you might consider:
246246

247247
- Loosening the range of packages that you are prepared to install
248-
(e.g. `pip install "package_coffee>0.44.*" "package_tea>4.0.0"`)
248+
(e.g. `pip install "package_coffee>0.44" "package_tea>4.0.0"`)
249249
- Asking pip to install _any_ version of `package_coffee` and `package_tea`
250250
by removing the version specifiers altogether (e.g.
251251
`pip install package_coffee package_tea`)
@@ -298,7 +298,70 @@ In this situation, you could consider:
298298
- Refactoring your project to reduce the number of dependencies (for
299299
example, by breaking up a monolithic code base into smaller pieces).
300300

301-
### Getting help
301+
## Handling Resolution Too Deep Errors
302+
303+
Sometimes pip's dependency resolver may exceed its search depth and terminate
304+
with a `ResolutionTooDeepError` exception. This typically occurs when the
305+
dependency graph is extremely complex or when there are too many package
306+
versions to evaluate.
307+
308+
To address this error, consider the following strategies:
309+
310+
### Specify Reasonable Lower Bounds
311+
312+
By setting a higher lower bound for your dependencies, you narrow the search
313+
space. This excludes older versions that might trigger excessive backtracking.
314+
For example:
315+
316+
```{pip-cli}
317+
$ pip install "package_coffee>=0.44.0" "package_tea>=4.0.0"
318+
```
319+
320+
### Use the `--upgrade` Flag
321+
322+
The `--upgrade` flag directs pip to ignore already installed versions and
323+
search for the latest versions that meet your requirements. This can help
324+
avoid unnecessary resolution paths:
325+
326+
```{pip-cli}
327+
$ pip install --upgrade package_coffee package_tea
328+
```
329+
330+
### Utilize Constraint Files
331+
332+
If you need to impose additional version restrictions on transitive
333+
dependencies (dependencies of dependencies), consider using a constraint
334+
file. A constraint file specifies version limits for packages that are
335+
indirectly required. For example:
336+
337+
```
338+
# constraints.txt
339+
indirect_dependency>=2.0.0
340+
```
341+
342+
Then install your packages with:
343+
344+
```{pip-cli}
345+
$ pip install --constraint constraints.txt package_coffee package_tea
346+
```
347+
348+
### Use Upper Bounds Sparingly
349+
350+
Although upper bounds are generally discouraged because they can complicate
351+
dependency management, they may be necessary when certain versions are known
352+
to cause conflicts. Use them cautiously—for example:
353+
354+
```{pip-cli}
355+
$ pip install "package_coffee>=0.44.0,<1.0.0" "package_tea>=4.0.0"
356+
```
357+
358+
### Report ResolutionTooDeep Errors
359+
360+
If you encounter a `ResolutionTooDeep` error consider reporting it, to help
361+
the pip team have real world examples to test against, at the dedicated
362+
[pip issue](https://github.com/pypa/pip/issues/13281).
363+
364+
## Getting help
302365

303366
If none of the suggestions above work for you, we recommend that you ask
304367
for help on:

news/12649.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Suggest checking "pip config debug" in case of an InvalidProxyURL error.

news/12903.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support multiple global configuration paths returned by ``platformdirs`` on MacOS.

news/13282.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Provide hint, documentation, and link to the documentation when
2+
resolution too deep error occurs.

news/9727.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix fish shell completion when commandline contains multiple commands.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ ignore = [
168168
"B020",
169169
"B904", # Ruff enables opinionated warnings by default
170170
"B905", # Ruff enables opinionated warnings by default
171+
"C420", # unnecessary-dict-comprehension-for-iterable (worsens readability)
171172
]
172173
select = [
173174
"ASYNC",

src/pip/_internal/commands/completion.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,18 @@
3838
""",
3939
"fish": """
4040
function __fish_complete_pip
41-
set -lx COMP_WORDS (commandline -o) ""
42-
set -lx COMP_CWORD ( \\
43-
math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
44-
)
41+
set -lx COMP_WORDS \\
42+
(commandline --current-process --tokenize --cut-at-cursor) \\
43+
(commandline --current-token --cut-at-cursor)
44+
set -lx COMP_CWORD (math (count $COMP_WORDS) - 1)
4545
set -lx PIP_AUTO_COMPLETE 1
46-
string split \\ -- (eval $COMP_WORDS[1])
46+
set -l completions
47+
if string match -q '2.*' $version
48+
set completions (eval $COMP_WORDS[1])
49+
else
50+
set completions ($COMP_WORDS[1])
51+
end
52+
string split \\ -- $completions
4753
end
4854
complete -fa "(__fish_complete_pip)" -c {prog}
4955
""",

src/pip/_internal/commands/install.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import List, Optional
99

1010
from pip._vendor.packaging.utils import canonicalize_name
11+
from pip._vendor.requests.exceptions import InvalidProxyURL
1112
from pip._vendor.rich import print_json
1213

1314
# Eagerly import self_outdated_check to avoid crashes. Otherwise,
@@ -766,6 +767,13 @@ def create_os_error_message(
766767
parts.append(permissions_part)
767768
parts.append(".\n")
768769

770+
# Suggest to check "pip config debug" in case of invalid proxy
771+
if type(error) is InvalidProxyURL:
772+
parts.append(
773+
'Consider checking your local proxy configuration with "pip config debug"'
774+
)
775+
parts.append(".\n")
776+
769777
# Suggest the user to enable Long Paths if path length is
770778
# more than 260
771779
if (

src/pip/_internal/exceptions.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,3 +807,23 @@ def __init__(
807807
),
808808
hint_stmt="To proceed this package must be uninstalled.",
809809
)
810+
811+
812+
class ResolutionTooDeepError(DiagnosticPipError):
813+
"""Raised when the dependency resolver exceeds the maximum recursion depth."""
814+
815+
reference = "resolution-too-deep"
816+
817+
def __init__(self) -> None:
818+
super().__init__(
819+
message="Dependency resolution exceeded maximum depth",
820+
context=(
821+
"Pip cannot resolve the current dependencies as the dependency graph "
822+
"is too complex for pip to solve efficiently."
823+
),
824+
hint_stmt=(
825+
"Try adding lower bounds to constrain your dependencies, "
826+
"for example: 'package>=2.0.0' instead of just 'package'. "
827+
),
828+
link="https://pip.pypa.io/en/stable/topics/dependency-resolution/#handling-resolution-too-deep-errors",
829+
)

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast
66

77
from pip._vendor.packaging.utils import canonicalize_name
8-
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible
8+
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible, ResolutionTooDeep
99
from pip._vendor.resolvelib import Resolver as RLResolver
1010
from pip._vendor.resolvelib.structs import DirectedGraph
1111

1212
from pip._internal.cache import WheelCache
13+
from pip._internal.exceptions import ResolutionTooDeepError
1314
from pip._internal.index.package_finder import PackageFinder
1415
from pip._internal.operations.prepare import RequirementPreparer
1516
from pip._internal.req.constructors import install_req_extend_extras
@@ -102,6 +103,8 @@ def resolve(
102103
collected.constraints,
103104
)
104105
raise error from e
106+
except ResolutionTooDeep:
107+
raise ResolutionTooDeepError from None
105108

106109
req_set = RequirementSet(check_supported_wheels=check_supported_wheels)
107110
# process candidates with extras last to ensure their base equivalent is

0 commit comments

Comments
 (0)