Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/invoke/exceptions.py: 42%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

117 statements  

1""" 

2Custom exception classes. 

3 

4These vary in use case from "we needed a specific data structure layout in 

5exceptions used for message-passing" to simply "we needed to express an error 

6condition in a way easily told apart from other, truly unexpected errors". 

7""" 

8 

9from pprint import pformat 

10from traceback import format_exception 

11from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple 

12 

13if TYPE_CHECKING: 

14 from .parser import ParserContext 

15 from .runners import Result 

16 from .util import ExceptionWrapper 

17 

18 

19class CollectionNotFound(Exception): 

20 def __init__(self, name: str, start: str) -> None: 

21 self.name = name 

22 self.start = start 

23 

24 

25class Failure(Exception): 

26 """ 

27 Exception subclass representing failure of a command execution. 

28 

29 "Failure" may mean the command executed and the shell indicated an unusual 

30 result (usually, a non-zero exit code), or it may mean something else, like 

31 a ``sudo`` command which was aborted when the supplied password failed 

32 authentication. 

33 

34 Two attributes allow introspection to determine the nature of the problem: 

35 

36 * ``result``: a `.Result` instance with info about the command being 

37 executed and, if it ran to completion, how it exited. 

38 * ``reason``: a wrapped exception instance if applicable (e.g. a 

39 `.StreamWatcher` raised `WatcherError`) or ``None`` otherwise, in which 

40 case, it's probably a `Failure` subclass indicating its own specific 

41 nature, such as `UnexpectedExit` or `CommandTimedOut`. 

42 

43 This class is only rarely raised by itself; most of the time `.Runner.run` 

44 (or a wrapper of same, such as `.Context.sudo`) will raise a specific 

45 subclass like `UnexpectedExit` or `AuthFailure`. 

46 

47 .. versionadded:: 1.0 

48 """ 

49 

50 def __init__( 

51 self, result: "Result", reason: Optional["WatcherError"] = None 

52 ) -> None: 

53 self.result = result 

54 self.reason = reason 

55 

56 def streams_for_display(self) -> Tuple[str, str]: 

57 """ 

58 Return stdout/err streams as necessary for error display. 

59 

60 Subject to the following rules: 

61 

62 - If a given stream was *not* hidden during execution, a placeholder is 

63 used instead, to avoid printing it twice. 

64 - Only the last 10 lines of stream text is included. 

65 - PTY-driven execution will lack stderr, and a specific message to this 

66 effect is returned instead of a stderr dump. 

67 

68 :returns: Two-tuple of stdout, stderr strings. 

69 

70 .. versionadded:: 1.3 

71 """ 

72 already_printed = " already printed" 

73 if "stdout" not in self.result.hide: 

74 stdout = already_printed 

75 else: 

76 stdout = self.result.tail("stdout") 

77 if self.result.pty: 

78 stderr = " n/a (PTYs have no stderr)" 

79 else: 

80 if "stderr" not in self.result.hide: 

81 stderr = already_printed 

82 else: 

83 stderr = self.result.tail("stderr") 

84 return stdout, stderr 

85 

86 def __repr__(self) -> str: 

87 return self._repr() 

88 

89 def _repr(self, **kwargs: Any) -> str: 

90 """ 

91 Return ``__repr__``-like value from inner result + any kwargs. 

92 """ 

93 # TODO: expand? 

94 # TODO: truncate command? 

95 template = "<{}: cmd={!r}{}>" 

96 rest = "" 

97 if kwargs: 

98 rest = " " + " ".join( 

99 "{}={}".format(key, value) for key, value in kwargs.items() 

100 ) 

101 return template.format( 

102 self.__class__.__name__, self.result.command, rest 

103 ) 

104 

105 

106class UnexpectedExit(Failure): 

107 """ 

108 A shell command ran to completion but exited with an unexpected exit code. 

109 

110 Its string representation displays the following: 

111 

112 - Command executed; 

113 - Exit code; 

114 - The last 10 lines of stdout, if it was hidden; 

115 - The last 10 lines of stderr, if it was hidden and non-empty (e.g. 

116 pty=False; when pty=True, stderr never happens.) 

117 

118 .. versionadded:: 1.0 

119 """ 

120 

121 def __str__(self) -> str: 

122 stdout, stderr = self.streams_for_display() 

123 command = self.result.command 

124 exited = self.result.exited 

125 template = """Encountered a bad command exit code! 

126 

127Command: {!r} 

128 

129Exit code: {} 

130 

131Stdout:{} 

132 

133Stderr:{} 

134 

135""" 

