1
1
"""Exceptions used throughout package"""
2
2
3
- import configparser
4
- from itertools import chain , groupby , repeat
5
- from typing import TYPE_CHECKING , Dict , List , Optional
3
+ from itertools import groupby
4
+ from typing import TYPE_CHECKING , Dict , List
6
5
7
- from fetchcode .vcs .pip ._vendor .pkg_resources import Distribution
8
6
from fetchcode .vcs .pip ._vendor .requests .models import Request , Response
9
7
10
8
if TYPE_CHECKING :
11
9
from hashlib import _Hash
12
10
13
- from fetchcode .vcs .pip ._internal .req .req_install import InstallRequirement
14
-
15
-
16
11
class PipError (Exception ):
17
12
"""Base pip exception"""
18
13
19
-
20
- class ConfigurationError (PipError ):
21
- """General exception in configuration"""
22
-
23
-
24
14
class InstallationError (PipError ):
25
15
"""General exception during installation"""
26
16
27
17
28
- class UninstallationError (PipError ):
29
- """General exception during uninstallation"""
30
-
31
-
32
- class NoneMetadataError (PipError ):
33
- """
34
- Raised when accessing "METADATA" or "PKG-INFO" metadata for a
35
- pip._vendor.pkg_resources.Distribution object and
36
- `dist.has_metadata('METADATA')` returns True but
37
- `dist.get_metadata('METADATA')` returns None (and similarly for
38
- "PKG-INFO").
39
- """
40
-
41
- def __init__ (self , dist , metadata_name ):
42
- # type: (Distribution, str) -> None
43
- """
44
- :param dist: A Distribution object.
45
- :param metadata_name: The name of the metadata being accessed
46
- (can be "METADATA" or "PKG-INFO").
47
- """
48
- self .dist = dist
49
- self .metadata_name = metadata_name
50
-
51
- def __str__ (self ):
52
- # type: () -> str
53
- # Use `dist` in the error message because its stringification
54
- # includes more information, like the version and location.
55
- return (
56
- 'None {} metadata found for distribution: {}' .format (
57
- self .metadata_name , self .dist ,
58
- )
59
- )
60
-
61
-
62
- class UserInstallationInvalid (InstallationError ):
63
- """A --user install is requested on an environment without user site."""
64
-
65
- def __str__ (self ):
66
- # type: () -> str
67
- return "User base directory is not specified"
68
-
69
-
70
- class InvalidSchemeCombination (InstallationError ):
71
- def __str__ (self ):
72
- # type: () -> str
73
- before = ", " .join (str (a ) for a in self .args [:- 1 ])
74
- return f"Cannot set { before } and { self .args [- 1 ]} together"
75
-
76
-
77
- class DistributionNotFound (InstallationError ):
78
- """Raised when a distribution cannot be found to satisfy a requirement"""
79
-
80
-
81
- class RequirementsFileParseError (InstallationError ):
82
- """Raised when a general error occurs parsing a requirements file line."""
83
-
84
-
85
- class BestVersionAlreadyInstalled (PipError ):
86
- """Raised when the most up-to-date version of a package is already
87
- installed."""
88
-
89
-
90
18
class BadCommand (PipError ):
91
19
"""Raised when virtualenv or a command is not found"""
92
20
@@ -95,10 +23,6 @@ class CommandError(PipError):
95
23
"""Raised when there is an error in command-line arguments"""
96
24
97
25
98
- class PreviousBuildDirError (PipError ):
99
- """Raised when there's a previous conflicting build directory"""
100
-
101
-
102
26
class NetworkConnectionError (PipError ):
103
27
"""HTTP connection error"""
104
28
@@ -121,37 +45,6 @@ def __str__(self):
121
45
return str (self .error_msg )
122
46
123
47
124
- class InvalidWheelFilename (InstallationError ):
125
- """Invalid wheel filename."""
126
-
127
-
128
- class UnsupportedWheel (InstallationError ):
129
- """Unsupported wheel."""
130
-
131
-
132
- class MetadataInconsistent (InstallationError ):
133
- """Built metadata contains inconsistent information.
134
-
135
- This is raised when the metadata contains values (e.g. name and version)
136
- that do not match the information previously obtained from sdist filename
137
- or user-supplied ``#egg=`` value.
138
- """
139
- def __init__ (self , ireq , field , f_val , m_val ):
140
- # type: (InstallRequirement, str, str, str) -> None
141
- self .ireq = ireq
142
- self .field = field
143
- self .f_val = f_val
144
- self .m_val = m_val
145
-
146
- def __str__ (self ):
147
- # type: () -> str
148
- template = (
149
- "Requested {} has inconsistent {}: "
150
- "filename has {!r}, but metadata has {!r}"
151
- )
152
- return template .format (self .ireq , self .field , self .f_val , self .m_val )
153
-
154
-
155
48
class InstallationSubprocessError (InstallationError ):
156
49
"""A subprocess call failed during installation."""
157
50
def __init__ (self , returncode , description ):
@@ -207,43 +100,16 @@ class HashError(InstallationError):
207
100
about unpinned packages when he has deeper issues, like VCS
208
101
dependencies, to deal with. Also keeps error reports in a
209
102
deterministic order.
210
- :cvar head: A section heading for display above potentially many
103
+ :cvar head: A section heading for display in potentially many
211
104
exceptions of this kind
212
- :ivar req: The InstallRequirement that triggered this error. This is
213
- pasted on after the exception is instantiated, because it's not
214
- typically available earlier.
215
105
216
106
"""
217
- req = None # type: Optional[InstallRequirement]
218
107
head = ''
219
108
order = - 1 # type: int
220
109
221
- def body (self ):
222
- # type: () -> str
223
- """Return a summary of me for display under the heading.
224
-
225
- This default implementation simply prints a description of the
226
- triggering requirement.
227
-
228
- :param req: The InstallRequirement that provoked this error, with
229
- its link already populated by the resolver's _populate_link().
230
-
231
- """
232
- return f' { self ._requirement_name ()} '
233
-
234
110
def __str__ (self ):
235
111
# type: () -> str
236
- return f'{ self .head } \n { self .body ()} '
237
-
238
- def _requirement_name (self ):
239
- # type: () -> str
240
- """Return a description of the requirement that triggered me.
241
-
242
- This default implementation returns long description of the req, with
243
- line numbers
244
-
245
- """
246
- return str (self .req ) if self .req else 'unknown package'
112
+ return f'{ self .head } '
247
113
248
114
249
115
class VcsHashUnsupported (HashError ):
@@ -254,144 +120,3 @@ class VcsHashUnsupported(HashError):
254
120
head = ("Can't verify hashes for these requirements because we don't "
255
121
"have a way to hash version control repositories:" )
256
122
257
-
258
- class DirectoryUrlHashUnsupported (HashError ):
259
- """A hash was provided for a version-control-system-based requirement, but
260
- we don't have a method for hashing those."""
261
-
262
- order = 1
263
- head = ("Can't verify hashes for these file:// requirements because they "
264
- "point to directories:" )
265
-
266
-
267
- class HashMissing (HashError ):
268
- """A hash was needed for a requirement but is absent."""
269
-
270
- order = 2
271
- head = ('Hashes are required in --require-hashes mode, but they are '
272
- 'missing from some requirements. Here is a list of those '
273
- 'requirements along with the hashes their downloaded archives '
274
- 'actually had. Add lines like these to your requirements files to '
275
- 'prevent tampering. (If you did not enable --require-hashes '
276
- 'manually, note that it turns on automatically when any package '
277
- 'has a hash.)' )
278
-
279
- def __init__ (self , gotten_hash ):
280
- # type: (str) -> None
281
- """
282
- :param gotten_hash: The hash of the (possibly malicious) archive we
283
- just downloaded
284
- """
285
- self .gotten_hash = gotten_hash
286
-
287
- def body (self ):
288
- # type: () -> str
289
- # Dodge circular import.
290
- from fetchcode .vcs .pip ._internal .utils .hashes import FAVORITE_HASH
291
-
292
- package = None
293
- if self .req :
294
- # In the case of URL-based requirements, display the original URL
295
- # seen in the requirements file rather than the package name,
296
- # so the output can be directly copied into the requirements file.
297
- package = (self .req .original_link if self .req .original_link
298
- # In case someone feeds something downright stupid
299
- # to InstallRequirement's constructor.
300
- else getattr (self .req , 'req' , None ))
301
- return ' {} --hash={}:{}' .format (package or 'unknown package' ,
302
- FAVORITE_HASH ,
303
- self .gotten_hash )
304
-
305
-
306
- class HashUnpinned (HashError ):
307
- """A requirement had a hash specified but was not pinned to a specific
308
- version."""
309
-
310
- order = 3
311
- head = ('In --require-hashes mode, all requirements must have their '
312
- 'versions pinned with ==. These do not:' )
313
-
314
-
315
- class HashMismatch (HashError ):
316
- """
317
- Distribution file hash values don't match.
318
-
319
- :ivar package_name: The name of the package that triggered the hash
320
- mismatch. Feel free to write to this after the exception is raise to
321
- improve its error message.
322
-
323
- """
324
- order = 4
325
- head = ('THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS '
326
- 'FILE. If you have updated the package versions, please update '
327
- 'the hashes. Otherwise, examine the package contents carefully; '
328
- 'someone may have tampered with them.' )
329
-
330
- def __init__ (self , allowed , gots ):
331
- # type: (Dict[str, List[str]], Dict[str, _Hash]) -> None
332
- """
333
- :param allowed: A dict of algorithm names pointing to lists of allowed
334
- hex digests
335
- :param gots: A dict of algorithm names pointing to hashes we
336
- actually got from the files under suspicion
337
- """
338
- self .allowed = allowed
339
- self .gots = gots
340
-
341
- def body (self ):
342
- # type: () -> str
343
- return ' {}:\n {}' .format (self ._requirement_name (),
344
- self ._hash_comparison ())
345
-
346
- def _hash_comparison (self ):
347
- # type: () -> str
348
- """
349
- Return a comparison of actual and expected hash values.
350
-
351
- Example::
352
-
353
- Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
354
- or 123451234512345123451234512345123451234512345
355
- Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
356
-
357
- """
358
- def hash_then_or (hash_name ):
359
- # type: (str) -> chain[str]
360
- # For now, all the decent hashes have 6-char names, so we can get
361
- # away with hard-coding space literals.
362
- return chain ([hash_name ], repeat (' or' ))
363
-
364
- lines = [] # type: List[str]
365
- for hash_name , expecteds in self .allowed .items ():
366
- prefix = hash_then_or (hash_name )
367
- lines .extend ((' Expected {} {}' .format (next (prefix ), e ))
368
- for e in expecteds )
369
- lines .append (' Got {}\n ' .format (
370
- self .gots [hash_name ].hexdigest ()))
371
- return '\n ' .join (lines )
372
-
373
-
374
- class UnsupportedPythonVersion (InstallationError ):
375
- """Unsupported python version according to Requires-Python package
376
- metadata."""
377
-
378
-
379
- class ConfigurationFileCouldNotBeLoaded (ConfigurationError ):
380
- """When there are errors while loading a configuration file
381
- """
382
-
383
- def __init__ (self , reason = "could not be loaded" , fname = None , error = None ):
384
- # type: (str, Optional[str], Optional[configparser.Error]) -> None
385
- super ().__init__ (error )
386
- self .reason = reason
387
- self .fname = fname
388
- self .error = error
389
-
390
- def __str__ (self ):
391
- # type: () -> str
392
- if self .fname is not None :
393
- message_part = f" in { self .fname } ."
394
- else :
395
- assert self .error is not None
396
- message_part = f".\n { self .error } \n "
397
- return f"Configuration file { self .reason } { message_part } "
0 commit comments