Skip to content

Commit 9858332

Browse files
committed
Fix error reporting on Requires-Python conflicts
1 parent 1c128f4 commit 9858332

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 get_installation_error(self, e):
@@ -411,12 +424,14 @@ def get_installation_error(self, e):
411424

412425
# If one of the things we can't solve is "we need Python X.Y",
413426
# that is what we report.
414-
for cause in e.causes:
415-
if isinstance(cause.requirement, RequiresPythonRequirement):
416-
return self._report_requires_python_error(
417-
cause.requirement,
418-
cause.parent,
419-
)
427+
requires_python_causes = [
428+
cause
429+
for cause in e.causes
430+
if isinstance(cause.requirement, RequiresPythonRequirement)
431+
and not cause.requirement.is_satisfied_by(self._python_candidate)
432+
]
433+
if requires_python_causes:
434+
return self._report_requires_python_error(requires_python_causes)
420435

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

0 commit comments

Comments
 (0)