136 return template.format(command, exited, stdout, stderr) 

137 

138 def _repr(self, **kwargs: Any) -> str: 

139 kwargs.setdefault("exited", self.result.exited) 

140 return super()._repr(**kwargs) 

141 

142 

143class CommandTimedOut(Failure): 

144 """ 

145 Raised when a subprocess did not exit within a desired timeframe. 

146 """ 

147 

148 def __init__(self, result: "Result", timeout: int) -> None: 

149 super().__init__(result) 

150 self.timeout = timeout 

151 

152 def __repr__(self) -> str: 

153 return self._repr(timeout=self.timeout) 

154 

155 def __str__(self) -> str: 

156 stdout, stderr = self.streams_for_display() 

157 command = self.result.command 

158 template = """Command did not complete within {} seconds! 

159 

160Command: {!r} 

161 

162Stdout:{} 

163 

164Stderr:{} 

165 

166""" 

167 return template.format(self.timeout, command, stdout, stderr) 

168 

169 

170class AuthFailure(Failure): 

171 """ 

172 An authentication failure, e.g. due to an incorrect ``sudo`` password. 

173 

174 .. note:: 

175 `.Result` objects attached to these exceptions typically lack exit code 

176 information, since the command was never fully executed - the exception 

177 was raised instead. 

178 

179 .. versionadded:: 1.0 

180 """ 

181 

182 def __init__(self, result: "Result", prompt: str) -> None: 

183 self.result = result 

184 self.prompt = prompt 

185 

186 def __str__(self) -> str: 

187 err = "The password submitted to prompt {!r} was rejected." 

188 return err.format(self.prompt) 

189 

190 

191class ParseError(Exception): 

192 """ 

193 An error arising from the parsing of command-line flags/arguments. 

194 

195 Ambiguous input, invalid task names, invalid flags, etc. 

196 

197 .. versionadded:: 1.0 

198 """ 

199 

200 def __init__( 

201 self, msg: str, context: Optional["ParserContext"] = None 

202 ) -> None: 

203 super().__init__(msg) 

204 self.context = context 

205 

206 

207class Exit(Exception): 

208 """ 

209 Simple custom stand-in for SystemExit. 

210 

211 Replaces scattered sys.exit calls, improves testability, allows one to 

212 catch an exit request without intercepting real SystemExits (typically an 

213 unfriendly thing to do, as most users calling `sys.exit` rather expect it 

214 to truly exit.) 

215 

216 Defaults to a non-printing, exit-0 friendly termination behavior if the 

217 exception is uncaught. 

218 

219 If ``code`` (an int) given, that code is used to exit. 

220 

221 If ``message`` (a string) given, it is printed to standard error, and the 

222 program exits with code ``1`` by default (unless overridden by also giving 

223 ``code`` explicitly.) 

224 

225 .. versionadded:: 1.0 

226 """ 

227 

228 def __init__( 

229 self, message: Optional[str] = None, code: Optional[int] = None 

230 ) -> None: 

231 self.message = message 

232 self._code = code 

233 

234 @property 

235 def code(self) -> int: 

236 if self._code is not None: 

237 return self._code 

238 return 1 if self.message else 0 

239 

240 

241class PlatformError(Exception): 

242 """ 

243 Raised when an illegal operation occurs for the current platform. 

244 

245 E.g. Windows users trying to use functionality requiring the ``pty`` 

246 module. 

247 

248 Typically used to present a clearer error message to the user. 

249 

250 .. versionadded:: 1.0 

251 """ 

252 

253 pass 

254 

255 

256class AmbiguousEnvVar(Exception): 

257 """ 

258 Raised when loading env var config keys has an ambiguous target. 

259 

260 .. versionadded:: 1.0 

261 """ 

262 

263 pass 

264 

265 

266class UncastableEnvVar(Exception): 

267 """ 

268 Raised on attempted env var loads whose default values are too rich. 

269 

270 E.g. trying to stuff ``MY_VAR="foo"`` into ``{'my_var': ['uh', 'oh']}`` 

271 doesn't make any sense until/if we implement some sort of transform option. 

272 

273 .. versionadded:: 1.0 

274 """ 

275 

276 pass 

277 

278 

279class UnknownFileType(Exception): 

280 """ 

281 A config file of an unknown type was specified and cannot be loaded. 

282 

283 .. versionadded:: 1.0 

284 """ 

285 

286 pass 

287 

288 

289class UnpicklableConfigMember(Exception): 

