11"""Exceptions used throughout package"""
22
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
65
7- from fetchcode .vcs .pip ._vendor .pkg_resources import Distribution
86from fetchcode .vcs .pip ._vendor .requests .models import Request , Response
97
108if TYPE_CHECKING :
119 from hashlib import _Hash
1210
13- from fetchcode .vcs .pip ._internal .req .req_install import InstallRequirement
14-
15-
1611class PipError (Exception ):
1712 """Base pip exception"""
1813
19-
20- class ConfigurationError (PipError ):
21- """General exception in configuration"""
22-
23-
2414class InstallationError (PipError ):
2515 """General exception during installation"""
2616
2717
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-
9018class BadCommand (PipError ):
9119 """Raised when virtualenv or a command is not found"""
9220
@@ -95,10 +23,6 @@ class CommandError(PipError):
9523 """Raised when there is an error in command-line arguments"""
9624
9725
98- class PreviousBuildDirError (PipError ):
99- """Raised when there's a previous conflicting build directory"""
100-
101-
10226class NetworkConnectionError (PipError ):
10327 """HTTP connection error"""
10428
@@ -121,37 +45,6 @@ def __str__(self):
12145 return str (self .error_msg )
12246
12347
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-
15548class InstallationSubprocessError (InstallationError ):
15649 """A subprocess call failed during installation."""
15750 def __init__ (self , returncode , description ):
@@ -207,43 +100,16 @@ class HashError(InstallationError):
207100 about unpinned packages when he has deeper issues, like VCS
208101 dependencies, to deal with. Also keeps error reports in a
209102 deterministic order.
210- :cvar head: A section heading for display above potentially many
103+ :cvar head: A section heading for display in potentially many
211104 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.
215105
216106 """
217- req = None # type: Optional[InstallRequirement]
218107 head = ''
219108 order = - 1 # type: int
220109
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-
234110 def __str__ (self ):
235111 # 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 } '
247113
248114
249115class VcsHashUnsupported (HashError ):
@@ -254,144 +120,3 @@ class VcsHashUnsupported(HashError):
254120 head = ("Can't verify hashes for these requirements because we don't "
255121 "have a way to hash version control repositories:" )
256122
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