Path: blob/main/test/lib/python3.9/site-packages/pip/_internal/exceptions.py
4799 views
"""Exceptions used throughout package.12This module MUST NOT try to import from anything within `pip._internal` to3operate. This is expected to be importable from any/all files within the4subpackage and, thus, should not depend on them.5"""67import configparser8import re9from itertools import chain, groupby, repeat10from typing import TYPE_CHECKING, Dict, List, Optional, Union1112from pip._vendor.requests.models import Request, Response13from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult14from pip._vendor.rich.markup import escape15from pip._vendor.rich.text import Text1617if TYPE_CHECKING:18from hashlib import _Hash19from typing import Literal2021from pip._internal.metadata import BaseDistribution22from pip._internal.req.req_install import InstallRequirement232425#26# Scaffolding27#28def _is_kebab_case(s: str) -> bool:29return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None303132def _prefix_with_indent(33s: Union[Text, str],34console: Console,35*,36prefix: str,37indent: str,38) -> Text:39if isinstance(s, Text):40text = s41else:42text = console.render_str(s)4344return console.render_str(prefix, overflow="ignore") + console.render_str(45f"\n{indent}", overflow="ignore"46).join(text.split(allow_blank=True))474849class PipError(Exception):50"""The base pip error."""515253class DiagnosticPipError(PipError):54"""An error, that presents diagnostic information to the user.5556This contains a bunch of logic, to enable pretty presentation of our error57messages. Each error gets a unique reference. Each error can also include58additional context, a hint and/or a note -- which are presented with the59main error message in a consistent style.6061This is adapted from the error output styling in `sphinx-theme-builder`.62"""6364reference: str6566def __init__(67self,68*,69kind: 'Literal["error", "warning"]' = "error",70reference: Optional[str] = None,71message: Union[str, Text],72context: Optional[Union[str, Text]],73hint_stmt: Optional[Union[str, Text]],74note_stmt: Optional[Union[str, Text]] = None,75link: Optional[str] = None,76) -> None:77# Ensure a proper reference is provided.78if reference is None:79assert hasattr(self, "reference"), "error reference not provided!"80reference = self.reference81assert _is_kebab_case(reference), "error reference must be kebab-case!"8283self.kind = kind84self.reference = reference8586self.message = message87self.context = context8889self.note_stmt = note_stmt90self.hint_stmt = hint_stmt9192self.link = link9394super().__init__(f"<{self.__class__.__name__}: {self.reference}>")9596def __repr__(self) -> str:97return (98f"<{self.__class__.__name__}("99f"reference={self.reference!r}, "100f"message={self.message!r}, "101f"context={self.context!r}, "102f"note_stmt={self.note_stmt!r}, "103f"hint_stmt={self.hint_stmt!r}"104")>"105)106107def __rich_console__(108self,109console: Console,110options: ConsoleOptions,111) -> RenderResult:112colour = "red" if self.kind == "error" else "yellow"113114yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"115yield ""116117if not options.ascii_only:118# Present the main message, with relevant context indented.119if self.context is not None:120yield _prefix_with_indent(121self.message,122console,123prefix=f"[{colour}]×[/] ",124indent=f"[{colour}]│[/] ",125)126yield _prefix_with_indent(127self.context,128console,129prefix=f"[{colour}]╰─>[/] ",130indent=f"[{colour}] [/] ",131)132else:133yield _prefix_with_indent(134self.message,135console,136prefix="[red]×[/] ",137indent=" ",138)139else:140yield self.message141if self.context is not None:142yield ""143yield self.context144145if self.note_stmt is not None or self.hint_stmt is not None:146yield ""147148if self.note_stmt is not None:149yield _prefix_with_indent(150self.note_stmt,151console,152prefix="[magenta bold]note[/]: ",153indent=" ",154)155if self.hint_stmt is not None:156yield _prefix_with_indent(157self.hint_stmt,158console,159prefix="[cyan bold]hint[/]: ",160indent=" ",161)162163if self.link is not None:164yield ""165yield f"Link: {self.link}"166167168#169# Actual Errors170#171class ConfigurationError(PipError):172"""General exception in configuration"""173174175class InstallationError(PipError):176"""General exception during installation"""177178179class UninstallationError(PipError):180"""General exception during uninstallation"""181182183class MissingPyProjectBuildRequires(DiagnosticPipError):184"""Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""185186reference = "missing-pyproject-build-system-requires"187188def __init__(self, *, package: str) -> None:189super().__init__(190message=f"Can not process {escape(package)}",191context=Text(192"This package has an invalid pyproject.toml file.\n"193"The [build-system] table is missing the mandatory `requires` key."194),195note_stmt="This is an issue with the package mentioned above, not pip.",196hint_stmt=Text("See PEP 518 for the detailed specification."),197)198199200class InvalidPyProjectBuildRequires(DiagnosticPipError):201"""Raised when pyproject.toml an invalid `build-system.requires`."""202203reference = "invalid-pyproject-build-system-requires"204205def __init__(self, *, package: str, reason: str) -> None:206super().__init__(207message=f"Can not process {escape(package)}",208context=Text(209"This package has an invalid `build-system.requires` key in "210f"pyproject.toml.\n{reason}"211),212note_stmt="This is an issue with the package mentioned above, not pip.",213hint_stmt=Text("See PEP 518 for the detailed specification."),214)215216217class NoneMetadataError(PipError):218"""Raised when accessing a Distribution's "METADATA" or "PKG-INFO".219220This signifies an inconsistency, when the Distribution claims to have221the metadata file (if not, raise ``FileNotFoundError`` instead), but is222not actually able to produce its content. This may be due to permission223errors.224"""225226def __init__(227self,228dist: "BaseDistribution",229metadata_name: str,230) -> None:231"""232:param dist: A Distribution object.233:param metadata_name: The name of the metadata being accessed234(can be "METADATA" or "PKG-INFO").235"""236self.dist = dist237self.metadata_name = metadata_name238239def __str__(self) -> str:240# Use `dist` in the error message because its stringification241# includes more information, like the version and location.242return "None {} metadata found for distribution: {}".format(243self.metadata_name,244self.dist,245)246247248class UserInstallationInvalid(InstallationError):249"""A --user install is requested on an environment without user site."""250251def __str__(self) -> str:252return "User base directory is not specified"253254255class InvalidSchemeCombination(InstallationError):256def __str__(self) -> str:257before = ", ".join(str(a) for a in self.args[:-1])258return f"Cannot set {before} and {self.args[-1]} together"259260261class DistributionNotFound(InstallationError):262"""Raised when a distribution cannot be found to satisfy a requirement"""263264265class RequirementsFileParseError(InstallationError):266"""Raised when a general error occurs parsing a requirements file line."""267268269class BestVersionAlreadyInstalled(PipError):270"""Raised when the most up-to-date version of a package is already271installed."""272273274class BadCommand(PipError):275"""Raised when virtualenv or a command is not found"""276277278class CommandError(PipError):279"""Raised when there is an error in command-line arguments"""280281282class PreviousBuildDirError(PipError):283"""Raised when there's a previous conflicting build directory"""284285286class NetworkConnectionError(PipError):287"""HTTP connection error"""288289def __init__(290self, error_msg: str, response: Response = None, request: Request = None291) -> None:292"""293Initialize NetworkConnectionError with `request` and `response`294objects.295"""296self.response = response297self.request = request298self.error_msg = error_msg299if (300self.response is not None301and not self.request302and hasattr(response, "request")303):304self.request = self.response.request305super().__init__(error_msg, response, request)306307def __str__(self) -> str:308return str(self.error_msg)309310311class InvalidWheelFilename(InstallationError):312"""Invalid wheel filename."""313314315class UnsupportedWheel(InstallationError):316"""Unsupported wheel."""317318319class InvalidWheel(InstallationError):320"""Invalid (e.g. corrupt) wheel."""321322def __init__(self, location: str, name: str):323self.location = location324self.name = name325326def __str__(self) -> str:327return f"Wheel '{self.name}' located at {self.location} is invalid."328329330class MetadataInconsistent(InstallationError):331"""Built metadata contains inconsistent information.332333This is raised when the metadata contains values (e.g. name and version)334that do not match the information previously obtained from sdist filename335or user-supplied ``#egg=`` value.336"""337338def __init__(339self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str340) -> None:341self.ireq = ireq342self.field = field343self.f_val = f_val344self.m_val = m_val345346def __str__(self) -> str:347template = (348"Requested {} has inconsistent {}: "349"filename has {!r}, but metadata has {!r}"350)351return template.format(self.ireq, self.field, self.f_val, self.m_val)352353354class LegacyInstallFailure(DiagnosticPipError):355"""Error occurred while executing `setup.py install`"""356357reference = "legacy-install-failure"358359def __init__(self, package_details: str) -> None:360super().__init__(361message="Encountered error while trying to install package.",362context=package_details,363hint_stmt="See above for output from the failure.",364note_stmt="This is an issue with the package mentioned above, not pip.",365)366367368class InstallationSubprocessError(DiagnosticPipError, InstallationError):369"""A subprocess call failed."""370371reference = "subprocess-exited-with-error"372373def __init__(374self,375*,376command_description: str,377exit_code: int,378output_lines: Optional[List[str]],379) -> None:380if output_lines is None:381output_prompt = Text("See above for output.")382else:383output_prompt = (384Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")385+ Text("".join(output_lines))386+ Text.from_markup(R"[red]\[end of output][/]")387)388389super().__init__(390message=(391f"[green]{escape(command_description)}[/] did not run successfully.\n"392f"exit code: {exit_code}"393),394context=output_prompt,395hint_stmt=None,396note_stmt=(397"This error originates from a subprocess, and is likely not a "398"problem with pip."399),400)401402self.command_description = command_description403self.exit_code = exit_code404405def __str__(self) -> str:406return f"{self.command_description} exited with {self.exit_code}"407408409class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):410reference = "metadata-generation-failed"411412def __init__(413self,414*,415package_details: str,416) -> None:417super(InstallationSubprocessError, self).__init__(418message="Encountered error while generating package metadata.",419context=escape(package_details),420hint_stmt="See above for details.",421note_stmt="This is an issue with the package mentioned above, not pip.",422)423424def __str__(self) -> str:425return "metadata generation failed"426427428class HashErrors(InstallationError):429"""Multiple HashError instances rolled into one for reporting"""430431def __init__(self) -> None:432self.errors: List["HashError"] = []433434def append(self, error: "HashError") -> None:435self.errors.append(error)436437def __str__(self) -> str:438lines = []439self.errors.sort(key=lambda e: e.order)440for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):441lines.append(cls.head)442lines.extend(e.body() for e in errors_of_cls)443if lines:444return "\n".join(lines)445return ""446447def __bool__(self) -> bool:448return bool(self.errors)449450451class HashError(InstallationError):452"""453A failure to verify a package against known-good hashes454455:cvar order: An int sorting hash exception classes by difficulty of456recovery (lower being harder), so the user doesn't bother fretting457about unpinned packages when he has deeper issues, like VCS458dependencies, to deal with. Also keeps error reports in a459deterministic order.460:cvar head: A section heading for display above potentially many461exceptions of this kind462:ivar req: The InstallRequirement that triggered this error. This is463pasted on after the exception is instantiated, because it's not464typically available earlier.465466"""467468req: Optional["InstallRequirement"] = None469head = ""470order: int = -1471472def body(self) -> str:473"""Return a summary of me for display under the heading.474475This default implementation simply prints a description of the476triggering requirement.477478:param req: The InstallRequirement that provoked this error, with479its link already populated by the resolver's _populate_link().480481"""482return f" {self._requirement_name()}"483484def __str__(self) -> str:485return f"{self.head}\n{self.body()}"486487def _requirement_name(self) -> str:488"""Return a description of the requirement that triggered me.489490This default implementation returns long description of the req, with491line numbers492493"""494return str(self.req) if self.req else "unknown package"495496497class VcsHashUnsupported(HashError):498"""A hash was provided for a version-control-system-based requirement, but499we don't have a method for hashing those."""500501order = 0502head = (503"Can't verify hashes for these requirements because we don't "504"have a way to hash version control repositories:"505)506507508class DirectoryUrlHashUnsupported(HashError):509"""A hash was provided for a version-control-system-based requirement, but510we don't have a method for hashing those."""511512order = 1513head = (514"Can't verify hashes for these file:// requirements because they "515"point to directories:"516)517518519class HashMissing(HashError):520"""A hash was needed for a requirement but is absent."""521522order = 2523head = (524"Hashes are required in --require-hashes mode, but they are "525"missing from some requirements. Here is a list of those "526"requirements along with the hashes their downloaded archives "527"actually had. Add lines like these to your requirements files to "528"prevent tampering. (If you did not enable --require-hashes "529"manually, note that it turns on automatically when any package "530"has a hash.)"531)532533def __init__(self, gotten_hash: str) -> None:534"""535:param gotten_hash: The hash of the (possibly malicious) archive we536just downloaded537"""538self.gotten_hash = gotten_hash539540def body(self) -> str:541# Dodge circular import.542from pip._internal.utils.hashes import FAVORITE_HASH543544package = None545if self.req:546# In the case of URL-based requirements, display the original URL547# seen in the requirements file rather than the package name,548# so the output can be directly copied into the requirements file.549package = (550self.req.original_link551if self.req.original_link552# In case someone feeds something downright stupid553# to InstallRequirement's constructor.554else getattr(self.req, "req", None)555)556return " {} --hash={}:{}".format(557package or "unknown package", FAVORITE_HASH, self.gotten_hash558)559560561class HashUnpinned(HashError):562"""A requirement had a hash specified but was not pinned to a specific563version."""564565order = 3566head = (567"In --require-hashes mode, all requirements must have their "568"versions pinned with ==. These do not:"569)570571572class HashMismatch(HashError):573"""574Distribution file hash values don't match.575576:ivar package_name: The name of the package that triggered the hash577mismatch. Feel free to write to this after the exception is raise to578improve its error message.579580"""581582order = 4583head = (584"THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "585"FILE. If you have updated the package versions, please update "586"the hashes. Otherwise, examine the package contents carefully; "587"someone may have tampered with them."588)589590def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:591"""592:param allowed: A dict of algorithm names pointing to lists of allowed593hex digests594:param gots: A dict of algorithm names pointing to hashes we595actually got from the files under suspicion596"""597self.allowed = allowed598self.gots = gots599600def body(self) -> str:601return " {}:\n{}".format(self._requirement_name(), self._hash_comparison())602603def _hash_comparison(self) -> str:604"""605Return a comparison of actual and expected hash values.606607Example::608609Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde610or 123451234512345123451234512345123451234512345611Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef612613"""614615def hash_then_or(hash_name: str) -> "chain[str]":616# For now, all the decent hashes have 6-char names, so we can get617# away with hard-coding space literals.618return chain([hash_name], repeat(" or"))619620lines: List[str] = []621for hash_name, expecteds in self.allowed.items():622prefix = hash_then_or(hash_name)623lines.extend(624(" Expected {} {}".format(next(prefix), e)) for e in expecteds625)626lines.append(627" Got {}\n".format(self.gots[hash_name].hexdigest())628)629return "\n".join(lines)630631632class UnsupportedPythonVersion(InstallationError):633"""Unsupported python version according to Requires-Python package634metadata."""635636637class ConfigurationFileCouldNotBeLoaded(ConfigurationError):638"""When there are errors while loading a configuration file"""639640def __init__(641self,642reason: str = "could not be loaded",643fname: Optional[str] = None,644error: Optional[configparser.Error] = None,645) -> None:646super().__init__(error)647self.reason = reason648self.fname = fname649self.error = error650651def __str__(self) -> str:652if self.fname is not None:653message_part = f" in {self.fname}."654else:655assert self.error is not None656message_part = f".\n{self.error}\n"657return f"Configuration file {self.reason}{message_part}"658659660