123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- """Exceptions used throughout package"""
- import configparser
- from itertools import chain, groupby, repeat
- from typing import TYPE_CHECKING, Dict, List, Optional
- from pip._vendor.pkg_resources import Distribution
- from pip._vendor.requests.models import Request, Response
- if TYPE_CHECKING:
- from hashlib import _Hash
- from pip._internal.req.req_install import InstallRequirement
- class PipError(Exception):
- """Base pip exception"""
- class ConfigurationError(PipError):
- """General exception in configuration"""
- class InstallationError(PipError):
- """General exception during installation"""
- class UninstallationError(PipError):
- """General exception during uninstallation"""
- class NoneMetadataError(PipError):
- """
- Raised when accessing "METADATA" or "PKG-INFO" metadata for a
- pip._vendor.pkg_resources.Distribution object and
- `dist.has_metadata('METADATA')` returns True but
- `dist.get_metadata('METADATA')` returns None (and similarly for
- "PKG-INFO").
- """
- def __init__(self, dist, metadata_name):
- # type: (Distribution, str) -> None
- """
- :param dist: A Distribution object.
- :param metadata_name: The name of the metadata being accessed
- (can be "METADATA" or "PKG-INFO").
- """
- self.dist = dist
- self.metadata_name = metadata_name
- def __str__(self):
- # type: () -> str
- # Use `dist` in the error message because its stringification
- # includes more information, like the version and location.
- return (
- 'None {} metadata found for distribution: {}'.format(
- self.metadata_name, self.dist,
- )
- )
- class UserInstallationInvalid(InstallationError):
- """A --user install is requested on an environment without user site."""
- def __str__(self):
- # type: () -> str
- return "User base directory is not specified"
- class InvalidSchemeCombination(InstallationError):
- def __str__(self):
- # type: () -> str
- before = ", ".join(str(a) for a in self.args[:-1])
- return f"Cannot set {before} and {self.args[-1]} together"
- class DistributionNotFound(InstallationError):
- """Raised when a distribution cannot be found to satisfy a requirement"""
- class RequirementsFileParseError(InstallationError):
- """Raised when a general error occurs parsing a requirements file line."""
- class BestVersionAlreadyInstalled(PipError):
- """Raised when the most up-to-date version of a package is already
- installed."""
- class BadCommand(PipError):
- """Raised when virtualenv or a command is not found"""
- class CommandError(PipError):
- """Raised when there is an error in command-line arguments"""
- class PreviousBuildDirError(PipError):
- """Raised when there's a previous conflicting build directory"""
- class NetworkConnectionError(PipError):
- """HTTP connection error"""
- def __init__(self, error_msg, response=None, request=None):
- # type: (str, Response, Request) -> None
- """
- Initialize NetworkConnectionError with `request` and `response`
- objects.
- """
- self.response = response
- self.request = request
- self.error_msg = error_msg
- if (self.response is not None and not self.request and
- hasattr(response, 'request')):
- self.request = self.response.request
- super().__init__(error_msg, response, request)
- def __str__(self):
- # type: () -> str
- return str(self.error_msg)
- class InvalidWheelFilename(InstallationError):
- """Invalid wheel filename."""
- class UnsupportedWheel(InstallationError):
- """Unsupported wheel."""
- class MetadataInconsistent(InstallationError):
- """Built metadata contains inconsistent information.
- This is raised when the metadata contains values (e.g. name and version)
- that do not match the information previously obtained from sdist filename
- or user-supplied ``#egg=`` value.
- """
- def __init__(self, ireq, field, f_val, m_val):
- # type: (InstallRequirement, str, str, str) -> None
- self.ireq = ireq
- self.field = field
- self.f_val = f_val
- self.m_val = m_val
- def __str__(self):
- # type: () -> str
- template = (
- "Requested {} has inconsistent {}: "
- "filename has {!r}, but metadata has {!r}"
- )
- return template.format(self.ireq, self.field, self.f_val, self.m_val)
- class InstallationSubprocessError(InstallationError):
- """A subprocess call failed during installation."""
- def __init__(self, returncode, description):
- # type: (int, str) -> None
- self.returncode = returncode
- self.description = description
- def __str__(self):
- # type: () -> str
- return (
- "Command errored out with exit status {}: {} "
- "Check the logs for full command output."
- ).format(self.returncode, self.description)
- class HashErrors(InstallationError):
- """Multiple HashError instances rolled into one for reporting"""
- def __init__(self):
- # type: () -> None
- self.errors = [] # type: List[HashError]
- def append(self, error):
- # type: (HashError) -> None
- self.errors.append(error)
- def __str__(self):
- # type: () -> str
- lines = []
- self.errors.sort(key=lambda e: e.order)
- for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
- lines.append(cls.head)
- lines.extend(e.body() for e in errors_of_cls)
- if lines:
- return '\n'.join(lines)
- return ''
- def __nonzero__(self):
- # type: () -> bool
- return bool(self.errors)
- def __bool__(self):
- # type: () -> bool
- return self.__nonzero__()
- class HashError(InstallationError):
- """
- A failure to verify a package against known-good hashes
- :cvar order: An int sorting hash exception classes by difficulty of
- recovery (lower being harder), so the user doesn't bother fretting
- about unpinned packages when he has deeper issues, like VCS
- dependencies, to deal with. Also keeps error reports in a
- deterministic order.
- :cvar head: A section heading for display above potentially many
- exceptions of this kind
- :ivar req: The InstallRequirement that triggered this error. This is
- pasted on after the exception is instantiated, because it's not
- typically available earlier.
- """
- req = None # type: Optional[InstallRequirement]
- head = ''
- order = -1 # type: int
- def body(self):
- # type: () -> str
- """Return a summary of me for display under the heading.
- This default implementation simply prints a description of the
- triggering requirement.
- :param req: The InstallRequirement that provoked this error, with
- its link already populated by the resolver's _populate_link().
- """
- return f' {self._requirement_name()}'
- def __str__(self):
- # type: () -> str
- return f'{self.head}\n{self.body()}'
- def _requirement_name(self):
- # type: () -> str
- """Return a description of the requirement that triggered me.
- This default implementation returns long description of the req, with
- line numbers
- """
- return str(self.req) if self.req else 'unknown package'
- class VcsHashUnsupported(HashError):
- """A hash was provided for a version-control-system-based requirement, but
- we don't have a method for hashing those."""
- order = 0
- head = ("Can't verify hashes for these requirements because we don't "
- "have a way to hash version control repositories:")
- class DirectoryUrlHashUnsupported(HashError):
- """A hash was provided for a version-control-system-based requirement, but
- we don't have a method for hashing those."""
- order = 1
- head = ("Can't verify hashes for these file:// requirements because they "
- "point to directories:")
- class HashMissing(HashError):
- """A hash was needed for a requirement but is absent."""
- order = 2
- head = ('Hashes are required in --require-hashes mode, but they are '
- 'missing from some requirements. Here is a list of those '
- 'requirements along with the hashes their downloaded archives '
- 'actually had. Add lines like these to your requirements files to '
- 'prevent tampering. (If you did not enable --require-hashes '
- 'manually, note that it turns on automatically when any package '
- 'has a hash.)')
- def __init__(self, gotten_hash):
- # type: (str) -> None
- """
- :param gotten_hash: The hash of the (possibly malicious) archive we
- just downloaded
- """
- self.gotten_hash = gotten_hash
- def body(self):
- # type: () -> str
- # Dodge circular import.
- from pip._internal.utils.hashes import FAVORITE_HASH
- package = None
- if self.req:
- # In the case of URL-based requirements, display the original URL
- # seen in the requirements file rather than the package name,
- # so the output can be directly copied into the requirements file.
- package = (self.req.original_link if self.req.original_link
- # In case someone feeds something downright stupid
- # to InstallRequirement's constructor.
- else getattr(self.req, 'req', None))
- return ' {} --hash={}:{}'.format(package or 'unknown package',
- FAVORITE_HASH,
- self.gotten_hash)
- class HashUnpinned(HashError):
- """A requirement had a hash specified but was not pinned to a specific
- version."""
- order = 3
- head = ('In --require-hashes mode, all requirements must have their '
- 'versions pinned with ==. These do not:')
- class HashMismatch(HashError):
- """
- Distribution file hash values don't match.
- :ivar package_name: The name of the package that triggered the hash
- mismatch. Feel free to write to this after the exception is raise to
- improve its error message.
- """
- order = 4
- head = ('THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS '
- 'FILE. If you have updated the package versions, please update '
- 'the hashes. Otherwise, examine the package contents carefully; '
- 'someone may have tampered with them.')
- def __init__(self, allowed, gots):
- # type: (Dict[str, List[str]], Dict[str, _Hash]) -> None
- """
- :param allowed: A dict of algorithm names pointing to lists of allowed
- hex digests
- :param gots: A dict of algorithm names pointing to hashes we
- actually got from the files under suspicion
- """
- self.allowed = allowed
- self.gots = gots
- def body(self):
- # type: () -> str
- return ' {}:\n{}'.format(self._requirement_name(),
- self._hash_comparison())
- def _hash_comparison(self):
- # type: () -> str
- """
- Return a comparison of actual and expected hash values.
- Example::
- Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
- or 123451234512345123451234512345123451234512345
- Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
- """
- def hash_then_or(hash_name):
- # type: (str) -> chain[str]
- # For now, all the decent hashes have 6-char names, so we can get
- # away with hard-coding space literals.
- return chain([hash_name], repeat(' or'))
- lines = [] # type: List[str]
- for hash_name, expecteds in self.allowed.items():
- prefix = hash_then_or(hash_name)
- lines.extend((' Expected {} {}'.format(next(prefix), e))
- for e in expecteds)
- lines.append(' Got {}\n'.format(
- self.gots[hash_name].hexdigest()))
- return '\n'.join(lines)
- class UnsupportedPythonVersion(InstallationError):
- """Unsupported python version according to Requires-Python package
- metadata."""
- class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
- """When there are errors while loading a configuration file
- """
- def __init__(self, reason="could not be loaded", fname=None, error=None):
- # type: (str, Optional[str], Optional[configparser.Error]) -> None
- super().__init__(error)
- self.reason = reason
- self.fname = fname
- self.error = error
- def __str__(self):
- # type: () -> str
- if self.fname is not None:
- message_part = f" in {self.fname}."
- else:
- assert self.error is not None
- message_part = f".\n{self.error}\n"
- return f"Configuration file {self.reason}{message_part}"
|