]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_internal/exceptions.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _internal / exceptions.py
1 """Exceptions used throughout package.
2
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.
6 """
7
8 import configparser
9 import contextlib
10 import locale
11 import logging
12 import pathlib
13 import re
14 import sys
15 from itertools import chain, groupby, repeat
16 from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Union
17
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
22
23 if 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
30 logger = logging.getLogger(__name__)
31
32
33 #
34 # Scaffolding
35 #
36 def _is_kebab_case(s: str) -> bool:
37 return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
38
39
40 def _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
57 class PipError(Exception):
58 """The base pip error."""
59
60
61 class 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 #
179 class ConfigurationError(PipError):
180 """General exception in configuration"""
181
182
183 class InstallationError(PipError):
184 """General exception during installation"""
185
186
187 class UninstallationError(PipError):
188 """General exception during uninstallation"""
189
190
191 class 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
208 class 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
225 class 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
256 class 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
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"
267
268
269 class DistributionNotFound(InstallationError):
270 """Raised when a distribution cannot be found to satisfy a requirement"""
271
272
273 class RequirementsFileParseError(InstallationError):
274 """Raised when a general error occurs parsing a requirements file line."""
275
276
277 class BestVersionAlreadyInstalled(PipError):
278 """Raised when the most up-to-date version of a package is already
279 installed."""
280
281
282 class BadCommand(PipError):
283 """Raised when virtualenv or a command is not found"""
284
285
286 class CommandError(PipError):
287 """Raised when there is an error in command-line arguments"""
288
289
290 class PreviousBuildDirError(PipError):
291 """Raised when there's a previous conflicting build directory"""
292
293
294 class 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
322 class InvalidWheelFilename(InstallationError):
323 """Invalid wheel filename."""
324
325
326 class UnsupportedWheel(InstallationError):
327 """Unsupported wheel."""
328
329
330 class 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
341 class 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
364 class InstallationSubprocessError(DiagnosticPipError, InstallationError):
365 """A subprocess call failed."""
366
367 reference = "subprocess-exited-with-error"
368
369 def __init__(
370 self,
371 *,
372 command_description: str,
373 exit_code: int,
374 output_lines: Optional[List[str]],
375 ) -> None:
376 if output_lines is None:
377 output_prompt = Text("See above for output.")
378 else:
379 output_prompt = (
380 Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
381 + Text("".join(output_lines))
382 + Text.from_markup(R"[red]\[end of output][/]")
383 )
384
385 super().__init__(
386 message=(
387 f"[green]{escape(command_description)}[/] did not run successfully.\n"
388 f"exit code: {exit_code}"
389 ),
390 context=output_prompt,
391 hint_stmt=None,
392 note_stmt=(
393 "This error originates from a subprocess, and is likely not a "
394 "problem with pip."
395 ),
396 )
397
398 self.command_description = command_description
399 self.exit_code = exit_code
400
401 def __str__(self) -> str:
402 return f"{self.command_description} exited with {self.exit_code}"
403
404
405 class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
406 reference = "metadata-generation-failed"
407
408 def __init__(
409 self,
410 *,
411 package_details: str,
412 ) -> None:
413 super(InstallationSubprocessError, self).__init__(
414 message="Encountered error while generating package metadata.",
415 context=escape(package_details),
416 hint_stmt="See above for details.",
417 note_stmt="This is an issue with the package mentioned above, not pip.",
418 )
419
420 def __str__(self) -> str:
421 return "metadata generation failed"
422
423
424 class HashErrors(InstallationError):
425 """Multiple HashError instances rolled into one for reporting"""
426
427 def __init__(self) -> None:
428 self.errors: List["HashError"] = []
429
430 def append(self, error: "HashError") -> None:
431 self.errors.append(error)
432
433 def __str__(self) -> str:
434 lines = []
435 self.errors.sort(key=lambda e: e.order)
436 for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
437 lines.append(cls.head)
438 lines.extend(e.body() for e in errors_of_cls)
439 if lines:
440 return "\n".join(lines)
441 return ""
442
443 def __bool__(self) -> bool:
444 return bool(self.errors)
445
446
447 class HashError(InstallationError):
448 """
449 A failure to verify a package against known-good hashes
450
451 :cvar order: An int sorting hash exception classes by difficulty of
452 recovery (lower being harder), so the user doesn't bother fretting
453 about unpinned packages when he has deeper issues, like VCS
454 dependencies, to deal with. Also keeps error reports in a
455 deterministic order.
456 :cvar head: A section heading for display above potentially many
457 exceptions of this kind
458 :ivar req: The InstallRequirement that triggered this error. This is
459 pasted on after the exception is instantiated, because it's not
460 typically available earlier.
461
462 """
463
464 req: Optional["InstallRequirement"] = None
465 head = ""
466 order: int = -1
467
468 def body(self) -> str:
469 """Return a summary of me for display under the heading.
470
471 This default implementation simply prints a description of the
472 triggering requirement.
473
474 :param req: The InstallRequirement that provoked this error, with
475 its link already populated by the resolver's _populate_link().
476
477 """
478 return f" {self._requirement_name()}"
479
480 def __str__(self) -> str:
481 return f"{self.head}\n{self.body()}"
482
483 def _requirement_name(self) -> str:
484 """Return a description of the requirement that triggered me.
485
486 This default implementation returns long description of the req, with
487 line numbers
488
489 """
490 return str(self.req) if self.req else "unknown package"
491
492
493 class VcsHashUnsupported(HashError):
494 """A hash was provided for a version-control-system-based requirement, but
495 we don't have a method for hashing those."""
496
497 order = 0
498 head = (
499 "Can't verify hashes for these requirements because we don't "
500 "have a way to hash version control repositories:"
501 )
502
503
504 class DirectoryUrlHashUnsupported(HashError):
505 """A hash was provided for a version-control-system-based requirement, but
506 we don't have a method for hashing those."""
507
508 order = 1
509 head = (
510 "Can't verify hashes for these file:// requirements because they "
511 "point to directories:"
512 )
513
514
515 class HashMissing(HashError):
516 """A hash was needed for a requirement but is absent."""
517
518 order = 2
519 head = (
520 "Hashes are required in --require-hashes mode, but they are "
521 "missing from some requirements. Here is a list of those "
522 "requirements along with the hashes their downloaded archives "
523 "actually had. Add lines like these to your requirements files to "
524 "prevent tampering. (If you did not enable --require-hashes "
525 "manually, note that it turns on automatically when any package "
526 "has a hash.)"
527 )
528
529 def __init__(self, gotten_hash: str) -> None:
530 """
531 :param gotten_hash: The hash of the (possibly malicious) archive we
532 just downloaded
533 """
534 self.gotten_hash = gotten_hash
535
536 def body(self) -> str:
537 # Dodge circular import.
538 from pip._internal.utils.hashes import FAVORITE_HASH
539
540 package = None
541 if self.req:
542 # In the case of URL-based requirements, display the original URL
543 # seen in the requirements file rather than the package name,
544 # so the output can be directly copied into the requirements file.
545 package = (
546 self.req.original_link
547 if self.req.is_direct
548 # In case someone feeds something downright stupid
549 # to InstallRequirement's constructor.
550 else getattr(self.req, "req", None)
551 )
552 return " {} --hash={}:{}".format(
553 package or "unknown package", FAVORITE_HASH, self.gotten_hash
554 )
555
556
557 class HashUnpinned(HashError):
558 """A requirement had a hash specified but was not pinned to a specific
559 version."""
560
561 order = 3
562 head = (
563 "In --require-hashes mode, all requirements must have their "
564 "versions pinned with ==. These do not:"
565 )
566
567
568 class HashMismatch(HashError):
569 """
570 Distribution file hash values don't match.
571
572 :ivar package_name: The name of the package that triggered the hash
573 mismatch. Feel free to write to this after the exception is raise to
574 improve its error message.
575
576 """
577
578 order = 4
579 head = (
580 "THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "
581 "FILE. If you have updated the package versions, please update "
582 "the hashes. Otherwise, examine the package contents carefully; "
583 "someone may have tampered with them."
584 )
585
586 def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
587 """
588 :param allowed: A dict of algorithm names pointing to lists of allowed
589 hex digests
590 :param gots: A dict of algorithm names pointing to hashes we
591 actually got from the files under suspicion
592 """
593 self.allowed = allowed
594 self.gots = gots
595
596 def body(self) -> str:
597 return " {}:\n{}".format(self._requirement_name(), self._hash_comparison())
598
599 def _hash_comparison(self) -> str:
600 """
601 Return a comparison of actual and expected hash values.
602
603 Example::
604
605 Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
606 or 123451234512345123451234512345123451234512345
607 Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
608
609 """
610
611 def hash_then_or(hash_name: str) -> "chain[str]":
612 # For now, all the decent hashes have 6-char names, so we can get
613 # away with hard-coding space literals.
614 return chain([hash_name], repeat(" or"))
615
616 lines: List[str] = []
617 for hash_name, expecteds in self.allowed.items():
618 prefix = hash_then_or(hash_name)
619 lines.extend(
620 (" Expected {} {}".format(next(prefix), e)) for e in expecteds
621 )
622 lines.append(
623 " Got {}\n".format(self.gots[hash_name].hexdigest())
624 )
625 return "\n".join(lines)
626
627
628 class UnsupportedPythonVersion(InstallationError):
629 """Unsupported python version according to Requires-Python package
630 metadata."""
631
632
633 class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
634 """When there are errors while loading a configuration file"""
635
636 def __init__(
637 self,
638 reason: str = "could not be loaded",
639 fname: Optional[str] = None,
640 error: Optional[configparser.Error] = None,
641 ) -> None:
642 super().__init__(error)
643 self.reason = reason
644 self.fname = fname
645 self.error = error
646
647 def __str__(self) -> str:
648 if self.fname is not None:
649 message_part = f" in {self.fname}."
650 else:
651 assert self.error is not None
652 message_part = f".\n{self.error}\n"
653 return f"Configuration file {self.reason}{message_part}"
654
655
656 _DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\
657 The Python environment under {sys.prefix} is managed externally, and may not be
658 manipulated by the user. Please use specific tooling from the distributor of
659 the Python installation to interact with this environment instead.
660 """
661
662
663 class ExternallyManagedEnvironment(DiagnosticPipError):
664 """The current environment is externally managed.
665
666 This is raised when the current environment is externally managed, as
667 defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked
668 and displayed when the error is bubbled up to the user.
669
670 :param error: The error message read from ``EXTERNALLY-MANAGED``.
671 """
672
673 reference = "externally-managed-environment"
674
675 def __init__(self, error: Optional[str]) -> None:
676 if error is None:
677 context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
678 else:
679 context = Text(error)
680 super().__init__(
681 message="This environment is externally managed",
682 context=context,
683 note_stmt=(
684 "If you believe this is a mistake, please contact your "
685 "Python installation or OS distribution provider. "
686 "You can override this, at the risk of breaking your Python "
687 "installation or OS, by passing --break-system-packages."
688 ),
689 hint_stmt=Text("See PEP 668 for the detailed specification."),
690 )
691
692 @staticmethod
693 def _iter_externally_managed_error_keys() -> Iterator[str]:
694 # LC_MESSAGES is in POSIX, but not the C standard. The most common
695 # platform that does not implement this category is Windows, where
696 # using other categories for console message localization is equally
697 # unreliable, so we fall back to the locale-less vendor message. This
698 # can always be re-evaluated when a vendor proposes a new alternative.
699 try:
700 category = locale.LC_MESSAGES
701 except AttributeError:
702 lang: Optional[str] = None
703 else:
704 lang, _ = locale.getlocale(category)
705 if lang is not None:
706 yield f"Error-{lang}"
707 for sep in ("-", "_"):
708 before, found, _ = lang.partition(sep)
709 if not found:
710 continue
711 yield f"Error-{before}"
712 yield "Error"
713
714 @classmethod
715 def from_config(
716 cls,
717 config: Union[pathlib.Path, str],
718 ) -> "ExternallyManagedEnvironment":
719 parser = configparser.ConfigParser(interpolation=None)
720 try:
721 parser.read(config, encoding="utf-8")
722 section = parser["externally-managed"]
723 for key in cls._iter_externally_managed_error_keys():
724 with contextlib.suppress(KeyError):
725 return cls(section[key])
726 except KeyError:
727 pass
728 except (OSError, UnicodeDecodeError, configparser.ParsingError):
729 from pip._internal.utils._log import VERBOSE
730
731 exc_info = logger.isEnabledFor(VERBOSE)
732 logger.warning("Failed to read %s", config, exc_info=exc_info)
733 return cls(None)