Skip to content

Commit 80e0163

Browse files
authored
Merge pull request #9552
From uranusjr/new-resolver-requires-python-error
2 parents 031c34e + d87abdf commit 80e0163

File tree

3 files changed

+66
-22
lines changed

3 files changed

+66
-22
lines changed

news/9541.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix incorrect reporting on ``Requires-Python`` conflicts.

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

+36-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import functools
22
import logging
33
from typing import (
4+
TYPE_CHECKING,
45
Dict,
56
FrozenSet,
67
Iterable,
@@ -60,6 +61,14 @@
6061
UnsatisfiableRequirement,
6162
)
6263

64+
if TYPE_CHECKING:
65+
from typing import Protocol
66+
67+
class ConflictCause(Protocol):
68+
requirement: RequiresPythonRequirement
69+
parent: Candidate
70+
71+
6372
logger = logging.getLogger(__name__)
6473

6574
C = TypeVar("C")
@@ -387,21 +396,25 @@ def get_dist_to_uninstall(self, candidate):
387396
)
388397
return None
389398

390-
def _report_requires_python_error(
391-
self,
392-
requirement, # type: RequiresPythonRequirement
393-
template, # type: Candidate
394-
):
395-
# type: (...) -> UnsupportedPythonVersion
396-
message_format = (
397-
"Package {package!r} requires a different Python: "
398-
"{version} not in {specifier!r}"
399-
)
400-
message = message_format.format(
401-
package=template.name,
402-
version=self._python_candidate.version,
403-
specifier=str(requirement.specifier),
404-
)
399+
def _report_requires_python_error(self, causes):
400+
# type: (Sequence[ConflictCause]) -> UnsupportedPythonVersion
401+
assert causes, "Requires-Python error reported with no cause"
402+
403+
version = self._python_candidate.version
404+
405+
if len(causes) == 1:
406+
specifier = str(causes[0].requirement.specifier)
407+
message = (
408+
f"Package {causes[0].parent.name!r} requires a different "
409+
f"Python: {version} not in {specifier!r}"
410+
)
411+
return UnsupportedPythonVersion(message)
412+
413+
message = f"Packages require a different Python. {version} not in:"
414+
for cause in causes:
415+
package = cause.parent.format_for_error()
416+
specifier = str(cause.requirement.specifier)
417+
message += f"\n{specifier!r} (required by {package})"
405418
return UnsupportedPythonVersion(message)
406419

407420
def _report_single_requirement_conflict(self, req, parent):
@@ -434,12 +447,14 @@ def get_installation_error(
434447

435448
# If one of the things we can't solve is "we need Python X.Y",
436449
# that is what we report.
437-
for cause in e.causes:
438-
if isinstance(cause.requirement, RequiresPythonRequirement):
439-
return self._report_requires_python_error(
440-
cause.requirement,
441-
cause.parent,
442-
)
450+
requires_python_causes = [
451+
cause
452+
for cause in e.causes
453+
if isinstance(cause.requirement, RequiresPythonRequirement)
454+
and not cause.requirement.is_satisfied_by(self._python_candidate)
455+
]
456+
if requires_python_causes:
457+
return self._report_requires_python_error(requires_python_causes)
443458

444459
# Otherwise, we have a set of causes which can't all be satisfied
445460
# at once.

tests/functional/test_new_resolver_errors.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from tests.lib import create_basic_wheel_for_package
1+
import sys
2+
3+
from tests.lib import create_basic_wheel_for_package, create_test_package_with_setup
24

35

46
def test_new_resolver_conflict_requirements_file(tmpdir, script):
@@ -45,3 +47,29 @@ def test_new_resolver_conflict_constraints_file(tmpdir, script):
4547

4648
message = "The user requested (constraint) pkg!=1.0"
4749
assert message in result.stdout, str(result)
50+
51+
52+
def test_new_resolver_requires_python_error(script):
53+
compatible_python = ">={0.major}.{0.minor}".format(sys.version_info)
54+
incompatible_python = "<{0.major}.{0.minor}".format(sys.version_info)
55+
56+
pkga = create_test_package_with_setup(
57+
script,
58+
name="pkga",
59+
version="1.0",
60+
python_requires=compatible_python,
61+
)
62+
pkgb = create_test_package_with_setup(
63+
script,
64+
name="pkgb",
65+
version="1.0",
66+
python_requires=incompatible_python,
67+
)
68+
69+
# This always fails because pkgb can never be satisfied.
70+
result = script.pip("install", "--no-index", pkga, pkgb, expect_error=True)
71+
72+
# The error message should mention the Requires-Python: value causing the
73+
# conflict, not the compatible one.
74+
assert incompatible_python in result.stderr, str(result)
75+
assert compatible_python not in result.stderr, str(result)

0 commit comments

Comments
 (0)