Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
keewenaw
GitHub Repository: keewenaw/ethereum-wallet-cracker
Path: blob/main/test/lib/python3.9/site-packages/pip/_internal/exceptions.py
4799 views
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 re
10
from itertools import chain, groupby, repeat
11
from typing import TYPE_CHECKING, Dict, List, Optional, Union
12
13
from pip._vendor.requests.models import Request, Response
14
from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
15
from pip._vendor.rich.markup import escape
16
from pip._vendor.rich.text import Text
17
18
if TYPE_CHECKING:
19
from hashlib import _Hash
20
from typing import Literal
21
22
from pip._internal.metadata import BaseDistribution
23
from pip._internal.req.req_install import InstallRequirement
24
25
26
#
27
# Scaffolding
28
#
29
def _is_kebab_case(s: str) -> bool:
30
return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
31
32
33
def _prefix_with_indent(
34
s: Union[Text, str],
35
console: Console,
36
*,
37
prefix: str,
38
indent: str,
39
) -> Text:
40
if isinstance(s, Text):
41
text = s
42
else:
43
text = console.render_str(s)
44
45
return console.render_str(prefix, overflow="ignore") + console.render_str(
46
f"\n{indent}", overflow="ignore"
47
).join(text.split(allow_blank=True))
48
49
50
class PipError(Exception):
51
"""The base pip error."""
52
53
54
class DiagnosticPipError(PipError):
55
"""An error, that presents diagnostic information to the user.
56
57
This contains a bunch of logic, to enable pretty presentation of our error
58
messages. Each error gets a unique reference. Each error can also include
59
additional context, a hint and/or a note -- which are presented with the
60
main error message in a consistent style.
61
62
This is adapted from the error output styling in `sphinx-theme-builder`.
63
"""
64
65
reference: str
66
67
def __init__(
68
self,
69
*,
70
kind: 'Literal["error", "warning"]' = "error",
71
reference: Optional[str] = None,
72
message: Union[str, Text],
73
context: Optional[Union[str, Text]],
74
hint_stmt: Optional[Union[str, Text]],
75
note_stmt: Optional[Union[str, Text]] = None,
76
link: Optional[str] = None,
77
) -> None:
78
# Ensure a proper reference is provided.
79
if reference is None:
80
assert hasattr(self, "reference"), "error reference not provided!"
81
reference = self.reference
82
assert _is_kebab_case(reference), "error reference must be kebab-case!"
83
84
self.kind = kind
85
self.reference = reference
86
87
self.message = message
88
self.context = context
89
90
self.note_stmt = note_stmt
91
self.hint_stmt = hint_stmt
92
93
self.link = link
94
95
super().__init__(f"<{self.__class__.__name__}: {self.reference}>")
96
97
def __repr__(self) -> str:
98
return (
99
f"<{self.__class__.__name__}("
100
f"reference={self.reference!r}, "
101
f"message={self.message!r}, "
102
f"context={self.context!r}, "
103
f"note_stmt={self.note_stmt!r}, "
104
f"hint_stmt={self.hint_stmt!r}"
105
")>"
106
)
107
108
def __rich_console__(
109
self,
110
console: Console,
111
options: ConsoleOptions,
112
) -> RenderResult:
113
colour = "red" if self.kind == "error" else "yellow"
114
115
yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
116
yield ""
117
118
if not options.ascii_only:
119
# Present the main message, with relevant context indented.
120
if self.context is not None:
121
yield _prefix_with_indent(
122
self.message,
123
console,
124
prefix=f"[{colour}]×[/] ",
125
indent=f"[{colour}]│[/] ",
126
)
127
yield _prefix_with_indent(
128
self.context,
129
console,
130
prefix=f"[{colour}]╰─>[/] ",
131
indent=f"[{colour}] [/] ",
132
)
133
else:
134
yield _prefix_with_indent(
135
self.message,
136
console,
137
prefix="[red]×[/] ",
138
indent=" ",
139
)
140
else:
141
yield self.message
142
if self.context is not None:
143
yield ""
144
yield self.context
145
146
if self.note_stmt is not None or self.hint_stmt is not None:
147
yield ""
148
149
if self.note_stmt is not None:
150
yield _prefix_with_indent(
151
self.note_stmt,
152
console,
153
prefix="[magenta bold]note[/]: ",
154
indent=" ",
155
)
156
if self.hint_stmt is not None:
157
yield _prefix_with_indent(
158
self.hint_stmt,
159
console,
160
prefix="[cyan bold]hint[/]: ",
161
indent=" ",
162
)
163
164
if self.link is not None:
165
yield ""
166
yield f"Link: {self.link}"
167
168
169
#
170
# Actual Errors
171
#
172
class ConfigurationError(PipError):
173
"""General exception in configuration"""
174
175
176
class InstallationError(PipError):
177
"""General exception during installation"""
178
179
180
class UninstallationError(PipError):
181
"""General exception during uninstallation"""
182
183
184
class MissingPyProjectBuildRequires(DiagnosticPipError):
185
"""Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
186
187
reference = "missing-pyproject-build-system-requires"
188
189
def __init__(self, *, package: str) -> None:
190
super().__init__(
191
message=f"Can not process {escape(package)}",
192
context=Text(
193
"This package has an invalid pyproject.toml file.\n"
194
"The [build-system] table is missing the mandatory `requires` key."
195
),
196
note_stmt="This is an issue with the package mentioned above, not pip.",
197
hint_stmt=Text("See PEP 518 for the detailed specification."),
198
)
199
200
201
class InvalidPyProjectBuildRequires(DiagnosticPipError):
202
"""Raised when pyproject.toml an invalid `build-system.requires`."""
203
204
reference = "invalid-pyproject-build-system-requires"
205
206
def __init__(self, *, package: str, reason: str) -> None:
207
super().__init__(
208
message=f"Can not process {escape(package)}",
209
context=Text(
210
"This package has an invalid `build-system.requires` key in "
211
f"pyproject.toml.\n{reason}"
212
),
213
note_stmt="This is an issue with the package mentioned above, not pip.",
214
hint_stmt=Text("See PEP 518 for the detailed specification."),
215
)
216
217
218
class NoneMetadataError(PipError):
219
"""Raised when accessing a Distribution's "METADATA" or "PKG-INFO".
220
221
This signifies an inconsistency, when the Distribution claims to have
222
the metadata file (if not, raise ``FileNotFoundError`` instead), but is
223
not actually able to produce its content. This may be due to permission
224
errors.
225
"""
226
227
def __init__(
228
self,
229
dist: "BaseDistribution",
230
metadata_name: str,
231
) -> None:
232
"""
233
:param dist: A Distribution object.
234
:param metadata_name: The name of the metadata being accessed
235
(can be "METADATA" or "PKG-INFO").
236
"""
237
self.dist = dist
238
self.metadata_name = metadata_name
239
240
def __str__(self) -> str:
241
# Use `dist` in the error message because its stringification
242
# includes more information, like the version and location.
243
return "None {} metadata found for distribution: {}".format(
244
self.metadata_name,
245
self.dist,
246
)
247
248
249
class UserInstallationInvalid(InstallationError):
250
"""A --user install is requested on an environment without user site."""
251
252
def __str__(self) -> str:
253
return "User base directory is not specified"
254
255
256
class InvalidSchemeCombination(InstallationError):
257
def __str__(self) -> str:
258
before = ", ".join(str(a) for a in self.args[:-1])
259
return f"Cannot set {before} and {self.args[-1]} together"
260
261
262
class DistributionNotFound(InstallationError):
263
"""Raised when a distribution cannot be found to satisfy a requirement"""
264
265
266
class RequirementsFileParseError(InstallationError):
267
"""Raised when a general error occurs parsing a requirements file line."""
268
269
270
class BestVersionAlreadyInstalled(PipError):
271
"""Raised when the most up-to-date version of a package is already
272
installed."""
273
274
275
class BadCommand(PipError):
276
"""Raised when virtualenv or a command is not found"""
277
278
279
class CommandError(PipError):
280
"""Raised when there is an error in command-line arguments"""
281
282
283
class PreviousBuildDirError(PipError):
284
"""Raised when there's a previous conflicting build directory"""
285
286
287
class NetworkConnectionError(PipError):
288
"""HTTP connection error"""
289
290
def __init__(
291
self, error_msg: str, response: Response = None, request: Request = None
292
) -> None:
293
"""
294
Initialize NetworkConnectionError with `request` and `response`
295
objects.
296
"""
297
self.response = response
298
self.request = request
299
self.error_msg = error_msg
300
if (
301
self.response is not None
302
and not self.request
303
and hasattr(response, "request")
304
):
305
self.request = self.response.request
306
super().__init__(error_msg, response, request)
307
308
def __str__(self) -> str:
309
return str(self.error_msg)
310
311
312
class InvalidWheelFilename(InstallationError):
313
"""Invalid wheel filename."""
314
315
316
class UnsupportedWheel(InstallationError):
317
"""Unsupported wheel."""
318
319
320
class InvalidWheel(InstallationError):
321
"""Invalid (e.g. corrupt) wheel."""
322
323
def __init__(self, location: str, name: str):
324
self.location = location
325
self.name = name
326
327
def __str__(self) -> str:
328
return f"Wheel '{self.name}' located at {self.location} is invalid."
329
330
331
class MetadataInconsistent(InstallationError):
332
"""Built metadata contains inconsistent information.
333
334
This is raised when the metadata contains values (e.g. name and version)
335
that do not match the information previously obtained from sdist filename
336
or user-supplied ``#egg=`` value.
337
"""
338
339
def __init__(
340
self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
341
) -> None:
342
self.ireq = ireq
343
self.field = field
344
self.f_val = f_val
345
self.m_val = m_val
346
347
def __str__(self) -> str:
348
template = (
349
"Requested {} has inconsistent {}: "
350
"filename has {!r}, but metadata has {!r}"
351
)
352
return template.format(self.ireq, self.field, self.f_val, self.m_val)
353
354
355
class LegacyInstallFailure(DiagnosticPipError):
356
"""Error occurred while executing `setup.py install`"""
357
358
reference = "legacy-install-failure"
359
360
def __init__(self, package_details: str) -> None:
361
super().__init__(
362
message="Encountered error while trying to install package.",
363
context=package_details,
364
hint_stmt="See above for output from the failure.",
365
note_stmt="This is an issue with the package mentioned above, not pip.",
366
)
367
368
369
class InstallationSubprocessError(DiagnosticPipError, InstallationError):
370
"""A subprocess call failed."""
371
372
reference = "subprocess-exited-with-error"
373
374
def __init__(
375
self,
376
*,
377
command_description: str,
378
exit_code: int,
379
output_lines: Optional[List[str]],
380
) -> None:
381
if output_lines is None:
382
output_prompt = Text("See above for output.")
383
else:
384
output_prompt = (
385
Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
386
+ Text("".join(output_lines))
387
+ Text.from_markup(R"[red]\[end of output][/]")
388
)
389
390
super().__init__(
391
message=(
392
f"[green]{escape(command_description)}[/] did not run successfully.\n"
393
f"exit code: {exit_code}"
394
),
395
context=output_prompt,
396
hint_stmt=None,
397
note_stmt=(
398
"This error originates from a subprocess, and is likely not a "
399
"problem with pip."
400
),
401
)
402
403
self.command_description = command_description
404
self.exit_code = exit_code
405
406
def __str__(self) -> str:
407
return f"{self.command_description} exited with {self.exit_code}"
408
409
410
class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
411
reference = "metadata-generation-failed"
412
413
def __init__(
414
self,
415
*,
416
package_details: str,
417
) -> None:
418
super(InstallationSubprocessError, self).__init__(
419
message="Encountered error while generating package metadata.",
420
context=escape(package_details),
421
hint_stmt="See above for details.",
422
note_stmt="This is an issue with the package mentioned above, not pip.",
423
)
424
425
def __str__(self) -> str:
426
return "metadata generation failed"
427
428
429
class HashErrors(InstallationError):
430
"""Multiple HashError instances rolled into one for reporting"""
431
432
def __init__(self) -> None:
433
self.errors: List["HashError"] = []
434
435
def append(self, error: "HashError") -> None:
436
self.errors.append(error)
437
438
def __str__(self) -> str:
439
lines = []
440
self.errors.sort(key=lambda e: e.order)
441
for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
442
lines.append(cls.head)
443
lines.extend(e.body() for e in errors_of_cls)
444
if lines:
445
return "\n".join(lines)
446
return ""
447
448
def __bool__(self) -> bool:
449
return bool(self.errors)
450
451
452
class HashError(InstallationError):
453
"""
454
A failure to verify a package against known-good hashes
455
456
:cvar order: An int sorting hash exception classes by difficulty of
457
recovery (lower being harder), so the user doesn't bother fretting
458
about unpinned packages when he has deeper issues, like VCS
459
dependencies, to deal with. Also keeps error reports in a
460
deterministic order.
461
:cvar head: A section heading for display above potentially many
462
exceptions of this kind
463
:ivar req: The InstallRequirement that triggered this error. This is
464
pasted on after the exception is instantiated, because it's not
465
typically available earlier.
466
467
"""
468
469
req: Optional["InstallRequirement"] = None
470
head = ""
471
order: int = -1
472
473
def body(self) -> str:
474
"""Return a summary of me for display under the heading.
475
476
This default implementation simply prints a description of the
477
triggering requirement.
478
479
:param req: The InstallRequirement that provoked this error, with
480
its link already populated by the resolver's _populate_link().
481
482
"""
483
return f" {self._requirement_name()}"
484
485
def __str__(self) -> str:
486
return f"{self.head}\n{self.body()}"
487
488
def _requirement_name(self) -> str:
489
"""Return a description of the requirement that triggered me.
490
491
This default implementation returns long description of the req, with
492
line numbers
493
494
"""
495
return str(self.req) if self.req else "unknown package"
496
497
498
class VcsHashUnsupported(HashError):
499
"""A hash was provided for a version-control-system-based requirement, but
500
we don't have a method for hashing those."""
501
502
order = 0
503
head = (
504
"Can't verify hashes for these requirements because we don't "
505
"have a way to hash version control repositories:"
506
)
507
508
509
class DirectoryUrlHashUnsupported(HashError):
510
"""A hash was provided for a version-control-system-based requirement, but
511
we don't have a method for hashing those."""
512
513
order = 1
514
head = (
515
"Can't verify hashes for these file:// requirements because they "
516
"point to directories:"
517
)
518
519
520
class HashMissing(HashError):
521
"""A hash was needed for a requirement but is absent."""
522
523
order = 2
524
head = (
525
"Hashes are required in --require-hashes mode, but they are "
526
"missing from some requirements. Here is a list of those "
527
"requirements along with the hashes their downloaded archives "
528
"actually had. Add lines like these to your requirements files to "
529
"prevent tampering. (If you did not enable --require-hashes "
530
"manually, note that it turns on automatically when any package "
531
"has a hash.)"
532
)
533
534
def __init__(self, gotten_hash: str) -> None:
535
"""
536
:param gotten_hash: The hash of the (possibly malicious) archive we
537
just downloaded
538
"""
539
self.gotten_hash = gotten_hash
540
541
def body(self) -> str:
542
# Dodge circular import.
543
from pip._internal.utils.hashes import FAVORITE_HASH
544
545
package = None
546
if self.req:
547
# In the case of URL-based requirements, display the original URL
548
# seen in the requirements file rather than the package name,
549
# so the output can be directly copied into the requirements file.
550
package = (
551
self.req.original_link
552
if self.req.original_link
553
# In case someone feeds something downright stupid
554
# to InstallRequirement's constructor.
555
else getattr(self.req, "req", None)
556
)
557
return " {} --hash={}:{}".format(
558
package or "unknown package", FAVORITE_HASH, self.gotten_hash
559
)
560
561
562
class HashUnpinned(HashError):
563
"""A requirement had a hash specified but was not pinned to a specific
564
version."""
565
566
order = 3
567
head = (
568
"In --require-hashes mode, all requirements must have their "
569
"versions pinned with ==. These do not:"
570
)
571
572
573
class HashMismatch(HashError):
574
"""
575
Distribution file hash values don't match.
576
577
:ivar package_name: The name of the package that triggered the hash
578
mismatch. Feel free to write to this after the exception is raise to
579
improve its error message.
580
581
"""
582
583
order = 4
584
head = (
585
"THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "
586
"FILE. If you have updated the package versions, please update "
587
"the hashes. Otherwise, examine the package contents carefully; "
588
"someone may have tampered with them."
589
)
590
591
def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
592
"""
593
:param allowed: A dict of algorithm names pointing to lists of allowed
594
hex digests
595
:param gots: A dict of algorithm names pointing to hashes we
596
actually got from the files under suspicion
597
"""
598
self.allowed = allowed
599
self.gots = gots
600
601
def body(self) -> str:
602
return " {}:\n{}".format(self._requirement_name(), self._hash_comparison())
603
604
def _hash_comparison(self) -> str:
605
"""
606
Return a comparison of actual and expected hash values.
607
608
Example::
609
610
Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
611
or 123451234512345123451234512345123451234512345
612
Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
613
614
"""
615
616
def hash_then_or(hash_name: str) -> "chain[str]":
617
# For now, all the decent hashes have 6-char names, so we can get
618
# away with hard-coding space literals.
619
return chain([hash_name], repeat(" or"))
620
621
lines: List[str] = []
622
for hash_name, expecteds in self.allowed.items():
623
prefix = hash_then_or(hash_name)
624
lines.extend(
625
(" Expected {} {}".format(next(prefix), e)) for e in expecteds
626
)
627
lines.append(
628
" Got {}\n".format(self.gots[hash_name].hexdigest())
629
)
630
return "\n".join(lines)
631
632
633
class UnsupportedPythonVersion(InstallationError):
634
"""Unsupported python version according to Requires-Python package
635
metadata."""
636
637
638
class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
639
"""When there are errors while loading a configuration file"""
640
641
def __init__(
642
self,
643
reason: str = "could not be loaded",
644
fname: Optional[str] = None,
645
error: Optional[configparser.Error] = None,
646
) -> None:
647
super().__init__(error)
648
self.reason = reason
649
self.fname = fname
650
self.error = error
651
652
def __str__(self) -> str:
653
if self.fname is not None:
654
message_part = f" in {self.fname}."
655
else:
656
assert self.error is not None
657
message_part = f".\n{self.error}\n"
658
return f"Configuration file {self.reason}{message_part}"
659
660