]> crepu.dev Git - config.git/blame - djavu-asus/elpy/rpc-venv/lib/python3.11/site-packages/pip/_internal/exceptions.py
Actualizado el Readme
[config.git] / djavu-asus / elpy / rpc-venv / lib / python3.11 / site-packages / pip / _internal / exceptions.py
CommitLineData
53e6db90
DC
1"""Exceptions used throughout package.
2
3This module MUST NOT try to import from anything within `pip._internal` to
4operate. This is expected to be importable from any/all files within the
5subpackage and, thus, should not depend on them.
6"""
7
8import configparser
9import contextlib
10import locale
11import logging
12import pathlib
13import re
14import sys
15from itertools import chain, groupby, repeat
16from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Union
17
18from pip._vendor.requests.models import Request, Response
19from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
20from pip._vendor.rich.markup import escape
21from pip._vendor.rich.text import Text
22
23if TYPE_CHECKING:
24 from hashlib import _Hash
25 from typing import Literal
26
27 from pip._internal.metadata import BaseDistribution
28 from pip._internal.req.req_install import InstallRequirement
29
30logger = logging.getLogger(__name__)
31
32
33#
34# Scaffolding
35#
36def _is_kebab_case(s: str) -> bool:
37 return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
38
39
40def _prefix_with_indent(
41 s: Union[Text, str],
42 console: Console,
43 *,
44 prefix: str,
45 indent: str,
46) -> Text:
47 if isinstance(s, Text):
48 text = s
49 else:
50 text = console.render_str(s)
51
52 return console.render_str(prefix, overflow="ignore") + console.render_str(
53 f"\n{indent}", overflow="ignore"
54 ).join(text.split(allow_blank=True))
55
56
57class PipError(Exception):
58 """The base pip error."""
59
60
61class DiagnosticPipError(PipError):
62 """An error, that presents diagnostic information to the user.
63
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.
68
69 This is adapted from the error output styling in `sphinx-theme-builder`.
70 """
71
72 reference: str
73
74 def __init__(
75 self,
76 *,
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,
84 ) -> None:
85 # Ensure a proper reference is provided.
86 if reference is None:
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!"
90
91 self.kind = kind
92 self.reference = reference
93
94 self.message = message
95 self.context = context
96
97 self.note_stmt = note_stmt
98 self.hint_stmt = hint_stmt
99
100 self.link = link
101
102 super().__init__(f"<{self.__class__.__name__}: {self.reference}>")
103
104 def __repr__(self) -> str:
105 return (
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}"
112 ")>"
113 )
114
115 def __rich_console__(
116 self,
117 console: Console,
118 options: ConsoleOptions,
119 ) -> RenderResult:
120 colour = "red" if self.kind == "error" else "yellow"
121
122 yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
123 yield ""
124
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(
129 self.message,
130 console,
131 prefix=f"[{colour}]×[/] ",
132 indent=f"[{colour}]│[/] ",
133 )
134 yield _prefix_with_indent(
135 self.context,
136 console,
137 prefix=f"[{colour}]╰─>[/] ",
138 indent=f"[{colour}] [/] ",
139 )
140 else:
141 yield _prefix_with_indent(
142 self.message,
143 console,
144 prefix="[red]×[/] ",
145 indent=" ",
146 )
147 else:
148 yield self.message
149 if self.context is not None:
150 yield ""
151 yield self.context
152
153 if self.note_stmt is not None or self.hint_stmt is not None:
154 yield ""
155
156 if self.note_stmt is not None:
157 yield _prefix_with_indent(
158 self.note_stmt,
159 console,
160 prefix="[magenta bold]note[/]: ",
161 indent=" ",
162 )
163 if self.hint_stmt is not None:
164 yield _prefix_with_indent(
165 self.hint_stmt,
166 console,
167 prefix="[cyan bold]hint[/]: ",
168 indent=" ",
169 )
170
171 if self.link is not None:
172 yield ""
173 yield f"Link: {self.link}"
174
175
176#
177# Actual Errors
178#
179class ConfigurationError(PipError):
180 """General exception in configuration"""
181
182
183class InstallationError(PipError):
184 """General exception during installation"""
185
186
187class UninstallationError(PipError):
188 """General exception during uninstallation"""
189
190
191class MissingPyProjectBuildRequires(DiagnosticPipError):
192 """Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
193
194 reference = "missing-pyproject-build-system-requires"
195
196 def __init__(self, *, package: str) -> None:
197 super().__init__(
198 message=f"Can not process {escape(package)}",
199 context=Text(
200 "This package has an invalid pyproject.toml file.\n"
201 "The [build-system] table is missing the mandatory `requires` key."
202 ),
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."),
205 )
206
207
208class InvalidPyProjectBuildRequires(DiagnosticPipError):
209 """Raised when pyproject.toml an invalid `build-system.requires`."""
210
211 reference = "invalid-pyproject-build-system-requires"
212
213 def __init__(self, *, package: str, reason: str) -> None:
214 super().__init__(
215 message=f"Can not process {escape(package)}",
216 context=Text(
217 "This package has an invalid `build-system.requires` key in "
218 f"pyproject.toml.\n{reason}"
219 ),
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."),
222 )
223
224
225class NoneMetadataError(PipError):
226 """Raised when accessing a Distribution's "METADATA" or "PKG-INFO".
227
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
231 errors.
232 """
233
234 def __init__(
235 self,
236 dist: "BaseDistribution",
237 metadata_name: str,
238 ) -> None:
239 """
240 :param dist: A Distribution object.
241 :param metadata_name: The name of the metadata being accessed
242 (can be "METADATA" or "PKG-INFO").
243 """
244 self.dist = dist
245 self.metadata_name = metadata_name
246
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(
251 self.metadata_name,
252 self.dist,
253 )
254
255
256class UserInstallationInvalid(InstallationError):
257 """A --user install is requested on an environment without user site."""
258
259 def __str__(self) -> str:
260 return "User base directory is not specified"
261
262
263class 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"
267
268
269class DistributionNotFound(InstallationError):
270 """Raised when a distribution cannot be found to satisfy a requirement"""
271
272
273class RequirementsFileParseError(InstallationError):
274 """Raised when a general error occurs parsing a requirements file line."""
275
276
277class BestVersionAlreadyInstalled(PipError):
278 """Raised when the most up-to-date version of a package is already
279 installed."""
280
281
282class BadCommand(PipError):
283 """Raised when virtualenv or a command is not found"""
284
285
286class CommandError(PipError):
287 """Raised when there is an error in command-line arguments"""
288
289
290class PreviousBuildDirError(PipError):
291 """Raised when there's a previous conflicting build directory"""
292
293
294class NetworkConnectionError(PipError):
295 """HTTP connection error"""
296
297 def __init__(
298 self,
299 error_msg: str,
300 response: Optional[Response] = None,
301 request: Optional[Request] = None,
302 ) -> None:
303 """
304 Initialize NetworkConnectionError with `request` and `response`
305 objects.
306 """
307 self.response = response
308 self.request = request
309 self.error_msg = error_msg
310 if (
311 self.response is not None
312 and not self.request
313 and hasattr(response, "request")
314 ):
315 self.request = self.response.request
316 super().__init__(error_msg, response, request)
317
318 def __str__(self) -> str:
319 return str(self.error_msg)
320
321
322class InvalidWheelFilename(InstallationError):
323 """Invalid wheel filename."""
324
325
326class UnsupportedWheel(InstallationError):
327 """Unsupported wheel."""
328
329
330class InvalidWheel(InstallationError):
331 """Invalid (e.g. corrupt) wheel."""
332
333 def __init__(self, location: str, name: str):
334 self.location = location
335 self.name = name
336
337 def __str__(self) -> str:
338 return f"Wheel '{self.name}' located at {self.location} is invalid."
339
340
341class MetadataInconsistent(InstallationError):
342 """Built metadata contains inconsistent information.
343
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.
347 """
348
349 def __init__(
350 self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
351 ) -> None:
352 self.ireq = ireq
353 self.field = field
354 self.f_val = f_val
355 self.m_val = m_val
356
357 def __str__(self) -> str:
358 return (
359 f"Requested {self.ireq} has inconsistent {self.field}: "
360 f"expected {self.f_val!r}, but metadata has {self.m_val!r}"
361 )
362
363
364class LegacyInstallFailure(DiagnosticPipError):
365 """Error occurred while executing `setup.py install`"""
366
367 reference = "legacy-install-failure"
368
369 def __init__(self, package_details: str) -> None:
370 super().__init__(
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.",
375 )
376
377
378class InstallationSubprocessError(DiagnosticPipError, InstallationError):
379 """A subprocess call failed."""
380
381 reference = "subprocess-exited-with-error"
382
383 def __init__(
384 self,
385 *,
386 command_description: str,
387 exit_code: int,
388 output_lines: Optional[List[str]],
389 ) -> None:
390 if output_lines is None:
391 output_prompt = Text("See above for output.")
392 else:
393 output_prompt = (
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][/]")
397 )
398
399 super().__init__(
400 message=(
401 f"[green]{escape(command_description)}[/] did not run successfully.\n"
402 f"exit code: {exit_code}"
403 ),
404 context=output_prompt,
405 hint_stmt=None,
406 note_stmt=(
407 "This error originates from a subprocess, and is likely not a "
408 "problem with pip."
409 ),
410 )
411
412 self.command_description = command_description
413 self.exit_code = exit_code
414
415 def __str__(self) -> str:
416 return f"{self.command_description} exited with {self.exit_code}"
417
418
419class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
420 reference = "metadata-generation-failed"
421
422 def __init__(
423 self,
424 *,
425 package_details: str,
426 ) -> None:
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.",
432 )
433
434 def __str__(self) -> str:
435 return "metadata generation failed"
436
437
438class HashErrors(InstallationError):
439 """Multiple HashError instances rolled into one for reporting"""
440
441 def __init__(self) -> None:
442 self.errors: List["HashError"] = []
443
444 def append(self, error: "HashError") -> None:
445 self.errors.append(error)
446
447 def __str__(self) -> str:
448 lines = []
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)
453 if lines:
454 return "\n".join(lines)
455 return ""
456
457 def __bool__(self) -> bool:
458 return bool(self.errors)
459
460
461class HashError(InstallationError):
462 """
463 A failure to verify a package against known-good hashes
464
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
469 deterministic order.
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.
475
476 """
477
478 req: Optional["InstallRequirement"] = None
479 head = ""
480 order: int = -1
481
482 def body(self) -> str:
483 """Return a summary of me for display under the heading.
484
485 This default implementation simply prints a description of the
486 triggering requirement.
487
488 :param req: The InstallRequirement that provoked this error, with
489 its link already populated by the resolver's _populate_link().
490
491 """
492 return f" {self._requirement_name()}"
493
494 def __str__(self) -> str:
495 return f"{self.head}\n{self.body()}"
496
497 def _requirement_name(self) -> str:
498 """Return a description of the requirement that triggered me.
499
500 This default implementation returns long description of the req, with
501 line numbers
502
503 """
504 return str(self.req) if self.req else "unknown package"
505
506
507class 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."""
510
511 order = 0
512 head = (
513 "Can't verify hashes for these requirements because we don't "
514 "have a way to hash version control repositories:"
515 )
516
517
518class 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."""
521
522 order = 1
523 head = (
524 "Can't verify hashes for these file:// requirements because they "
525 "point to directories:"
526 )
527
528
529class HashMissing(HashError):
530 """A hash was needed for a requirement but is absent."""
531
532 order = 2
533 head = (
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 "
540 "has a hash.)"
541 )
542
543 def __init__(self, gotten_hash: str) -> None:
544 """
545 :param gotten_hash: The hash of the (possibly malicious) archive we
546 just downloaded
547 """
548 self.gotten_hash = gotten_hash
549
550 def body(self) -> str:
551 # Dodge circular import.
552 from pip._internal.utils.hashes import FAVORITE_HASH
553
554 package = None
555 if self.req:
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.
559 package = (
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)
565 )
566 return " {} --hash={}:{}".format(
567 package or "unknown package", FAVORITE_HASH, self.gotten_hash
568 )
569
570
571class HashUnpinned(HashError):
572 """A requirement had a hash specified but was not pinned to a specific
573 version."""
574
575 order = 3
576 head = (
577 "In --require-hashes mode, all requirements must have their "
578 "versions pinned with ==. These do not:"
579 )
580
581
582class HashMismatch(HashError):
583 """
584 Distribution file hash values don't match.
585
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.
589
590 """
591
592 order = 4
593 head = (
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."
598 )
599
600 def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
601 """
602 :param allowed: A dict of algorithm names pointing to lists of allowed
603 hex digests
604 :param gots: A dict of algorithm names pointing to hashes we
605 actually got from the files under suspicion
606 """
607 self.allowed = allowed
608 self.gots = gots
609
610 def body(self) -> str:
611 return " {}:\n{}".format(self._requirement_name(), self._hash_comparison())
612
613 def _hash_comparison(self) -> str:
614 """
615 Return a comparison of actual and expected hash values.
616
617 Example::
618
619 Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
620 or 123451234512345123451234512345123451234512345
621 Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
622
623 """
624
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"))
629
630 lines: List[str] = []
631 for hash_name, expecteds in self.allowed.items():
632 prefix = hash_then_or(hash_name)
633 lines.extend(
634 (" Expected {} {}".format(next(prefix), e)) for e in expecteds
635 )
636 lines.append(
637 " Got {}\n".format(self.gots[hash_name].hexdigest())
638 )
639 return "\n".join(lines)
640
641
642class UnsupportedPythonVersion(InstallationError):
643 """Unsupported python version according to Requires-Python package
644 metadata."""
645
646
647class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
648 """When there are errors while loading a configuration file"""
649
650 def __init__(
651 self,
652 reason: str = "could not be loaded",
653 fname: Optional[str] = None,
654 error: Optional[configparser.Error] = None,
655 ) -> None:
656 super().__init__(error)
657 self.reason = reason
658 self.fname = fname
659 self.error = error
660
661 def __str__(self) -> str:
662 if self.fname is not None:
663 message_part = f" in {self.fname}."
664 else:
665 assert self.error is not None
666 message_part = f".\n{self.error}\n"
667 return f"Configuration file {self.reason}{message_part}"
668
669
670_DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\
671The Python environment under {sys.prefix} is managed externally, and may not be
672manipulated by the user. Please use specific tooling from the distributor of
673the Python installation to interact with this environment instead.
674"""
675
676
677class ExternallyManagedEnvironment(DiagnosticPipError):
678 """The current environment is externally managed.
679
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.
683
684 :param error: The error message read from ``EXTERNALLY-MANAGED``.
685 """
686
687 reference = "externally-managed-environment"
688
689 def __init__(self, error: Optional[str]) -> None:
690 if error is None:
691 context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
692 else:
693 context = Text(error)
694 super().__init__(
695 message="This environment is externally managed",
696 context=context,
697 note_stmt=(
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."
702 ),
703 hint_stmt=Text("See PEP 668 for the detailed specification."),
704 )
705
706 @staticmethod
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.
713 try:
714 category = locale.LC_MESSAGES
715 except AttributeError:
716 lang: Optional[str] = None
717 else:
718 lang, _ = locale.getlocale(category)
719 if lang is not None:
720 yield f"Error-{lang}"
721 for sep in ("-", "_"):
722 before, found, _ = lang.partition(sep)
723 if not found:
724 continue
725 yield f"Error-{before}"
726 yield "Error"
727
728 @classmethod
729 def from_config(
730 cls,
731 config: Union[pathlib.Path, str],
732 ) -> "ExternallyManagedEnvironment":
733 parser = configparser.ConfigParser(interpolation=None)
734 try:
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])
740 except KeyError:
741 pass
742 except (OSError, UnicodeDecodeError, configparser.ParsingError):
743 from pip._internal.utils._log import VERBOSE
744
745 exc_info = logger.isEnabledFor(VERBOSE)
746 logger.warning("Failed to read %s", config, exc_info=exc_info)
747 return cls(None)