Skip to content

Commit 67e8ac2

Browse files
authored
Merge pull request #13588 from notatallshaw/hint-on-resolution-impossible-when-no-packages-are-available
Add hint when resolution impossible causes have no versions available
2 parents 990ca8a + 9bee3f4 commit 67e8ac2

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

news/13588.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
On ``ResolutionImpossible`` errors, include a note about causes with no candidates.

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,21 @@ def _report_single_requirement_conflict(
711711

712712
return DistributionNotFound(f"No matching distribution found for {req}")
713713

714+
def _has_any_candidates(self, project_name: str) -> bool:
715+
"""
716+
Check if there are any candidates available for the project name.
717+
"""
718+
return any(
719+
self.find_candidates(
720+
project_name,
721+
requirements={project_name: []},
722+
incompatibilities={},
723+
constraint=Constraint.empty(),
724+
prefers_installed=True,
725+
is_satisfied_by=lambda r, c: True,
726+
)
727+
)
728+
714729
def get_installation_error(
715730
self,
716731
e: ResolutionImpossible[Requirement, Candidate],
@@ -796,6 +811,22 @@ def describe_trigger(parent: Candidate) -> str:
796811
spec = constraints[key].specifier
797812
msg += f"\n The user requested (constraint) {key}{spec}"
798813

814+
# Check for causes that had no candidates
815+
causes = set()
816+
for req, _ in e.causes:
817+
causes.add(req.name)
818+
819+
no_candidates = {c for c in causes if not self._has_any_candidates(c)}
820+
if no_candidates:
821+
msg = (
822+
msg
823+
+ "\n\n"
824+
+ "Additionally, some packages in these conflicts have no "
825+
+ "matching distributions available for your environment:"
826+
+ "\n "
827+
+ "\n ".join(sorted(no_candidates))
828+
)
829+
799830
msg = (
800831
msg
801832
+ "\n\n"

tests/functional/test_new_resolver_errors.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
create_basic_wheel_for_package,
77
create_test_package_with_setup,
88
)
9+
from tests.lib.wheel import make_wheel
910

1011

1112
def test_new_resolver_conflict_requirements_file(
@@ -134,3 +135,61 @@ def test_new_resolver_checks_requires_python_before_dependencies(
134135
# Setuptools produces wheels with normalized names.
135136
assert "pkg_dep" not in result.stderr, str(result)
136137
assert "pkg_dep" not in result.stdout, str(result)
138+
139+
140+
def test_new_resolver_no_versions_available_hint(script: PipTestEnvironment) -> None:
141+
"""
142+
Test hint that no package candidate is available at all,
143+
when ResolutionImpossible occurs.
144+
"""
145+
wheel_house = script.scratch_path.joinpath("wheelhouse")
146+
wheel_house.mkdir()
147+
148+
incompatible_dep_wheel = make_wheel(
149+
name="incompatible-dep",
150+
version="1.0.0",
151+
wheel_metadata_updates={"Tag": ["py3-none-fakeplat"]},
152+
)
153+
incompatible_dep_wheel.save_to(
154+
wheel_house.joinpath("incompatible_dep-1.0.0-py3-none-fakeplat.whl")
155+
)
156+
157+
# Create multiple versions of a package that depend on the incompatible dependency
158+
requesting_pkg_v1 = make_wheel(
159+
name="requesting-pkg",
160+
version="1.0.0",
161+
metadata_updates={"Requires-Dist": ["incompatible-dep==1.0.0"]},
162+
)
163+
requesting_pkg_v1.save_to(
164+
wheel_house.joinpath("requesting_pkg-1.0.0-py2.py3-none-any.whl")
165+
)
166+
167+
requesting_pkg_v2 = make_wheel(
168+
name="requesting-pkg",
169+
version="2.0.0",
170+
metadata_updates={"Requires-Dist": ["incompatible-dep==1.0.0"]},
171+
)
172+
requesting_pkg_v2.save_to(
173+
wheel_house.joinpath("requesting_pkg-2.0.0-py2.py3-none-any.whl")
174+
)
175+
176+
# Attempt to install the requesting package
177+
result = script.pip(
178+
"install",
179+
"--no-cache-dir",
180+
"--no-index",
181+
"--find-links",
182+
str(wheel_house),
183+
"requesting-pkg",
184+
expect_error=True,
185+
)
186+
187+
# Check that ResolutionImpossible error occurred
188+
assert "ResolutionImpossible" in result.stderr, str(result)
189+
190+
# Check that the new hint message is present
191+
assert (
192+
"Additionally, some packages in these conflicts have no "
193+
"matching distributions available for your environment:\n"
194+
" incompatible-dep\n" in result.stdout
195+
), str(result)

0 commit comments

Comments
 (0)