11import functools
22import logging
33from typing import (
4+ TYPE_CHECKING ,
45 Dict ,
56 FrozenSet ,
67 Iterable ,
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+
6372logger = logging .getLogger (__name__ )
6473
6574C = 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