1"""Exceptions used throughout package""" 2from __future__ import absolute_import 3 4from itertools import chain, groupby, repeat 5 6from pip9._vendor.six import iteritems 7 8 9class PipError(Exception): 10 """Base pip exception""" 11 12 13class InstallationError(PipError): 14 """General exception during installation""" 15 16 17class UninstallationError(PipError): 18 """General exception during uninstallation""" 19 20 21class DistributionNotFound(InstallationError): 22 """Raised when a distribution cannot be found to satisfy a requirement""" 23 24 25class RequirementsFileParseError(InstallationError): 26 """Raised when a general error occurs parsing a requirements file line.""" 27 28 29class BestVersionAlreadyInstalled(PipError): 30 """Raised when the most up-to-date version of a package is already 31 installed.""" 32 33 34class BadCommand(PipError): 35 """Raised when virtualenv or a command is not found""" 36 37 38class CommandError(PipError): 39 """Raised when there is an error in command-line arguments""" 40 41 42class PreviousBuildDirError(PipError): 43 """Raised when there's a previous conflicting build directory""" 44 45 46class InvalidWheelFilename(InstallationError): 47 """Invalid wheel filename.""" 48 49 50class UnsupportedWheel(InstallationError): 51 """Unsupported wheel.""" 52 53 54class HashErrors(InstallationError): 55 """Multiple HashError instances rolled into one for reporting""" 56 57 def __init__(self): 58 self.errors = [] 59 60 def append(self, error): 61 self.errors.append(error) 62 63 def __str__(self): 64 lines = [] 65 self.errors.sort(key=lambda e: e.order) 66 for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__): 67 lines.append(cls.head) 68 lines.extend(e.body() for e in errors_of_cls) 69 if lines: 70 return '\n'.join(lines) 71 72 def __nonzero__(self): 73 return bool(self.errors) 74 75 def __bool__(self): 76 return self.__nonzero__() 77 78 79class HashError(InstallationError): 80 """ 81 A failure to verify a package against known-good hashes 82 83 :cvar order: An int sorting hash exception classes by difficulty of 84 recovery (lower being harder), so the user doesn't bother fretting 85 about unpinned packages when he has deeper issues, like VCS 86 dependencies, to deal with. Also keeps error reports in a 87 deterministic order. 88 :cvar head: A section heading for display above potentially many 89 exceptions of this kind 90 :ivar req: The InstallRequirement that triggered this error. This is 91 pasted on after the exception is instantiated, because it's not 92 typically available earlier. 93 94 """ 95 req = None 96 head = '' 97 98 def body(self): 99 """Return a summary of me for display under the heading. 100 101 This default implementation simply prints a description of the 102 triggering requirement. 103 104 :param req: The InstallRequirement that provoked this error, with 105 populate_link() having already been called 106 107 """ 108 return ' %s' % self._requirement_name() 109 110 def __str__(self): 111 return '%s\n%s' % (self.head, self.body()) 112 113 def _requirement_name(self): 114 """Return a description of the requirement that triggered me. 115 116 This default implementation returns long description of the req, with 117 line numbers 118 119 """ 120 return str(self.req) if self.req else 'unknown package' 121 122 123class VcsHashUnsupported(HashError): 124 """A hash was provided for a version-control-system-based requirement, but 125 we don't have a method for hashing those.""" 126 127 order = 0 128 head = ("Can't verify hashes for these requirements because we don't " 129 "have a way to hash version control repositories:") 130 131 132class DirectoryUrlHashUnsupported(HashError): 133 """A hash was provided for a version-control-system-based requirement, but 134 we don't have a method for hashing those.""" 135 136 order = 1 137 head = ("Can't verify hashes for these file:// requirements because they " 138 "point to directories:") 139 140 141class HashMissing(HashError): 142 """A hash was needed for a requirement but is absent.""" 143 144 order = 2 145 head = ('Hashes are required in --require-hashes mode, but they are ' 146 'missing from some requirements. Here is a list of those ' 147 'requirements along with the hashes their downloaded archives ' 148 'actually had. Add lines like these to your requirements files to ' 149 'prevent tampering. (If you did not enable --require-hashes ' 150 'manually, note that it turns on automatically when any package ' 151 'has a hash.)') 152 153 def __init__(self, gotten_hash): 154 """ 155 :param gotten_hash: The hash of the (possibly malicious) archive we 156 just downloaded 157 """ 158 self.gotten_hash = gotten_hash 159 160 def body(self): 161 from pip9.utils.hashes import FAVORITE_HASH # Dodge circular import. 162 163 package = None 164 if self.req: 165 # In the case of URL-based requirements, display the original URL 166 # seen in the requirements file rather than the package name, 167 # so the output can be directly copied into the requirements file. 168 package = (self.req.original_link if self.req.original_link 169 # In case someone feeds something downright stupid 170 # to InstallRequirement's constructor. 171 else getattr(self.req, 'req', None)) 172 return ' %s --hash=%s:%s' % (package or 'unknown package', 173 FAVORITE_HASH, 174 self.gotten_hash) 175 176 177class HashUnpinned(HashError): 178 """A requirement had a hash specified but was not pinned to a specific 179 version.""" 180 181 order = 3 182 head = ('In --require-hashes mode, all requirements must have their ' 183 'versions pinned with ==. These do not:') 184 185 186class HashMismatch(HashError): 187 """ 188 Distribution file hash values don't match. 189 190 :ivar package_name: The name of the package that triggered the hash 191 mismatch. Feel free to write to this after the exception is raise to 192 improve its error message. 193 194 """ 195 order = 4 196 head = ('THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS ' 197 'FILE. If you have updated the package versions, please update ' 198 'the hashes. Otherwise, examine the package contents carefully; ' 199 'someone may have tampered with them.') 200 201 def __init__(self, allowed, gots): 202 """ 203 :param allowed: A dict of algorithm names pointing to lists of allowed 204 hex digests 205 :param gots: A dict of algorithm names pointing to hashes we 206 actually got from the files under suspicion 207 """ 208 self.allowed = allowed 209 self.gots = gots 210 211 def body(self): 212 return ' %s:\n%s' % (self._requirement_name(), 213 self._hash_comparison()) 214 215 def _hash_comparison(self): 216 """ 217 Return a comparison of actual and expected hash values. 218 219 Example:: 220 221 Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde 222 or 123451234512345123451234512345123451234512345 223 Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef 224 225 """ 226 def hash_then_or(hash_name): 227 # For now, all the decent hashes have 6-char names, so we can get 228 # away with hard-coding space literals. 229 return chain([hash_name], repeat(' or')) 230 231 lines = [] 232 for hash_name, expecteds in iteritems(self.allowed): 233 prefix = hash_then_or(hash_name) 234 lines.extend((' Expected %s %s' % (next(prefix), e)) 235 for e in expecteds) 236 lines.append(' Got %s\n' % 237 self.gots[hash_name].hexdigest()) 238 prefix = ' or' 239 return '\n'.join(lines) 240 241 242class UnsupportedPythonVersion(InstallationError): 243 """Unsupported python version according to Requires-Python package 244 metadata.""" 245