|
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):
|
@@ -434,12 +447,14 @@ def get_installation_error(
|
434 | 447 |
|
435 | 448 | # If one of the things we can't solve is "we need Python X.Y",
|
436 | 449 | # 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) |
443 | 458 |
|
444 | 459 | # Otherwise, we have a set of causes which can't all be satisfied
|
445 | 460 | # at once.
|
|
0 commit comments