codekingpro/portable-devtools
114k
1"""Exceptions used throughout package.2 3This module MUST NOT try to import from anything within `pip._internal` to4operate. This is expected to be importable from any/all files within the5subpackage and, thus, should not depend on them.6"""7 8from __future__ import annotations9 10import configparser11import contextlib12import locale13import logging14import pathlib15import re16import sys17import traceback18from collections.abc import Iterable, Iterator19from itertools import chain, groupby, repeat20from typing import TYPE_CHECKING, Literal21 22from pip._vendor.packaging.requirements import InvalidRequirement23from pip._vendor.packaging.version import InvalidVersion24from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult25from pip._vendor.rich.markup import escape26from pip._vendor.rich.text import Text27 28if TYPE_CHECKING:29 from hashlib import _Hash30 31 from pip._vendor.requests.models import PreparedRequest, Request, Response32 33 from pip._internal.metadata import BaseDistribution34 from pip._internal.models.link import Link35 from pip._internal.network.download import _FileDownload36 from pip._internal.req.req_install import InstallRequirement37 38logger = logging.getLogger(__name__)39 40 41#42# Scaffolding43#44def _is_kebab_case(s: str) -> bool:45 return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None46 47 48def _prefix_with_indent(49 s: Text | str,50 console: Console,51 *,52 prefix: str,53 indent: str,54) -> Text:55 if isinstance(s, Text):56 text = s57 else:58 text = console.render_str(s)59 60 return console.render_str(prefix, overflow="ignore") + console.render_str(61 f"\n{indent}", overflow="ignore"62 ).join(text.split(allow_blank=True))63 64 65class PipError(Exception):66 """The base pip error."""67 68 69class DiagnosticPipError(PipError):70 """An error, that presents diagnostic information to the user.71 72 This contains a bunch of logic, to enable pretty presentation of our error73 messages. Each error gets a unique reference. Each error can also include74 additional context, a hint and/or a note -- which are presented with the75 main error message in a consistent style.76 77 This is adapted from the error output styling in `sphinx-theme-builder`.78 """79 80 reference: str81 82 def __init__(83 self,84 *,85 kind: Literal["error", "warning"] = "error",86 reference: str | None = None,87 message: str | Text,88 context: str | Text | None,89 hint_stmt: str | Text | None,90 note_stmt: str | Text | None = None,91 link: str | None = None,92 ) -> None:93 # Ensure a proper reference is provided.94 if reference is None:95 assert hasattr(self, "reference"), "error reference not provided!"96 reference = self.reference97 assert _is_kebab_case(reference), "error reference must be kebab-case!"98 99 self.kind = kind100 self.reference = reference101 102 self.message = message103 self.context = context104 105 self.note_stmt = note_stmt106 self.hint_stmt = hint_stmt107 108 self.link = link109 110 super().__init__(f"<{self.__class__.__name__}: {self.reference}>")111 112 def __repr__(self) -> str:113 return (114 f"<{self.__class__.__name__}("115 f"reference={self.reference!r}, "116 f"message={self.message!r}, "117 f"context={self.context!r}, "118 f"note_stmt={self.note_stmt!r}, "119 f"hint_stmt={self.hint_stmt!r}"120 ")>"121 )122 123 def __rich_console__(124 self,125 console: Console,126 options: ConsoleOptions,127 ) -> RenderResult:128 colour = "red" if self.kind == "error" else "yellow"129 130 yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"131 yield ""132 133 if not options.ascii_only:134 # Present the main message, with relevant context indented.135 if self.context is not None:136 yield _prefix_with_indent(137 self.message,138 console,139 prefix=f"[{colour}]×[/] ",140 indent=f"[{colour}]│[/] ",141 )142 yield _prefix_with_indent(143 self.context,144 console,145 prefix=f"[{colour}]╰─>[/] ",146 indent=f"[{colour}] [/] ",147 )148 else:149 yield _prefix_with_indent(150 self.message,151 console,152 prefix="[red]×[/] ",153 indent=" ",154 )155 else:156 yield self.message157 if self.context is not None:158 yield ""159 yield self.context160 161 if self.note_stmt is not None or self.hint_stmt is not None:162 yield ""163 164 if self.note_stmt is not None:165 yield _prefix_with_indent(166 self.note_stmt,167 console,168 prefix="[magenta bold]note[/]: ",169 indent=" ",170 )171 if self.hint_stmt is not None:172 yield _prefix_with_indent(173 self.hint_stmt,174 console,175 prefix="[cyan bold]hint[/]: ",176 indent=" ",177 )178 179 if self.link is not None:180 yield ""181 yield f"Link: {self.link}"182 183 184#185# Actual Errors186#187class ConfigurationError(PipError):188 """General exception in configuration"""189 190 191class InstallationError(PipError):192 """General exception during installation"""193 194 195class FailedToPrepareCandidate(InstallationError):196 """Raised when we fail to prepare a candidate (i.e. fetch and generate metadata).197 198 This is intentionally not a diagnostic error, since the output will be presented199 above this error, when this occurs. This should instead present information to the200 user.201 """202 203 def __init__(204 self, *, package_name: str, requirement_chain: str, failed_step: str205 ) -> None:206 super().__init__(f"Failed to build '{package_name}' when {failed_step.lower()}")207 self.package_name = package_name208 self.requirement_chain = requirement_chain209 self.failed_step = failed_step210 211 212class MissingPyProjectBuildRequires(DiagnosticPipError):213 """Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""214 215 reference = "missing-pyproject-build-system-requires"216 217 def __init__(self, *, package: str) -> None:218 super().__init__(219 message=f"Can not process {escape(package)}",220 context=Text(221 "This package has an invalid pyproject.toml file.\n"222 "The [build-system] table is missing the mandatory `requires` key."223 ),224 note_stmt="This is an issue with the package mentioned above, not pip.",225 hint_stmt=Text("See PEP 518 for the detailed specification."),226 )227 228 229class InvalidPyProjectBuildRequires(DiagnosticPipError):230 """Raised when pyproject.toml an invalid `build-system.requires`."""231 232 reference = "invalid-pyproject-build-system-requires"233 234 def __init__(self, *, package: str, reason: str) -> None:235 super().__init__(236 message=f"Can not process {escape(package)}",237 context=Text(238 "This package has an invalid `build-system.requires` key in "239 f"pyproject.toml.\n{reason}"240 ),241 note_stmt="This is an issue with the package mentioned above, not pip.",242 hint_stmt=Text("See PEP 518 for the detailed specification."),243 )244 245 246class NoneMetadataError(PipError):247 """Raised when accessing a Distribution's "METADATA" or "PKG-INFO".248 249 This signifies an inconsistency, when the Distribution claims to have250 the metadata file (if not, raise ``FileNotFoundError`` instead), but is251 not actually able to produce its content. This may be due to permission252 errors.253 """254 255 def __init__(256 self,257 dist: BaseDistribution,258 metadata_name: str,259 ) -> None:260 """261 :param dist: A Distribution object.262 :param metadata_name: The name of the metadata being accessed263 (can be "METADATA" or "PKG-INFO").264 """265 self.dist = dist266 self.metadata_name = metadata_name267 268 def __str__(self) -> str:269 # Use `dist` in the error message because its stringification270 # includes more information, like the version and location.271 return f"None {self.metadata_name} metadata found for distribution: {self.dist}"272 273 274class UserInstallationInvalid(InstallationError):275 """A --user install is requested on an environment without user site."""276 277 def __str__(self) -> str:278 return "User base directory is not specified"279 280 281class InvalidSchemeCombination(InstallationError):282 def __str__(self) -> str:283 before = ", ".join(str(a) for a in self.args[:-1])284 return f"Cannot set {before} and {self.args[-1]} together"285 286 287class DistributionNotFound(InstallationError):288 """Raised when a distribution cannot be found to satisfy a requirement"""289 290 291class RequirementsFileParseError(InstallationError):292 """Raised when a general error occurs parsing a requirements file line."""293 294 295class BestVersionAlreadyInstalled(PipError):296 """Raised when the most up-to-date version of a package is already297 installed."""298 299 300class BadCommand(PipError):301 """Raised when virtualenv or a command is not found"""302 303 304class CommandError(PipError):305 """Raised when there is an error in command-line arguments"""306 307 308class PreviousBuildDirError(PipError):309 """Raised when there's a previous conflicting build directory"""310 311 312class NetworkConnectionError(PipError):313 """HTTP connection error"""314 315 def __init__(316 self,317 error_msg: str,318 response: Response | None = None,319 request: Request | PreparedRequest | None = None,320 ) -> None:321 """322 Initialize NetworkConnectionError with `request` and `response`323 objects.324 """325 self.response = response326 self.request = request327 self.error_msg = error_msg328 if (329 self.response is not None330 and not self.request331 and hasattr(response, "request")332 ):333 self.request = self.response.request334 super().__init__(error_msg, response, request)335 336 def __str__(self) -> str:337 return str(self.error_msg)338 339 340class InvalidWheelFilename(InstallationError):341 """Invalid wheel filename."""342 343 344class UnsupportedWheel(InstallationError):345 """Unsupported wheel."""346 347 348class InvalidWheel(InstallationError):349 """Invalid (e.g. corrupt) wheel."""350 351 def __init__(self, location: str, name: str):352 self.location = location353 self.name = name354 355 def __str__(self) -> str:356 return f"Wheel '{self.name}' located at {self.location} is invalid."357 358 359class MetadataInconsistent(InstallationError):360 """Built metadata contains inconsistent information.361 362 This is raised when the metadata contains values (e.g. name and version)363 that do not match the information previously obtained from sdist filename,364 user-supplied ``#egg=`` value, or an install requirement name.365 """366 367 def __init__(368 self, ireq: InstallRequirement, field: str, f_val: str, m_val: str369 ) -> None:370 self.ireq = ireq371 self.field = field372 self.f_val = f_val373 self.m_val = m_val374 375 def __str__(self) -> str:376 return (377 f"Requested {self.ireq} has inconsistent {self.field}: "378 f"expected {self.f_val!r}, but metadata has {self.m_val!r}"379 )380 381 382class MetadataInvalid(InstallationError):383 """Metadata is invalid."""384 385 def __init__(self, ireq: InstallRequirement, error: str) -> None:386 self.ireq = ireq387 self.error = error388 389 def __str__(self) -> str:390 return f"Requested {self.ireq} has invalid metadata: {self.error}"391 392 393class InstallationSubprocessError(DiagnosticPipError, InstallationError):394 """A subprocess call failed."""395 396 reference = "subprocess-exited-with-error"397 398 def __init__(399 self,400 *,401 command_description: str,402 exit_code: int,403 output_lines: list[str] | None,404 ) -> None:405 if output_lines is None:406 output_prompt = Text("No available output.")407 else:408 output_prompt = (409 Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")410 + Text("".join(output_lines))411 + Text.from_markup(R"[red]\[end of output][/]")412 )413 414 super().__init__(415 message=(416 f"[green]{escape(command_description)}[/] did not run successfully.\n"417 f"exit code: {exit_code}"418 ),419 context=output_prompt,420 hint_stmt=None,421 note_stmt=(422 "This error originates from a subprocess, and is likely not a "423 "problem with pip."424 ),425 )426 427 self.command_description = command_description428 self.exit_code = exit_code429 430 def __str__(self) -> str:431 return f"{self.command_description} exited with {self.exit_code}"432 433 434class MetadataGenerationFailed(DiagnosticPipError, InstallationError):435 reference = "metadata-generation-failed"436 437 def __init__(438 self,439 *,440 package_details: str,441 ) -> None:442 super().__init__(443 message="Encountered error while generating package metadata.",444 context=escape(package_details),445 hint_stmt="See above for details.",446 note_stmt="This is an issue with the package mentioned above, not pip.",447 )448 449 def __str__(self) -> str:450 return "metadata generation failed"451 452 453class HashErrors(InstallationError):454 """Multiple HashError instances rolled into one for reporting"""455 456 def __init__(self) -> None:457 self.errors: list[HashError] = []458 459 def append(self, error: HashError) -> None:460 self.errors.append(error)461 462 def __str__(self) -> str:463 lines = []464 self.errors.sort(key=lambda e: e.order)465 for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):466 lines.append(cls.head)467 lines.extend(e.body() for e in errors_of_cls)468 if lines:469 return "\n".join(lines)470 return ""471 472 def __bool__(self) -> bool:473 return bool(self.errors)474 475 476class HashError(InstallationError):477 """478 A failure to verify a package against known-good hashes479 480 :cvar order: An int sorting hash exception classes by difficulty of481 recovery (lower being harder), so the user doesn't bother fretting482 about unpinned packages when he has deeper issues, like VCS483 dependencies, to deal with. Also keeps error reports in a484 deterministic order.485 :cvar head: A section heading for display above potentially many486 exceptions of this kind487 :ivar req: The InstallRequirement that triggered this error. This is488 pasted on after the exception is instantiated, because it's not489 typically available earlier.490 491 """492 493 req: InstallRequirement | None = None494 head = ""495 order: int = -1496 497 def body(self) -> str:498 """Return a summary of me for display under the heading.499 500 This default implementation simply prints a description of the501 triggering requirement.502 503 :param req: The InstallRequirement that provoked this error, with504 its link already populated by the resolver's _populate_link().505 506 """507 return f" {self._requirement_name()}"508 509 def __str__(self) -> str:510 return f"{self.head}\n{self.body()}"511 512 def _requirement_name(self) -> str:513 """Return a description of the requirement that triggered me.514 515 This default implementation returns long description of the req, with516 line numbers517 518 """519 return str(self.req) if self.req else "unknown package"520 521 522class VcsHashUnsupported(HashError):523 """A hash was provided for a version-control-system-based requirement, but524 we don't have a method for hashing those."""525 526 order = 0527 head = (528 "Can't verify hashes for these requirements because we don't "529 "have a way to hash version control repositories:"530 )531 532 533class DirectoryUrlHashUnsupported(HashError):534 """A hash was provided for a version-control-system-based requirement, but535 we don't have a method for hashing those."""536 537 order = 1538 head = (539 "Can't verify hashes for these file:// requirements because they "540 "point to directories:"541 )542 543 544class HashMissing(HashError):545 """A hash was needed for a requirement but is absent."""546 547 order = 2548 head = (549 "Hashes are required in --require-hashes mode, but they are "550 "missing from some requirements. Here is a list of those "551 "requirements along with the hashes their downloaded archives "552 "actually had. Add lines like these to your requirements files to "553 "prevent tampering. (If you did not enable --require-hashes "554 "manually, note that it turns on automatically when any package "555 "has a hash.)"556 )557 558 def __init__(self, gotten_hash: str) -> None:559 """560 :param gotten_hash: The hash of the (possibly malicious) archive we561 just downloaded562 """563 self.gotten_hash = gotten_hash564 565 def body(self) -> str:566 # Dodge circular import.567 from pip._internal.utils.hashes import FAVORITE_HASH568 569 package = None570 if self.req:571 # In the case of URL-based requirements, display the original URL572 # seen in the requirements file rather than the package name,573 # so the output can be directly copied into the requirements file.574 package = (575 self.req.original_link576 if self.req.is_direct577 # In case someone feeds something downright stupid578 # to InstallRequirement's constructor.579 else getattr(self.req, "req", None)580 )581 return " {} --hash={}:{}".format(582 package or "unknown package", FAVORITE_HASH, self.gotten_hash583 )584 585 586class HashUnpinned(HashError):587 """A requirement had a hash specified but was not pinned to a specific588 version."""589 590 order = 3591 head = (592 "In --require-hashes mode, all requirements must have their "593 "versions pinned with ==. These do not:"594 )595 596 597class HashMismatch(HashError):598 """599 Distribution file hash values don't match.600 601 :ivar package_name: The name of the package that triggered the hash602 mismatch. Feel free to write to this after the exception is raise to603 improve its error message.604 605 """606 607 order = 4608 head = (609 "THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "610 "FILE. If you have updated the package versions, please update "611 "the hashes. Otherwise, examine the package contents carefully; "612 "someone may have tampered with them."613 )614 615 def __init__(self, allowed: dict[str, list[str]], gots: dict[str, _Hash]) -> None:616 """617 :param allowed: A dict of algorithm names pointing to lists of allowed618 hex digests619 :param gots: A dict of algorithm names pointing to hashes we620 actually got from the files under suspicion621 """622 self.allowed = allowed623 self.gots = gots624 625 def body(self) -> str:626 return f" {self._requirement_name()}:\n{self._hash_comparison()}"627 628 def _hash_comparison(self) -> str:629 """630 Return a comparison of actual and expected hash values.631 632 Example::633 634 Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde635 or 123451234512345123451234512345123451234512345636 Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef637 638 """639 640 def hash_then_or(hash_name: str) -> chain[str]:641 # For now, all the decent hashes have 6-char names, so we can get642 # away with hard-coding space literals.643 return chain([hash_name], repeat(" or"))644 645 lines: list[str] = []646 for hash_name, expecteds in self.allowed.items():647 prefix = hash_then_or(hash_name)648 lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds)649 lines.append(650 f" Got {self.gots[hash_name].hexdigest()}\n"651 )652 return "\n".join(lines)653 654 655class UnsupportedPythonVersion(InstallationError):656 """Unsupported python version according to Requires-Python package657 metadata."""658 659 660class ConfigurationFileCouldNotBeLoaded(ConfigurationError):661 """When there are errors while loading a configuration file"""662 663 def __init__(664 self,665 reason: str = "could not be loaded",666 fname: str | None = None,667 error: configparser.Error | None = None,668 ) -> None:669 super().__init__(error)670 self.reason = reason671 self.fname = fname672 self.error = error673 674 def __str__(self) -> str:675 if self.fname is not None:676 message_part = f" in {self.fname}."677 else:678 assert self.error is not None679 message_part = f".\n{self.error}\n"680 return f"Configuration file {self.reason}{message_part}"681 682 683_DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\684The Python environment under {sys.prefix} is managed externally, and may not be685manipulated by the user. Please use specific tooling from the distributor of686the Python installation to interact with this environment instead.687"""688 689 690class ExternallyManagedEnvironment(DiagnosticPipError):691 """The current environment is externally managed.692 693 This is raised when the current environment is externally managed, as694 defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked695 and displayed when the error is bubbled up to the user.696 697 :param error: The error message read from ``EXTERNALLY-MANAGED``.698 """699 700 reference = "externally-managed-environment"701 702 def __init__(self, error: str | None) -> None:703 if error is None:704 context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)705 else:706 context = Text(error)707 super().__init__(708 message="This environment is externally managed",709 context=context,710 note_stmt=(711 "If you believe this is a mistake, please contact your "712 "Python installation or OS distribution provider. "713 "You can override this, at the risk of breaking your Python "714 "installation or OS, by passing --break-system-packages."715 ),716 hint_stmt=Text("See PEP 668 for the detailed specification."),717 )718 719 @staticmethod720 def _iter_externally_managed_error_keys() -> Iterator[str]:721 # LC_MESSAGES is in POSIX, but not the C standard. The most common722 # platform that does not implement this category is Windows, where723 # using other categories for console message localization is equally724 # unreliable, so we fall back to the locale-less vendor message. This725 # can always be re-evaluated when a vendor proposes a new alternative.726 try:727 category = locale.LC_MESSAGES728 except AttributeError:729 lang: str | None = None730 else:731 lang, _ = locale.getlocale(category)732 if lang is not None:733 yield f"Error-{lang}"734 for sep in ("-", "_"):735 before, found, _ = lang.partition(sep)736 if not found:737 continue738 yield f"Error-{before}"739 yield "Error"740 741 @classmethod742 def from_config(743 cls,744 config: pathlib.Path | str,745 ) -> ExternallyManagedEnvironment:746 parser = configparser.ConfigParser(interpolation=None)747 try:748 parser.read(config, encoding="utf-8")749 section = parser["externally-managed"]750 for key in cls._iter_externally_managed_error_keys():751 with contextlib.suppress(KeyError):752 return cls(section[key])753 except KeyError:754 pass755 except (OSError, UnicodeDecodeError, configparser.ParsingError):756 from pip._internal.utils._log import VERBOSE757 758 exc_info = logger.isEnabledFor(VERBOSE)759 logger.warning("Failed to read %s", config, exc_info=exc_info)760 return cls(None)761 762 763class UninstallMissingRecord(DiagnosticPipError):764 reference = "uninstall-no-record-file"765 766 def __init__(self, *, distribution: BaseDistribution) -> None:767 installer = distribution.installer768 if not installer or installer == "pip":769 dep = f"{distribution.raw_name}=={distribution.version}"770 hint = Text.assemble(771 "You might be able to recover from this via: ",772 (f"pip install --ignore-installed --no-deps {dep}", "green"),773 )774 else:775 hint = Text(776 f"The package was installed by {installer}. "777 "You should check if it can uninstall the package."778 )779 780 super().__init__(781 message=Text(f"Cannot uninstall {distribution}"),782 context=(783 "The package's contents are unknown: "784 f"no RECORD file was found for {distribution.raw_name}."785 ),786 hint_stmt=hint,787 )788 789 790class LegacyDistutilsInstall(DiagnosticPipError):791 reference = "uninstall-distutils-installed-package"792 793 def __init__(self, *, distribution: BaseDistribution) -> None:794 super().__init__(795 message=Text(f"Cannot uninstall {distribution}"),796 context=(797 "It is a distutils installed project and thus we cannot accurately "798 "determine which files belong to it which would lead to only a partial "799 "uninstall."800 ),801 hint_stmt=None,802 )803 804 805class InvalidInstalledPackage(DiagnosticPipError):806 reference = "invalid-installed-package"807 808 def __init__(809 self,810 *,811 dist: BaseDistribution,812 invalid_exc: InvalidRequirement | InvalidVersion,813 ) -> None:814 installed_location = dist.installed_location815 816 if isinstance(invalid_exc, InvalidRequirement):817 invalid_type = "requirement"818 else:819 invalid_type = "version"820 821 super().__init__(822 message=Text(823 f"Cannot process installed package {dist} "824 + (f"in {installed_location!r} " if installed_location else "")825 + f"because it has an invalid {invalid_type}:\n{invalid_exc.args[0]}"826 ),827 context=(828 "Starting with pip 24.1, packages with invalid "829 f"{invalid_type}s can not be processed."830 ),831 hint_stmt="To proceed this package must be uninstalled.",832 )833 834 835class IncompleteDownloadError(DiagnosticPipError):836 """Raised when the downloader receives fewer bytes than advertised837 in the Content-Length header."""838 839 reference = "incomplete-download"840 841 def __init__(self, download: _FileDownload) -> None:842 # Dodge circular import.843 from pip._internal.utils.misc import format_size844 845 assert download.size is not None846 download_status = (847 f"{format_size(download.bytes_received)}/{format_size(download.size)}"848 )849 if download.reattempts:850 retry_status = f"after {download.reattempts + 1} attempts "851 hint = "Use --resume-retries to configure resume attempt limit."852 else:853 # Download retrying is not enabled.854 retry_status = ""855 hint = "Consider using --resume-retries to enable download resumption."856 message = Text(857 f"Download failed {retry_status}because not enough bytes "858 f"were received ({download_status})"859 )860 861 super().__init__(862 message=message,863 context=f"URL: {download.link.redacted_url}",864 hint_stmt=hint,865 note_stmt="This is an issue with network connectivity, not pip.",866 )867 868 869class ResolutionTooDeepError(DiagnosticPipError):870 """Raised when the dependency resolver exceeds the maximum recursion depth."""871 872 reference = "resolution-too-deep"873 874 def __init__(self) -> None:875 super().__init__(876 message="Dependency resolution exceeded maximum depth",877 context=(878 "Pip cannot resolve the current dependencies as the dependency graph "879 "is too complex for pip to solve efficiently."880 ),881 hint_stmt=(882 "Try adding lower bounds to constrain your dependencies, "883 "for example: 'package>=2.0.0' instead of just 'package'. "884 ),885 link="https://pip.pypa.io/en/stable/topics/dependency-resolution/#handling-resolution-too-deep-errors",886 )887 888 889class InstallWheelBuildError(DiagnosticPipError):890 reference = "failed-wheel-build-for-install"891 892 def __init__(self, failed: list[InstallRequirement]) -> None:893 super().__init__(894 message=(895 "Failed to build installable wheels for some "896 "pyproject.toml based projects"897 ),898 context=", ".join(r.name for r in failed), # type: ignore899 hint_stmt=None,900 )901 902 903class InvalidEggFragment(DiagnosticPipError):904 reference = "invalid-egg-fragment"905 906 def __init__(self, link: Link, fragment: str) -> None:907 hint = ""908 if ">" in fragment or "=" in fragment or "<" in fragment:909 hint = (910 "Version specifiers are silently ignored for URL references. "911 "Remove them. "912 )913 if "[" in fragment and "]" in fragment:914 hint += "Try using the Direct URL requirement syntax: 'name[extra] @ URL'"915 916 if not hint:917 hint = "Egg fragments can only be a valid project name."918 919 super().__init__(920 message=f"The '{escape(fragment)}' egg fragment is invalid",921 context=f"from '{escape(str(link))}'",922 hint_stmt=escape(hint),923 )924 925 926class BuildDependencyInstallError(DiagnosticPipError):927 """Raised when build dependencies cannot be installed."""928 929 reference = "failed-build-dependency-install"930 931 def __init__(932 self,933 req: InstallRequirement | None,934 build_reqs: Iterable[str],935 *,936 cause: Exception,937 log_lines: list[str] | None,938 ) -> None:939 if isinstance(cause, PipError):940 note = "This is likely not a problem with pip."941 else:942 note = (943 "pip crashed unexpectedly. Please file an issue on pip's issue "944 "tracker: https://github.com/pypa/pip/issues/new"945 )946 947 if log_lines is None:948 # No logs are available, they must have been printed earlier.949 context = Text("See above for more details.")950 else:951 if isinstance(cause, PipError):952 log_lines.append(f"ERROR: {cause}")953 else:954 # Split rendered error into real lines without trailing newlines.955 log_lines.extend(956 "".join(traceback.format_exception(cause)).splitlines()957 )958 959 context = Text.assemble(960 f"Installing {' '.join(build_reqs)}\n",961 (f"[{len(log_lines)} lines of output]\n", "red"),962 "\n".join(log_lines),963 ("\n[end of output]", "red"),964 )965 966 message = Text("Cannot install build dependencies", "green")967 if req:968 message += Text(f" for {req}")969 super().__init__(970 message=message, context=context, hint_stmt=None, note_stmt=note971 )972 