1 """Exceptions used throughout package.
3 This module MUST NOT try to import from anything within `pip._internal` to
4 operate. This is expected to be importable from any/all files within the
5 subpackage and, thus, should not depend on them.
15 from itertools
import chain
, groupby
, repeat
16 from typing
import TYPE_CHECKING
, Dict
, Iterator
, List
, Optional
, Union
18 from pip
._vendor
.requests
.models
import Request
, Response
19 from pip
._vendor
.rich
.console
import Console
, ConsoleOptions
, RenderResult
20 from pip
._vendor
.rich
.markup
import escape
21 from pip
._vendor
.rich
.text
import Text
24 from hashlib
import _Hash
25 from typing
import Literal
27 from pip
._internal
.metadata
import BaseDistribution
28 from pip
._internal
.req
.req_install
import InstallRequirement
30 logger
= logging
.getLogger(__name__
)
36 def _is_kebab_case(s
: str) -> bool:
37 return re
.match(r
"^[a-z]+(-[a-z]+)*$", s
) is not None
40 def _prefix_with_indent(
47 if isinstance(s
, Text
):
50 text
= console
.render_str(s
)
52 return console
.render_str(prefix
, overflow
="ignore") + console
.render_str(
53 f
"\n{indent}", overflow
="ignore"
54 ).join(text
.split(allow_blank
=True))
57 class PipError(Exception):
58 """The base pip error."""
61 class DiagnosticPipError(PipError
):
62 """An error, that presents diagnostic information to the user.
64 This contains a bunch of logic, to enable pretty presentation of our error
65 messages. Each error gets a unique reference. Each error can also include
66 additional context, a hint and/or a note -- which are presented with the
67 main error message in a consistent style.
69 This is adapted from the error output styling in `sphinx-theme-builder`.
77 kind
: 'Literal["error", "warning"]' = "error",
78 reference
: Optional
[str] = None,
79 message
: Union
[str, Text
],
80 context
: Optional
[Union
[str, Text
]],
81 hint_stmt
: Optional
[Union
[str, Text
]],
82 note_stmt
: Optional
[Union
[str, Text
]] = None,
83 link
: Optional
[str] = None,
85 # Ensure a proper reference is provided.
87 assert hasattr(self
, "reference"), "error reference not provided!"
88 reference
= self
.reference
89 assert _is_kebab_case(reference
), "error reference must be kebab-case!"
92 self
.reference
= reference
94 self
.message
= message
95 self
.context
= context
97 self
.note_stmt
= note_stmt
98 self
.hint_stmt
= hint_stmt
102 super().__init
__(f
"<{self.__class__.__name__}: {self.reference}>")
104 def __repr__(self
) -> str:
106 f
"<{self.__class__.__name__}("
107 f
"reference={self.reference!r}, "
108 f
"message={self.message!r}, "
109 f
"context={self.context!r}, "
110 f
"note_stmt={self.note_stmt!r}, "
111 f
"hint_stmt={self.hint_stmt!r}"
115 def __rich_console__(
118 options
: ConsoleOptions
,
120 colour
= "red" if self
.kind
== "error" else "yellow"
122 yield f
"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
125 if not options
.ascii_only
:
126 # Present the main message, with relevant context indented.
127 if self
.context
is not None:
128 yield _prefix_with_indent(
131 prefix
=f
"[{colour}]×[/] ",
132 indent
=f
"[{colour}]│[/] ",
134 yield _prefix_with_indent(
137 prefix
=f
"[{colour}]╰─>[/] ",
138 indent
=f
"[{colour}] [/] ",
141 yield _prefix_with_indent(
149 if self
.context
is not None:
153 if self
.note_stmt
is not None or self
.hint_stmt
is not None:
156 if self
.note_stmt
is not None:
157 yield _prefix_with_indent(
160 prefix
="[magenta bold]note[/]: ",
163 if self
.hint_stmt
is not None:
164 yield _prefix_with_indent(
167 prefix
="[cyan bold]hint[/]: ",
171 if self
.link
is not None:
173 yield f
"Link: {self.link}"
179 class ConfigurationError(PipError
):
180 """General exception in configuration"""
183 class InstallationError(PipError
):
184 """General exception during installation"""
187 class UninstallationError(PipError
):
188 """General exception during uninstallation"""
191 class MissingPyProjectBuildRequires(DiagnosticPipError
):
192 """Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
194 reference
= "missing-pyproject-build-system-requires"
196 def __init__(self
, *, package
: str) -> None:
198 message
=f
"Can not process {escape(package)}",
200 "This package has an invalid pyproject.toml file.\n"
201 "The [build-system] table is missing the mandatory `requires` key."
203 note_stmt
="This is an issue with the package mentioned above, not pip.",
204 hint_stmt
=Text("See PEP 518 for the detailed specification."),
208 class InvalidPyProjectBuildRequires(DiagnosticPipError
):
209 """Raised when pyproject.toml an invalid `build-system.requires`."""
211 reference
= "invalid-pyproject-build-system-requires"
213 def __init__(self
, *, package
: str, reason
: str) -> None:
215 message
=f
"Can not process {escape(package)}",
217 "This package has an invalid `build-system.requires` key in "
218 f
"pyproject.toml.\n{reason}"
220 note_stmt
="This is an issue with the package mentioned above, not pip.",
221 hint_stmt
=Text("See PEP 518 for the detailed specification."),
225 class NoneMetadataError(PipError
):
226 """Raised when accessing a Distribution's "METADATA" or "PKG-INFO".
228 This signifies an inconsistency, when the Distribution claims to have
229 the metadata file (if not, raise ``FileNotFoundError`` instead), but is
230 not actually able to produce its content. This may be due to permission
236 dist
: "BaseDistribution",
240 :param dist: A Distribution object.
241 :param metadata_name: The name of the metadata being accessed
242 (can be "METADATA" or "PKG-INFO").
245 self
.metadata_name
= metadata_name
247 def __str__(self
) -> str:
248 # Use `dist` in the error message because its stringification
249 # includes more information, like the version and location.
250 return "None {} metadata found for distribution: {}".format(
256 class UserInstallationInvalid(InstallationError
):
257 """A --user install is requested on an environment without user site."""
259 def __str__(self
) -> str:
260 return "User base directory is not specified"
263 class InvalidSchemeCombination(InstallationError
):
264 def __str__(self
) -> str:
265 before
= ", ".join(str(a
) for a
in self
.args
[:-1])
266 return f
"Cannot set {before} and {self.args[-1]} together"
269 class DistributionNotFound(InstallationError
):
270 """Raised when a distribution cannot be found to satisfy a requirement"""
273 class RequirementsFileParseError(InstallationError
):
274 """Raised when a general error occurs parsing a requirements file line."""
277 class BestVersionAlreadyInstalled(PipError
):
278 """Raised when the most up-to-date version of a package is already
282 class BadCommand(PipError
):
283 """Raised when virtualenv or a command is not found"""
286 class CommandError(PipError
):
287 """Raised when there is an error in command-line arguments"""
290 class PreviousBuildDirError(PipError
):
291 """Raised when there's a previous conflicting build directory"""
294 class NetworkConnectionError(PipError
):
295 """HTTP connection error"""
300 response
: Optional
[Response
] = None,
301 request
: Optional
[Request
] = None,
304 Initialize NetworkConnectionError with `request` and `response`
307 self
.response
= response
308 self
.request
= request
309 self
.error_msg
= error_msg
311 self
.response
is not None
313 and hasattr(response
, "request")
315 self
.request
= self
.response
.request
316 super().__init
__(error_msg
, response
, request
)
318 def __str__(self
) -> str:
319 return str(self
.error_msg
)
322 class InvalidWheelFilename(InstallationError
):
323 """Invalid wheel filename."""
326 class UnsupportedWheel(InstallationError
):
327 """Unsupported wheel."""
330 class InvalidWheel(InstallationError
):
331 """Invalid (e.g. corrupt) wheel."""
333 def __init__(self
, location
: str, name
: str):
334 self
.location
= location
337 def __str__(self
) -> str:
338 return f
"Wheel '{self.name}' located at {self.location} is invalid."
341 class MetadataInconsistent(InstallationError
):
342 """Built metadata contains inconsistent information.
344 This is raised when the metadata contains values (e.g. name and version)
345 that do not match the information previously obtained from sdist filename,
346 user-supplied ``#egg=`` value, or an install requirement name.
350 self
, ireq
: "InstallRequirement", field
: str, f_val
: str, m_val
: str
357 def __str__(self
) -> str:
359 f
"Requested {self.ireq} has inconsistent {self.field}: "
360 f
"expected {self.f_val!r}, but metadata has {self.m_val!r}"
364 class LegacyInstallFailure(DiagnosticPipError
):
365 """Error occurred while executing `setup.py install`"""
367 reference
= "legacy-install-failure"
369 def __init__(self
, package_details
: str) -> None:
371 message
="Encountered error while trying to install package.",
372 context
=package_details
,
373 hint_stmt
="See above for output from the failure.",
374 note_stmt
="This is an issue with the package mentioned above, not pip.",
378 class InstallationSubprocessError(DiagnosticPipError
, InstallationError
):
379 """A subprocess call failed."""
381 reference
= "subprocess-exited-with-error"
386 command_description
: str,
388 output_lines
: Optional
[List
[str]],
390 if output_lines
is None:
391 output_prompt
= Text("See above for output.")
394 Text
.from_markup(f
"[red][{len(output_lines)} lines of output][/]\n")
395 + Text("".join(output_lines
))
396 + Text
.from_markup(R
"[red]\[end of output][/]")
401 f
"[green]{escape(command_description)}[/] did not run successfully.\n"
402 f
"exit code: {exit_code}"
404 context
=output_prompt
,
407 "This error originates from a subprocess, and is likely not a "
412 self
.command_description
= command_description
413 self
.exit_code
= exit_code
415 def __str__(self
) -> str:
416 return f
"{self.command_description} exited with {self.exit_code}"
419 class MetadataGenerationFailed(InstallationSubprocessError
, InstallationError
):
420 reference
= "metadata-generation-failed"
425 package_details
: str,
427 super(InstallationSubprocessError
, self
).__init
__(
428 message
="Encountered error while generating package metadata.",
429 context
=escape(package_details
),
430 hint_stmt
="See above for details.",
431 note_stmt
="This is an issue with the package mentioned above, not pip.",
434 def __str__(self
) -> str:
435 return "metadata generation failed"
438 class HashErrors(InstallationError
):
439 """Multiple HashError instances rolled into one for reporting"""
441 def __init__(self
) -> None:
442 self
.errors
: List
["HashError"] = []
444 def append(self
, error
: "HashError") -> None:
445 self
.errors
.append(error
)
447 def __str__(self
) -> str:
449 self
.errors
.sort(key
=lambda e
: e
.order
)
450 for cls
, errors_of_cls
in groupby(self
.errors
, lambda e
: e
.__class
__):
451 lines
.append(cls
.head
)
452 lines
.extend(e
.body() for e
in errors_of_cls
)
454 return "\n".join(lines
)
457 def __bool__(self
) -> bool:
458 return bool(self
.errors
)
461 class HashError(InstallationError
):
463 A failure to verify a package against known-good hashes
465 :cvar order: An int sorting hash exception classes by difficulty of
466 recovery (lower being harder), so the user doesn't bother fretting
467 about unpinned packages when he has deeper issues, like VCS
468 dependencies, to deal with. Also keeps error reports in a
470 :cvar head: A section heading for display above potentially many
471 exceptions of this kind
472 :ivar req: The InstallRequirement that triggered this error. This is
473 pasted on after the exception is instantiated, because it's not
474 typically available earlier.
478 req
: Optional
["InstallRequirement"] = None
482 def body(self
) -> str:
483 """Return a summary of me for display under the heading.
485 This default implementation simply prints a description of the
486 triggering requirement.
488 :param req: The InstallRequirement that provoked this error, with
489 its link already populated by the resolver's _populate_link().
492 return f
" {self._requirement_name()}"
494 def __str__(self
) -> str:
495 return f
"{self.head}\n{self.body()}"
497 def _requirement_name(self
) -> str:
498 """Return a description of the requirement that triggered me.
500 This default implementation returns long description of the req, with
504 return str(self
.req
) if self
.req
else "unknown package"
507 class VcsHashUnsupported(HashError
):
508 """A hash was provided for a version-control-system-based requirement, but
509 we don't have a method for hashing those."""
513 "Can't verify hashes for these requirements because we don't "
514 "have a way to hash version control repositories:"
518 class DirectoryUrlHashUnsupported(HashError
):
519 """A hash was provided for a version-control-system-based requirement, but
520 we don't have a method for hashing those."""
524 "Can't verify hashes for these file:// requirements because they "
525 "point to directories:"
529 class HashMissing(HashError
):
530 """A hash was needed for a requirement but is absent."""
534 "Hashes are required in --require-hashes mode, but they are "
535 "missing from some requirements. Here is a list of those "
536 "requirements along with the hashes their downloaded archives "
537 "actually had. Add lines like these to your requirements files to "
538 "prevent tampering. (If you did not enable --require-hashes "
539 "manually, note that it turns on automatically when any package "
543 def __init__(self
, gotten_hash
: str) -> None:
545 :param gotten_hash: The hash of the (possibly malicious) archive we
548 self
.gotten_hash
= gotten_hash
550 def body(self
) -> str:
551 # Dodge circular import.
552 from pip
._internal
.utils
.hashes
import FAVORITE_HASH
556 # In the case of URL-based requirements, display the original URL
557 # seen in the requirements file rather than the package name,
558 # so the output can be directly copied into the requirements file.
560 self
.req
.original_link
561 if self
.req
.original_link
562 # In case someone feeds something downright stupid
563 # to InstallRequirement's constructor.
564 else getattr(self
.req
, "req", None)
566 return " {} --hash={}:{}".format(
567 package
or "unknown package", FAVORITE_HASH
, self
.gotten_hash
571 class HashUnpinned(HashError
):
572 """A requirement had a hash specified but was not pinned to a specific
577 "In --require-hashes mode, all requirements must have their "
578 "versions pinned with ==. These do not:"
582 class HashMismatch(HashError
):
584 Distribution file hash values don't match.
586 :ivar package_name: The name of the package that triggered the hash
587 mismatch. Feel free to write to this after the exception is raise to
588 improve its error message.
594 "THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "
595 "FILE. If you have updated the package versions, please update "
596 "the hashes. Otherwise, examine the package contents carefully; "
597 "someone may have tampered with them."
600 def __init__(self
, allowed
: Dict
[str, List
[str]], gots
: Dict
[str, "_Hash"]) -> None:
602 :param allowed: A dict of algorithm names pointing to lists of allowed
604 :param gots: A dict of algorithm names pointing to hashes we
605 actually got from the files under suspicion
607 self
.allowed
= allowed
610 def body(self
) -> str:
611 return " {}:\n{}".format(self
._requirement
_name
(), self
._hash
_comparison
())
613 def _hash_comparison(self
) -> str:
615 Return a comparison of actual and expected hash values.
619 Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
620 or 123451234512345123451234512345123451234512345
621 Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
625 def hash_then_or(hash_name
: str) -> "chain[str]":
626 # For now, all the decent hashes have 6-char names, so we can get
627 # away with hard-coding space literals.
628 return chain([hash_name
], repeat(" or"))
630 lines
: List
[str] = []
631 for hash_name
, expecteds
in self
.allowed
.items():
632 prefix
= hash_then_or(hash_name
)
634 (" Expected {} {}".format(next(prefix
), e
)) for e
in expecteds
637 " Got {}\n".format(self
.gots
[hash_name
].hexdigest())
639 return "\n".join(lines
)
642 class UnsupportedPythonVersion(InstallationError
):
643 """Unsupported python version according to Requires-Python package
647 class ConfigurationFileCouldNotBeLoaded(ConfigurationError
):
648 """When there are errors while loading a configuration file"""
652 reason
: str = "could not be loaded",
653 fname
: Optional
[str] = None,
654 error
: Optional
[configparser
.Error
] = None,
656 super().__init
__(error
)
661 def __str__(self
) -> str:
662 if self
.fname
is not None:
663 message_part
= f
" in {self.fname}."
665 assert self
.error
is not None
666 message_part
= f
".\n{self.error}\n"
667 return f
"Configuration file {self.reason}{message_part}"
670 _DEFAULT_EXTERNALLY_MANAGED_ERROR
= f
"""\
671 The Python environment under {sys.prefix} is managed externally, and may not be
672 manipulated by the user. Please use specific tooling from the distributor of
673 the Python installation to interact with this environment instead.
677 class ExternallyManagedEnvironment(DiagnosticPipError
):
678 """The current environment is externally managed.
680 This is raised when the current environment is externally managed, as
681 defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked
682 and displayed when the error is bubbled up to the user.
684 :param error: The error message read from ``EXTERNALLY-MANAGED``.
687 reference
= "externally-managed-environment"
689 def __init__(self
, error
: Optional
[str]) -> None:
691 context
= Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR
)
693 context
= Text(error
)
695 message
="This environment is externally managed",
698 "If you believe this is a mistake, please contact your "
699 "Python installation or OS distribution provider. "
700 "You can override this, at the risk of breaking your Python "
701 "installation or OS, by passing --break-system-packages."
703 hint_stmt
=Text("See PEP 668 for the detailed specification."),
707 def _iter_externally_managed_error_keys() -> Iterator
[str]:
708 # LC_MESSAGES is in POSIX, but not the C standard. The most common
709 # platform that does not implement this category is Windows, where
710 # using other categories for console message localization is equally
711 # unreliable, so we fall back to the locale-less vendor message. This
712 # can always be re-evaluated when a vendor proposes a new alternative.
714 category
= locale
.LC_MESSAGES
715 except AttributeError:
716 lang
: Optional
[str] = None
718 lang
, _
= locale
.getlocale(category
)
720 yield f
"Error-{lang}"
721 for sep
in ("-", "_"):
722 before
, found
, _
= lang
.partition(sep
)
725 yield f
"Error-{before}"
731 config
: Union
[pathlib
.Path
, str],
732 ) -> "ExternallyManagedEnvironment":
733 parser
= configparser
.ConfigParser(interpolation
=None)
735 parser
.read(config
, encoding
="utf-8")
736 section
= parser
["externally-managed"]
737 for key
in cls
._iter
_externally
_managed
_error
_keys
():
738 with contextlib
.suppress(KeyError):
739 return cls(section
[key
])
742 except (OSError, UnicodeDecodeError, configparser
.ParsingError
):
743 from pip
._internal
.utils
._log
import VERBOSE
745 exc_info
= logger
.isEnabledFor(VERBOSE
)
746 logger
.warning("Failed to read %s", config
, exc_info
=exc_info
)