290 """ 

291 A config file contained module objects, which can't be pickled/copied. 

292 

293 We raise this more easily catchable exception instead of letting the 

294 (unclearly phrased) TypeError bubble out of the pickle module. (However, to 

295 avoid our own fragile catching of that error, we head it off by explicitly 

296 testing for module members.) 

297 

298 .. versionadded:: 1.0.2 

299 """ 

300 

301 pass 

302 

303 

304def _printable_kwargs(kwargs: Any) -> Dict[str, Any]: 

305 """ 

306 Return print-friendly version of a thread-related ``kwargs`` dict. 

307 

308 Extra care is taken with ``args`` members which are very long iterables - 

309 those need truncating to be useful. 

310 """ 

311 printable = {} 

312 for key, value in kwargs.items(): 

313 item = value 

314 if key == "args": 

315 item = [] 

316 for arg in value: 

317 new_arg = arg 

318 if hasattr(arg, "__len__") and len(arg) > 10: 

319 msg = "<... remainder truncated during error display ...>" 

320 new_arg = arg[:10] + [msg] 

321 item.append(new_arg) 

322 printable[key] = item 

323 return printable 

324 

325 

326class ThreadException(Exception): 

327 """ 

328 One or more exceptions were raised within background threads. 

329 

330 The real underlying exceptions are stored in the `exceptions` attribute; 

331 see its documentation for data structure details. 

332 

333 .. note:: 

334 Threads which did not encounter an exception, do not contribute to this 

335 exception object and thus are not present inside `exceptions`. 

336 

337 .. versionadded:: 1.0 

338 """ 

339 

340 #: A tuple of `ExceptionWrappers <invoke.util.ExceptionWrapper>` containing 

341 #: the initial thread constructor kwargs (because `threading.Thread` 

342 #: subclasses should always be called with kwargs) and the caught exception 

343 #: for that thread as seen by `sys.exc_info` (so: type, value, traceback). 

344 #: 

345 #: .. note:: 

346 #: The ordering of this attribute is not well-defined. 

347 #: 

348 #: .. note:: 

349 #: Thread kwargs which appear to be very long (e.g. IO 

350 #: buffers) will be truncated when printed, to avoid huge 

351 #: unreadable error display. 

352 exceptions: Tuple["ExceptionWrapper", ...] = tuple() 

353 

354 def __init__(self, exceptions: List["ExceptionWrapper"]) -> None: 

355 self.exceptions = tuple(exceptions) 

356 

357 def __str__(self) -> str: 

358 details = [] 

359 for x in self.exceptions: 

360 # Build useful display 

361 detail = "Thread args: {}\n\n{}" 

362 details.append( 

363 detail.format( 

364 pformat(_printable_kwargs(x.kwargs)), 

365 "\n".join(format_exception(x.type, x.value, x.traceback)), 

366 ) 

367 ) 

368 args = ( 

369 len(self.exceptions), 

370 ", ".join(x.type.__name__ for x in self.exceptions), 

371 "\n\n".join(details), 

372 ) 

373 return """ 

374Saw {} exceptions within threads ({}): 

375 

376 

377{} 

378""".format( 

379 *args 

380 ) 

381 

382 

383class WatcherError(Exception): 

384 """ 

385 Generic parent exception class for `.StreamWatcher`-related errors. 

386 

387 Typically, one of these exceptions indicates a `.StreamWatcher` noticed 

388 something anomalous in an output stream, such as an authentication response 

389 failure. 

390 

391 `.Runner` catches these and attaches them to `.Failure` exceptions so they 

392 can be referenced by intermediate code and/or act as extra info for end 

393 users. 

394 

395 .. versionadded:: 1.0 

396 """ 

397 

398 pass 

399 

400 

401class ResponseNotAccepted(WatcherError): 

402 """ 

403 A responder/watcher class noticed a 'bad' response to its submission. 

404 

405 Mostly used by `.FailingResponder` and subclasses, e.g. "oh dear I 

406 autosubmitted a sudo password and it was incorrect." 

407 

408 .. versionadded:: 1.0 

409 """ 

410 

411 pass 

412 

413 

414class SubprocessPipeError(Exception): 

415 """ 

416 Some problem was encountered handling subprocess pipes (stdout/err/in). 

417 

418 Typically only for corner cases; most of the time, errors in this area are 

419 raised by the interpreter or the operating system, and end up wrapped in a 

420 `.ThreadException`. 

421 

422 .. versionadded:: 1.3 

423 """ 

424 

425 pass