Skip to content

Commit d87abdf

Browse files
committed
Fix error reporting on Requires-Python conflicts
1 parent 2a5e84e commit d87abdf

File tree

2 files changed

+37
-21
lines changed

2 files changed

+37
-21
lines changed

news/9541.bugfix.rst

Lines changed: 1 addition & 0 deletions
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

Lines changed: 36 additions & 21 deletions
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):
@@ -427,12 +440,14 @@ def get_installation_error(
427440

428441
# If one of the things we can't solve is "we need Python X.Y",
429442
# that is what we report.
430-
for cause in e.causes:
431-
if isinstance(cause.requirement, RequiresPythonRequirement):
432-
return self._report_requires_python_error(
433-
cause.requirement,
434-
cause.parent,
435-
)
443+
requires_python_causes = [
444+
cause
445+
for cause in e.causes
446+
if isinstance(cause.requirement, RequiresPythonRequirement)
447+
and not cause.requirement.is_satisfied_by(self._python_candidate)
448+
]
449+
if requires_python_causes:
450+
return self._report_requires_python_error(requires_python_causes)
436451

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

0 commit comments

Comments
 (0)