1
1
import functools
2
2
import logging
3
3
from typing import (
4
+ TYPE_CHECKING ,
4
5
Dict ,
5
6
FrozenSet ,
6
7
Iterable ,
60
61
UnsatisfiableRequirement ,
61
62
)
62
63
64
+ if TYPE_CHECKING :
65
+ from typing import Protocol
66
+
67
+ class ConflictCause (Protocol ):
68
+ requirement : RequiresPythonRequirement
69
+ parent : Candidate
70
+
71
+
63
72
logger = logging .getLogger (__name__ )
64
73
65
74
C = TypeVar ("C" )
@@ -387,21 +396,25 @@ def get_dist_to_uninstall(self, candidate):
387
396
)
388
397
return None
389
398
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 } )"
405
418
return UnsupportedPythonVersion (message )
406
419
407
420
def _report_single_requirement_conflict (self , req , parent ):
@@ -427,12 +440,14 @@ def get_installation_error(
427
440
428
441
# If one of the things we can't solve is "we need Python X.Y",
429
442
# 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 )
436
451
437
452
# Otherwise, we have a set of causes which can't all be satisfied
438
453
# at once.
0 commit comments