##// END OF EJS Templates
Annotate and check typing in ultratb
Matthias Bussonnier -
Show More
@@ -1,1144 +1,1194 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Verbose and colourful traceback formatting.
4 4
5 5 **ColorTB**
6 6
7 7 I've always found it a bit hard to visually parse tracebacks in Python. The
8 8 ColorTB class is a solution to that problem. It colors the different parts of a
9 9 traceback in a manner similar to what you would expect from a syntax-highlighting
10 10 text editor.
11 11
12 12 Installation instructions for ColorTB::
13 13
14 14 import sys,ultratb
15 15 sys.excepthook = ultratb.ColorTB()
16 16
17 17 **VerboseTB**
18 18
19 19 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
20 20 of useful info when a traceback occurs. Ping originally had it spit out HTML
21 21 and intended it for CGI programmers, but why should they have all the fun? I
22 22 altered it to spit out colored text to the terminal. It's a bit overwhelming,
23 23 but kind of neat, and maybe useful for long-running programs that you believe
24 24 are bug-free. If a crash *does* occur in that type of program you want details.
25 25 Give it a shot--you'll love it or you'll hate it.
26 26
27 27 .. note::
28 28
29 29 The Verbose mode prints the variables currently visible where the exception
30 30 happened (shortening their strings if too long). This can potentially be
31 31 very slow, if you happen to have a huge data structure whose string
32 32 representation is complex to compute. Your computer may appear to freeze for
33 33 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
34 34 with Ctrl-C (maybe hitting it more than once).
35 35
36 36 If you encounter this kind of situation often, you may want to use the
37 37 Verbose_novars mode instead of the regular Verbose, which avoids formatting
38 38 variables (but otherwise includes the information and context given by
39 39 Verbose).
40 40
41 41 .. note::
42 42
43 43 The verbose mode print all variables in the stack, which means it can
44 44 potentially leak sensitive information like access keys, or unencrypted
45 45 password.
46 46
47 47 Installation instructions for VerboseTB::
48 48
49 49 import sys,ultratb
50 50 sys.excepthook = ultratb.VerboseTB()
51 51
52 52 Note: Much of the code in this module was lifted verbatim from the standard
53 53 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
54 54
55 55 Color schemes
56 56 -------------
57 57
58 58 The colors are defined in the class TBTools through the use of the
59 59 ColorSchemeTable class. Currently the following exist:
60 60
61 61 - NoColor: allows all of this module to be used in any terminal (the color
62 62 escapes are just dummy blank strings).
63 63
64 64 - Linux: is meant to look good in a terminal like the Linux console (black
65 65 or very dark background).
66 66
67 67 - LightBG: similar to Linux but swaps dark/light colors to be more readable
68 68 in light background terminals.
69 69
70 70 - Neutral: a neutral color scheme that should be readable on both light and
71 71 dark background
72 72
73 73 You can implement other color schemes easily, the syntax is fairly
74 74 self-explanatory. Please send back new schemes you develop to the author for
75 75 possible inclusion in future releases.
76 76
77 77 Inheritance diagram:
78 78
79 79 .. inheritance-diagram:: IPython.core.ultratb
80 80 :parts: 3
81 81 """
82 82
83 83 #*****************************************************************************
84 84 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
85 85 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
86 86 #
87 87 # Distributed under the terms of the BSD License. The full license is in
88 88 # the file COPYING, distributed as part of this software.
89 89 #*****************************************************************************
90 90
91 91
92 92 import inspect
93 93 import linecache
94 94 import pydoc
95 95 import sys
96 96 import time
97 97 import traceback
98 from types import TracebackType
99 from typing import Tuple, List, Any, Optional
98 100
99 101 import stack_data
100 102 from pygments.formatters.terminal256 import Terminal256Formatter
101 103 from pygments.styles import get_style_by_name
102 104
103 105 # IPython's own modules
104 106 from IPython import get_ipython
105 107 from IPython.core import debugger
106 108 from IPython.core.display_trap import DisplayTrap
107 109 from IPython.core.excolors import exception_colors
108 110 from IPython.utils import path as util_path
109 111 from IPython.utils import py3compat
110 112 from IPython.utils.terminal import get_terminal_size
111 113
112 114 import IPython.utils.colorable as colorable
113 115
114 116 # Globals
115 117 # amount of space to put line numbers before verbose tracebacks
116 118 INDENT_SIZE = 8
117 119
118 120 # Default color scheme. This is used, for example, by the traceback
119 121 # formatter. When running in an actual IPython instance, the user's rc.colors
120 122 # value is used, but having a module global makes this functionality available
121 123 # to users of ultratb who are NOT running inside ipython.
122 124 DEFAULT_SCHEME = 'NoColor'
123 125
124 126 # ---------------------------------------------------------------------------
125 127 # Code begins
126 128
127 129 # Helper function -- largely belongs to VerboseTB, but we need the same
128 130 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
129 131 # can be recognized properly by ipython.el's py-traceback-line-re
130 132 # (SyntaxErrors have to be treated specially because they have no traceback)
131 133
132 134
133 def _format_traceback_lines(lines, Colors, has_colors, lvals):
135 def _format_traceback_lines(lines, Colors, has_colors: bool, lvals):
134 136 """
135 137 Format tracebacks lines with pointing arrow, leading numbers...
136 138
137 139 Parameters
138 140 ----------
139 141 lines : list[Line]
140 142 Colors
141 143 ColorScheme used.
142 144 lvals : str
143 145 Values of local variables, already colored, to inject just after the error line.
144 146 """
145 147 numbers_width = INDENT_SIZE - 1
146 148 res = []
147 149
148 150 for stack_line in lines:
149 151 if stack_line is stack_data.LINE_GAP:
150 152 res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal))
151 153 continue
152 154
153 155 line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
154 156 lineno = stack_line.lineno
155 157 if stack_line.is_current:
156 158 # This is the line with the error
157 159 pad = numbers_width - len(str(lineno))
158 160 num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
159 161 start_color = Colors.linenoEm
160 162 else:
161 163 num = '%*s' % (numbers_width, lineno)
162 164 start_color = Colors.lineno
163 165
164 166 line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
165 167
166 168 res.append(line)
167 169 if lvals and stack_line.is_current:
168 170 res.append(lvals + '\n')
169 171 return res
170 172
171 173
172 174 def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
173 175 """
174 176 Format filename lines with `In [n]` if it's the nth code cell or `File *.py` if it's a module.
175 177
176 178 Parameters
177 179 ----------
178 180 file : str
179 181 ColorFilename
180 182 ColorScheme's filename coloring to be used.
181 183 ColorNormal
182 184 ColorScheme's normal coloring to be used.
183 185 """
184 186 ipinst = get_ipython()
185 187
186 188 if ipinst is not None and file in ipinst.compile._filename_map:
187 189 file = "[%s]" % ipinst.compile._filename_map[file]
188 190 tpl_link = f"Input {ColorFilename}In {{file}}{ColorNormal}"
189 191 else:
190 192 file = util_path.compress_user(
191 193 py3compat.cast_unicode(file, util_path.fs_encoding)
192 194 )
193 195 if lineno is None:
194 196 tpl_link = f"File {ColorFilename}{{file}}{ColorNormal}"
195 197 else:
196 198 tpl_link = f"File {ColorFilename}{{file}}:{{lineno}}{ColorNormal}"
197 199
198 200 return tpl_link.format(file=file, lineno=lineno)
199 201
200 202 #---------------------------------------------------------------------------
201 203 # Module classes
202 204 class TBTools(colorable.Colorable):
203 205 """Basic tools used by all traceback printer classes."""
204 206
205 207 # Number of frames to skip when reporting tracebacks
206 208 tb_offset = 0
207 209
208 210 def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None, parent=None, config=None):
209 211 # Whether to call the interactive pdb debugger after printing
210 212 # tracebacks or not
211 213 super(TBTools, self).__init__(parent=parent, config=config)
212 214 self.call_pdb = call_pdb
213 215
214 216 # Output stream to write to. Note that we store the original value in
215 217 # a private attribute and then make the public ostream a property, so
216 218 # that we can delay accessing sys.stdout until runtime. The way
217 219 # things are written now, the sys.stdout object is dynamically managed
218 220 # so a reference to it should NEVER be stored statically. This
219 221 # property approach confines this detail to a single location, and all
220 222 # subclasses can simply access self.ostream for writing.
221 223 self._ostream = ostream
222 224
223 225 # Create color table
224 226 self.color_scheme_table = exception_colors()
225 227
226 228 self.set_colors(color_scheme)
227 229 self.old_scheme = color_scheme # save initial value for toggles
228 230
229 231 if call_pdb:
230 232 self.pdb = debugger.Pdb()
231 233 else:
232 234 self.pdb = None
233 235
234 236 def _get_ostream(self):
235 237 """Output stream that exceptions are written to.
236 238
237 239 Valid values are:
238 240
239 241 - None: the default, which means that IPython will dynamically resolve
240 242 to sys.stdout. This ensures compatibility with most tools, including
241 243 Windows (where plain stdout doesn't recognize ANSI escapes).
242 244
243 245 - Any object with 'write' and 'flush' attributes.
244 246 """
245 247 return sys.stdout if self._ostream is None else self._ostream
246 248
247 249 def _set_ostream(self, val):
248 250 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
249 251 self._ostream = val
250 252
251 253 ostream = property(_get_ostream, _set_ostream)
252 254
253 def get_parts_of_chained_exception(self, evalue):
254 def get_chained_exception(exception_value):
255 cause = getattr(exception_value, '__cause__', None)
256 if cause:
257 return cause
258 if getattr(exception_value, '__suppress_context__', False):
259 return None
260 return getattr(exception_value, '__context__', None)
255 @staticmethod
256 def _get_chained_exception(exception_value):
257 cause = getattr(exception_value, "__cause__", None)
258 if cause:
259 return cause
260 if getattr(exception_value, "__suppress_context__", False):
261 return None
262 return getattr(exception_value, "__context__", None)
263
264 def get_parts_of_chained_exception(
265 self, evalue
266 ) -> Optional[Tuple[type, BaseException, TracebackType]]:
261 267
262 chained_evalue = get_chained_exception(evalue)
268 chained_evalue = self._get_chained_exception(evalue)
263 269
264 270 if chained_evalue:
265 271 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
272 return None
266 273
267 def prepare_chained_exception_message(self, cause):
274 def prepare_chained_exception_message(self, cause) -> List[Any]:
268 275 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
269 276 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
270 277
271 278 if cause:
272 279 message = [[direct_cause]]
273 280 else:
274 281 message = [[exception_during_handling]]
275 282 return message
276 283
277 284 @property
278 def has_colors(self):
285 def has_colors(self) -> bool:
279 286 return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
280 287
281 288 def set_colors(self, *args, **kw):
282 289 """Shorthand access to the color table scheme selector method."""
283 290
284 291 # Set own color table
285 292 self.color_scheme_table.set_active_scheme(*args, **kw)
286 293 # for convenience, set Colors to the active scheme
287 294 self.Colors = self.color_scheme_table.active_colors
288 295 # Also set colors of debugger
289 296 if hasattr(self, 'pdb') and self.pdb is not None:
290 297 self.pdb.set_colors(*args, **kw)
291 298
292 299 def color_toggle(self):
293 300 """Toggle between the currently active color scheme and NoColor."""
294 301
295 302 if self.color_scheme_table.active_scheme_name == 'NoColor':
296 303 self.color_scheme_table.set_active_scheme(self.old_scheme)
297 304 self.Colors = self.color_scheme_table.active_colors
298 305 else:
299 306 self.old_scheme = self.color_scheme_table.active_scheme_name
300 307 self.color_scheme_table.set_active_scheme('NoColor')
301 308 self.Colors = self.color_scheme_table.active_colors
302 309
303 310 def stb2text(self, stb):
304 311 """Convert a structured traceback (a list) to a string."""
305 312 return '\n'.join(stb)
306 313
307 def text(self, etype, value, tb, tb_offset=None, context=5):
314 def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5):
308 315 """Return formatted traceback.
309 316
310 317 Subclasses may override this if they add extra arguments.
311 318 """
312 319 tb_list = self.structured_traceback(etype, value, tb,
313 320 tb_offset, context)
314 321 return self.stb2text(tb_list)
315 322
316 def structured_traceback(self, etype, evalue, tb, tb_offset=None,
317 context=5, mode=None):
323 def structured_traceback(
324 self, etype, evalue, tb, tb_offset: Optional[int] = None, context=5, mode=None
325 ):
318 326 """Return a list of traceback frames.
319 327
320 328 Must be implemented by each class.
321 329 """
322 330 raise NotImplementedError()
323 331
324 332
325 333 #---------------------------------------------------------------------------
326 334 class ListTB(TBTools):
327 335 """Print traceback information from a traceback list, with optional color.
328 336
329 337 Calling requires 3 arguments: (etype, evalue, elist)
330 338 as would be obtained by::
331 339
332 340 etype, evalue, tb = sys.exc_info()
333 341 if tb:
334 342 elist = traceback.extract_tb(tb)
335 343 else:
336 344 elist = None
337 345
338 346 It can thus be used by programs which need to process the traceback before
339 347 printing (such as console replacements based on the code module from the
340 348 standard library).
341 349
342 350 Because they are meant to be called without a full traceback (only a
343 351 list), instances of this class can't call the interactive pdb debugger."""
344 352
345 353 def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None, parent=None, config=None):
346 354 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
347 355 ostream=ostream, parent=parent,config=config)
348 356
349 357 def __call__(self, etype, value, elist):
350 358 self.ostream.flush()
351 359 self.ostream.write(self.text(etype, value, elist))
352 360 self.ostream.write('\n')
353 361
354 362 def _extract_tb(self, tb):
355 363 if tb:
356 364 return traceback.extract_tb(tb)
357 365 else:
358 366 return None
359 367
360 def structured_traceback(self, etype, evalue, etb=None, tb_offset=None,
361 context=5):
368 def structured_traceback(
369 self,
370 etype: type,
371 evalue: BaseException,
372 etb: Optional[TracebackType] = None,
373 tb_offset: Optional[int] = None,
374 context=5,
375 ):
362 376 """Return a color formatted string with the traceback info.
363 377
364 378 Parameters
365 379 ----------
366 380 etype : exception type
367 381 Type of the exception raised.
368 382 evalue : object
369 383 Data stored in the exception
370 etb : object
384 etb : list | TracebackType | None
371 385 If list: List of frames, see class docstring for details.
372 386 If Traceback: Traceback of the exception.
373 387 tb_offset : int, optional
374 388 Number of frames in the traceback to skip. If not given, the
375 389 instance evalue is used (set in constructor).
376 390 context : int, optional
377 391 Number of lines of context information to print.
378 392
379 393 Returns
380 394 -------
381 395 String with formatted exception.
382 396 """
383 397 # This is a workaround to get chained_exc_ids in recursive calls
384 398 # etb should not be a tuple if structured_traceback is not recursive
385 399 if isinstance(etb, tuple):
386 400 etb, chained_exc_ids = etb
387 401 else:
388 402 chained_exc_ids = set()
389 403
390 404 if isinstance(etb, list):
391 405 elist = etb
392 406 elif etb is not None:
393 407 elist = self._extract_tb(etb)
394 408 else:
395 409 elist = []
396 410 tb_offset = self.tb_offset if tb_offset is None else tb_offset
411 assert isinstance(tb_offset, int)
397 412 Colors = self.Colors
398 413 out_list = []
399 414 if elist:
400 415
401 416 if tb_offset and len(elist) > tb_offset:
402 417 elist = elist[tb_offset:]
403 418
404 419 out_list.append('Traceback %s(most recent call last)%s:' %
405 420 (Colors.normalEm, Colors.Normal) + '\n')
406 421 out_list.extend(self._format_list(elist))
407 422 # The exception info should be a single entry in the list.
408 423 lines = ''.join(self._format_exception_only(etype, evalue))
409 424 out_list.append(lines)
410 425
411 426 exception = self.get_parts_of_chained_exception(evalue)
412 427
413 428 if exception and not id(exception[1]) in chained_exc_ids:
414 429 chained_exception_message = self.prepare_chained_exception_message(
415 430 evalue.__cause__)[0]
416 431 etype, evalue, etb = exception
417 432 # Trace exception to avoid infinite 'cause' loop
418 433 chained_exc_ids.add(id(exception[1]))
419 434 chained_exceptions_tb_offset = 0
420 435 out_list = (
421 436 self.structured_traceback(
422 437 etype, evalue, (etb, chained_exc_ids),
423 438 chained_exceptions_tb_offset, context)
424 439 + chained_exception_message
425 440 + out_list)
426 441
427 442 return out_list
428 443
429 444 def _format_list(self, extracted_list):
430 445 """Format a list of traceback entry tuples for printing.
431 446
432 447 Given a list of tuples as returned by extract_tb() or
433 448 extract_stack(), return a list of strings ready for printing.
434 449 Each string in the resulting list corresponds to the item with the
435 450 same index in the argument list. Each string ends in a newline;
436 451 the strings may contain internal newlines as well, for those items
437 452 whose source text line is not None.
438 453
439 454 Lifted almost verbatim from traceback.py
440 455 """
441 456
442 457 Colors = self.Colors
443 458 list = []
444 459 for filename, lineno, name, line in extracted_list[:-1]:
445 460 item = " %s in %s%s%s\n" % (
446 461 _format_filename(
447 462 filename, Colors.filename, Colors.Normal, lineno=lineno
448 463 ),
449 464 Colors.name,
450 465 name,
451 466 Colors.Normal,
452 467 )
453 468 if line:
454 469 item += ' %s\n' % line.strip()
455 470 list.append(item)
456 471 # Emphasize the last entry
457 472 filename, lineno, name, line = extracted_list[-1]
458 473 item = "%s %s in %s%s%s%s\n" % (
459 474 Colors.normalEm,
460 475 _format_filename(
461 476 filename, Colors.filenameEm, Colors.normalEm, lineno=lineno
462 477 ),
463 478 Colors.nameEm,
464 479 name,
465 480 Colors.normalEm,
466 481 Colors.Normal,
467 482 )
468 483 if line:
469 484 item += '%s %s%s\n' % (Colors.line, line.strip(),
470 485 Colors.Normal)
471 486 list.append(item)
472 487 return list
473 488
474 489 def _format_exception_only(self, etype, value):
475 490 """Format the exception part of a traceback.
476 491
477 492 The arguments are the exception type and value such as given by
478 493 sys.exc_info()[:2]. The return value is a list of strings, each ending
479 494 in a newline. Normally, the list contains a single string; however,
480 495 for SyntaxError exceptions, it contains several lines that (when
481 496 printed) display detailed information about where the syntax error
482 497 occurred. The message indicating which exception occurred is the
483 498 always last string in the list.
484 499
485 500 Also lifted nearly verbatim from traceback.py
486 501 """
487 502 have_filedata = False
488 503 Colors = self.Colors
489 504 list = []
490 505 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
491 506 if value is None:
492 507 # Not sure if this can still happen in Python 2.6 and above
493 508 list.append(stype + '\n')
494 509 else:
495 510 if issubclass(etype, SyntaxError):
496 511 have_filedata = True
497 512 if not value.filename: value.filename = "<string>"
498 513 if value.lineno:
499 514 lineno = value.lineno
500 515 textline = linecache.getline(value.filename, value.lineno)
501 516 else:
502 517 lineno = "unknown"
503 518 textline = ""
504 519 list.append(
505 520 "%s %s%s\n"
506 521 % (
507 522 Colors.normalEm,
508 523 _format_filename(
509 524 value.filename,
510 525 Colors.filenameEm,
511 526 Colors.normalEm,
512 527 lineno=(None if lineno == "unknown" else lineno),
513 528 ),
514 529 Colors.Normal,
515 530 )
516 531 )
517 532 if textline == "":
518 533 textline = py3compat.cast_unicode(value.text, "utf-8")
519 534
520 535 if textline is not None:
521 536 i = 0
522 537 while i < len(textline) and textline[i].isspace():
523 538 i += 1
524 539 list.append('%s %s%s\n' % (Colors.line,
525 540 textline.strip(),
526 541 Colors.Normal))
527 542 if value.offset is not None:
528 543 s = ' '
529 544 for c in textline[i:value.offset - 1]:
530 545 if c.isspace():
531 546 s += c
532 547 else:
533 548 s += ' '
534 549 list.append('%s%s^%s\n' % (Colors.caret, s,
535 550 Colors.Normal))
536 551
537 552 try:
538 553 s = value.msg
539 554 except Exception:
540 555 s = self._some_str(value)
541 556 if s:
542 557 list.append('%s%s:%s %s\n' % (stype, Colors.excName,
543 558 Colors.Normal, s))
544 559 else:
545 560 list.append('%s\n' % stype)
546 561
547 562 # sync with user hooks
548 563 if have_filedata:
549 564 ipinst = get_ipython()
550 565 if ipinst is not None:
551 566 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
552 567
553 568 return list
554 569
555 570 def get_exception_only(self, etype, value):
556 571 """Only print the exception type and message, without a traceback.
557 572
558 573 Parameters
559 574 ----------
560 575 etype : exception type
561 576 value : exception value
562 577 """
563 578 return ListTB.structured_traceback(self, etype, value)
564 579
565 580 def show_exception_only(self, etype, evalue):
566 581 """Only print the exception type and message, without a traceback.
567 582
568 583 Parameters
569 584 ----------
570 585 etype : exception type
571 586 evalue : exception value
572 587 """
573 588 # This method needs to use __call__ from *this* class, not the one from
574 589 # a subclass whose signature or behavior may be different
575 590 ostream = self.ostream
576 591 ostream.flush()
577 592 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
578 593 ostream.flush()
579 594
580 595 def _some_str(self, value):
581 596 # Lifted from traceback.py
582 597 try:
583 598 return py3compat.cast_unicode(str(value))
584 599 except:
585 600 return u'<unprintable %s object>' % type(value).__name__
586 601
587 602
588 603 #----------------------------------------------------------------------------
589 604 class VerboseTB(TBTools):
590 605 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
591 606 of HTML. Requires inspect and pydoc. Crazy, man.
592 607
593 608 Modified version which optionally strips the topmost entries from the
594 609 traceback, to be used with alternate interpreters (because their own code
595 610 would appear in the traceback)."""
596 611
597 def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None,
598 tb_offset=0, long_header=False, include_vars=True,
599 check_cache=None, debugger_cls = None,
600 parent=None, config=None):
612 def __init__(
613 self,
614 color_scheme: str = "Linux",
615 call_pdb: bool = False,
616 ostream=None,
617 tb_offset: int = 0,
618 long_header: bool = False,
619 include_vars: bool = True,
620 check_cache=None,
621 debugger_cls=None,
622 parent=None,
623 config=None,
624 ):
601 625 """Specify traceback offset, headers and color scheme.
602 626
603 627 Define how many frames to drop from the tracebacks. Calling it with
604 628 tb_offset=1 allows use of this handler in interpreters which will have
605 629 their own code at the top of the traceback (VerboseTB will first
606 630 remove that frame before printing the traceback info)."""
607 631 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
608 632 ostream=ostream, parent=parent, config=config)
609 633 self.tb_offset = tb_offset
610 634 self.long_header = long_header
611 635 self.include_vars = include_vars
612 636 # By default we use linecache.checkcache, but the user can provide a
613 637 # different check_cache implementation. This is used by the IPython
614 638 # kernel to provide tracebacks for interactive code that is cached,
615 639 # by a compiler instance that flushes the linecache but preserves its
616 640 # own code cache.
617 641 if check_cache is None:
618 642 check_cache = linecache.checkcache
619 643 self.check_cache = check_cache
620 644
621 645 self.debugger_cls = debugger_cls or debugger.Pdb
622 646 self.skip_hidden = True
623 647
624 648 def format_record(self, frame_info):
625 649 """Format a single stack frame"""
626 650 Colors = self.Colors # just a shorthand + quicker name lookup
627 651 ColorsNormal = Colors.Normal # used a lot
628 652
629 653 if isinstance(frame_info, stack_data.RepeatedFrames):
630 654 return ' %s[... skipping similar frames: %s]%s\n' % (
631 655 Colors.excName, frame_info.description, ColorsNormal)
632 656
633 657 indent = " " * INDENT_SIZE
634 658 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
635 659 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
636 660 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
637 661 Colors.vName,
638 662 Colors.valEm,
639 663 ColorsNormal,
640 664 )
641 665 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
642 666
643 667 link = _format_filename(
644 668 frame_info.filename,
645 669 Colors.filenameEm,
646 670 ColorsNormal,
647 671 lineno=frame_info.lineno,
648 672 )
649 673 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
650 674
651 675 func = frame_info.executing.code_qualname()
652 676 if func == "<module>":
653 677 call = tpl_call.format(file=func, scope="")
654 678 else:
655 679 # Decide whether to include variable details or not
656 680 var_repr = eqrepr if self.include_vars else nullrepr
657 681 try:
658 682 scope = inspect.formatargvalues(
659 683 args, varargs, varkw, locals_, formatvalue=var_repr
660 684 )
661 685 call = tpl_call.format(file=func, scope=scope)
662 686 except KeyError:
663 687 # This happens in situations like errors inside generator
664 688 # expressions, where local variables are listed in the
665 689 # line, but can't be extracted from the frame. I'm not
666 690 # 100% sure this isn't actually a bug in inspect itself,
667 691 # but since there's no info for us to compute with, the
668 692 # best we can do is report the failure and move on. Here
669 693 # we must *not* call any traceback construction again,
670 694 # because that would mess up use of %debug later on. So we
671 695 # simply report the failure and move on. The only
672 696 # limitation will be that this frame won't have locals
673 697 # listed in the call signature. Quite subtle problem...
674 698 # I can't think of a good way to validate this in a unit
675 699 # test, but running a script consisting of:
676 700 # dict( (k,v.strip()) for (k,v) in range(10) )
677 701 # will illustrate the error, if this exception catch is
678 702 # disabled.
679 703 call = tpl_call_fail % func
680 704
681 705 lvals = ''
682 706 lvals_list = []
683 707 if self.include_vars:
684 708 try:
685 709 # we likely want to fix stackdata at some point, but
686 710 # still need a workaround.
687 711 fibp = frame_info.variables_in_executing_piece
688 712 for var in fibp:
689 713 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
690 714 except Exception:
691 715 lvals_list.append(
692 716 "Exception trying to inspect frame. No more locals available."
693 717 )
694 718 if lvals_list:
695 719 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
696 720
697 721 result = "%s, %s\n" % (link, call)
698 722
699 723 result += ''.join(_format_traceback_lines(frame_info.lines, Colors, self.has_colors, lvals))
700 724 return result
701 725
702 726 def prepare_header(self, etype, long_version=False):
703 727 colors = self.Colors # just a shorthand + quicker name lookup
704 728 colorsnormal = colors.Normal # used a lot
705 729 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
706 730 width = min(75, get_terminal_size()[0])
707 731 if long_version:
708 732 # Header with the exception type, python version, and date
709 733 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
710 734 date = time.ctime(time.time())
711 735
712 736 head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * width, colorsnormal,
713 737 exc, ' ' * (width - len(str(etype)) - len(pyver)),
714 738 pyver, date.rjust(width) )
715 739 head += "\nA problem occurred executing Python code. Here is the sequence of function" \
716 740 "\ncalls leading up to the error, with the most recent (innermost) call last."
717 741 else:
718 742 # Simplified header
719 743 head = '%s%s' % (exc, 'Traceback (most recent call last)'. \
720 744 rjust(width - len(str(etype))) )
721 745
722 746 return head
723 747
724 748 def format_exception(self, etype, evalue):
725 749 colors = self.Colors # just a shorthand + quicker name lookup
726 750 colorsnormal = colors.Normal # used a lot
727 751 # Get (safely) a string form of the exception info
728 752 try:
729 753 etype_str, evalue_str = map(str, (etype, evalue))
730 754 except:
731 755 # User exception is improperly defined.
732 756 etype, evalue = str, sys.exc_info()[:2]
733 757 etype_str, evalue_str = map(str, (etype, evalue))
734 758 # ... and format it
735 759 return ['%s%s%s: %s' % (colors.excName, etype_str,
736 760 colorsnormal, py3compat.cast_unicode(evalue_str))]
737 761
738 def format_exception_as_a_whole(self, etype, evalue, etb, number_of_lines_of_context, tb_offset):
762 def format_exception_as_a_whole(
763 self,
764 etype: type,
765 evalue: BaseException,
766 etb: TracebackType,
767 number_of_lines_of_context,
768 tb_offset: Optional[int],
769 ):
739 770 """Formats the header, traceback and exception message for a single exception.
740 771
741 772 This may be called multiple times by Python 3 exception chaining
742 773 (PEP 3134).
743 774 """
744 775 assert etb is not None
745 776 # some locals
746 777 orig_etype = etype
747 778 try:
748 779 etype = etype.__name__
749 780 except AttributeError:
750 781 pass
751 782
752 783 tb_offset = self.tb_offset if tb_offset is None else tb_offset
784 assert isinstance(tb_offset, int)
753 785 head = self.prepare_header(etype, self.long_header)
754 786 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
755 787
756 788 frames = []
757 789 skipped = 0
758 790 lastrecord = len(records) - 1
759 791 for i, r in enumerate(records):
760 792 if not isinstance(r, stack_data.RepeatedFrames) and self.skip_hidden:
761 793 if r.frame.f_locals.get("__tracebackhide__", 0) and i != lastrecord:
762 794 skipped += 1
763 795 continue
764 796 if skipped:
765 797 Colors = self.Colors # just a shorthand + quicker name lookup
766 798 ColorsNormal = Colors.Normal # used a lot
767 799 frames.append(
768 800 " %s[... skipping hidden %s frame]%s\n"
769 801 % (Colors.excName, skipped, ColorsNormal)
770 802 )
771 803 skipped = 0
772 804 frames.append(self.format_record(r))
773 805 if skipped:
774 806 Colors = self.Colors # just a shorthand + quicker name lookup
775 807 ColorsNormal = Colors.Normal # used a lot
776 808 frames.append(
777 809 " %s[... skipping hidden %s frame]%s\n"
778 810 % (Colors.excName, skipped, ColorsNormal)
779 811 )
780 812
781 813 formatted_exception = self.format_exception(etype, evalue)
782 814 if records:
783 815 frame_info = records[-1]
784 816 ipinst = get_ipython()
785 817 if ipinst is not None:
786 818 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
787 819
788 820 return [[head] + frames + [''.join(formatted_exception[0])]]
789 821
790 def get_records(self, etb, number_of_lines_of_context, tb_offset):
822 def get_records(
823 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
824 ):
791 825 context = number_of_lines_of_context - 1
792 826 after = context // 2
793 827 before = context - after
794 828 if self.has_colors:
795 829 style = get_style_by_name('default')
796 830 style = stack_data.style_with_executing_node(style, 'bg:#00005f')
797 831 formatter = Terminal256Formatter(style=style)
798 832 else:
799 833 formatter = None
800 834 options = stack_data.Options(
801 835 before=before,
802 836 after=after,
803 837 pygments_formatter=formatter,
804 838 )
805 839 assert etb is not None
806 840 return list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
807 841
808 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
809 number_of_lines_of_context=5):
842 def structured_traceback(
843 self,
844 etype: type,
845 evalue: Optional[BaseException],
846 etb: TracebackType,
847 tb_offset: Optional[int] = None,
848 number_of_lines_of_context: int = 5,
849 ):
810 850 """Return a nice text document describing the traceback."""
811 851 assert etb is not None
812 852 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
813 853 tb_offset)
814 854
815 855 colors = self.Colors # just a shorthand + quicker name lookup
816 856 colorsnormal = colors.Normal # used a lot
817 857 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
818 858 structured_traceback_parts = [head]
819 859 chained_exceptions_tb_offset = 0
820 860 lines_of_context = 3
821 861 formatted_exceptions = formatted_exception
822 862 exception = self.get_parts_of_chained_exception(evalue)
823 863 if exception:
864 assert evalue is not None
824 865 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
825 866 etype, evalue, etb = exception
826 867 else:
827 868 evalue = None
828 869 chained_exc_ids = set()
829 870 while evalue:
830 871 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
831 872 chained_exceptions_tb_offset)
832 873 exception = self.get_parts_of_chained_exception(evalue)
833 874
834 875 if exception and not id(exception[1]) in chained_exc_ids:
835 876 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
836 877 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
837 878 etype, evalue, etb = exception
838 879 else:
839 880 evalue = None
840 881
841 882 # we want to see exceptions in a reversed order:
842 883 # the first exception should be on top
843 884 for formatted_exception in reversed(formatted_exceptions):
844 885 structured_traceback_parts += formatted_exception
845 886
846 887 return structured_traceback_parts
847 888
848 def debugger(self, force=False):
889 def debugger(self, force: bool = False):
849 890 """Call up the pdb debugger if desired, always clean up the tb
850 891 reference.
851 892
852 893 Keywords:
853 894
854 895 - force(False): by default, this routine checks the instance call_pdb
855 896 flag and does not actually invoke the debugger if the flag is false.
856 897 The 'force' option forces the debugger to activate even if the flag
857 898 is false.
858 899
859 900 If the call_pdb flag is set, the pdb interactive debugger is
860 901 invoked. In all cases, the self.tb reference to the current traceback
861 902 is deleted to prevent lingering references which hamper memory
862 903 management.
863 904
864 905 Note that each call to pdb() does an 'import readline', so if your app
865 906 requires a special setup for the readline completers, you'll have to
866 907 fix that by hand after invoking the exception handler."""
867 908
868 909 if force or self.call_pdb:
869 910 if self.pdb is None:
870 911 self.pdb = self.debugger_cls()
871 912 # the system displayhook may have changed, restore the original
872 913 # for pdb
873 914 display_trap = DisplayTrap(hook=sys.__displayhook__)
874 915 with display_trap:
875 916 self.pdb.reset()
876 917 # Find the right frame so we don't pop up inside ipython itself
877 918 if hasattr(self, 'tb') and self.tb is not None:
878 919 etb = self.tb
879 920 else:
880 921 etb = self.tb = sys.last_traceback
881 922 while self.tb is not None and self.tb.tb_next is not None:
923 assert self.tb.tb_next is not None
882 924 self.tb = self.tb.tb_next
883 925 if etb and etb.tb_next:
884 926 etb = etb.tb_next
885 927 self.pdb.botframe = etb.tb_frame
886 928 self.pdb.interaction(None, etb)
887 929
888 930 if hasattr(self, 'tb'):
889 931 del self.tb
890 932
891 933 def handler(self, info=None):
892 934 (etype, evalue, etb) = info or sys.exc_info()
893 935 self.tb = etb
894 936 ostream = self.ostream
895 937 ostream.flush()
896 938 ostream.write(self.text(etype, evalue, etb))
897 939 ostream.write('\n')
898 940 ostream.flush()
899 941
900 942 # Changed so an instance can just be called as VerboseTB_inst() and print
901 943 # out the right info on its own.
902 944 def __call__(self, etype=None, evalue=None, etb=None):
903 945 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
904 946 if etb is None:
905 947 self.handler()
906 948 else:
907 949 self.handler((etype, evalue, etb))
908 950 try:
909 951 self.debugger()
910 952 except KeyboardInterrupt:
911 953 print("\nKeyboardInterrupt")
912 954
913 955
914 956 #----------------------------------------------------------------------------
915 957 class FormattedTB(VerboseTB, ListTB):
916 958 """Subclass ListTB but allow calling with a traceback.
917 959
918 960 It can thus be used as a sys.excepthook for Python > 2.1.
919 961
920 962 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
921 963
922 964 Allows a tb_offset to be specified. This is useful for situations where
923 965 one needs to remove a number of topmost frames from the traceback (such as
924 966 occurs with python programs that themselves execute other python code,
925 967 like Python shells). """
926 968
969 mode: str
970
927 971 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
928 972 ostream=None,
929 973 tb_offset=0, long_header=False, include_vars=False,
930 974 check_cache=None, debugger_cls=None,
931 975 parent=None, config=None):
932 976
933 977 # NEVER change the order of this list. Put new modes at the end:
934 978 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
935 979 self.verbose_modes = self.valid_modes[1:3]
936 980
937 981 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
938 982 ostream=ostream, tb_offset=tb_offset,
939 983 long_header=long_header, include_vars=include_vars,
940 984 check_cache=check_cache, debugger_cls=debugger_cls,
941 985 parent=parent, config=config)
942 986
943 987 # Different types of tracebacks are joined with different separators to
944 988 # form a single string. They are taken from this dict
945 989 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
946 990 Minimal='')
947 991 # set_mode also sets the tb_join_char attribute
948 992 self.set_mode(mode)
949 993
950 994 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
951 995 tb_offset = self.tb_offset if tb_offset is None else tb_offset
952 996 mode = self.mode
953 997 if mode in self.verbose_modes:
954 998 # Verbose modes need a full traceback
955 999 return VerboseTB.structured_traceback(
956 1000 self, etype, value, tb, tb_offset, number_of_lines_of_context
957 1001 )
958 1002 elif mode == 'Minimal':
959 1003 return ListTB.get_exception_only(self, etype, value)
960 1004 else:
961 1005 # We must check the source cache because otherwise we can print
962 1006 # out-of-date source code.
963 1007 self.check_cache()
964 1008 # Now we can extract and format the exception
965 1009 return ListTB.structured_traceback(
966 1010 self, etype, value, tb, tb_offset, number_of_lines_of_context
967 1011 )
968 1012
969 1013 def stb2text(self, stb):
970 1014 """Convert a structured traceback (a list) to a string."""
971 1015 return self.tb_join_char.join(stb)
972 1016
973
974 def set_mode(self, mode=None):
1017 def set_mode(self, mode: Optional[str] = None):
975 1018 """Switch to the desired mode.
976 1019
977 1020 If mode is not specified, cycles through the available modes."""
978 1021
979 1022 if not mode:
980 1023 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
981 1024 len(self.valid_modes)
982 1025 self.mode = self.valid_modes[new_idx]
983 1026 elif mode not in self.valid_modes:
984 raise ValueError('Unrecognized mode in FormattedTB: <' + mode + '>\n'
985 'Valid modes: ' + str(self.valid_modes))
1027 raise ValueError(
1028 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1029 "Valid modes: " + str(self.valid_modes)
1030 )
986 1031 else:
1032 assert isinstance(mode, str)
987 1033 self.mode = mode
988 1034 # include variable details only in 'Verbose' mode
989 1035 self.include_vars = (self.mode == self.valid_modes[2])
990 1036 # Set the join character for generating text tracebacks
991 1037 self.tb_join_char = self._join_chars[self.mode]
992 1038
993 1039 # some convenient shortcuts
994 1040 def plain(self):
995 1041 self.set_mode(self.valid_modes[0])
996 1042
997 1043 def context(self):
998 1044 self.set_mode(self.valid_modes[1])
999 1045
1000 1046 def verbose(self):
1001 1047 self.set_mode(self.valid_modes[2])
1002 1048
1003 1049 def minimal(self):
1004 1050 self.set_mode(self.valid_modes[3])
1005 1051
1006 1052
1007 1053 #----------------------------------------------------------------------------
1008 1054 class AutoFormattedTB(FormattedTB):
1009 1055 """A traceback printer which can be called on the fly.
1010 1056
1011 1057 It will find out about exceptions by itself.
1012 1058
1013 1059 A brief example::
1014 1060
1015 1061 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1016 1062 try:
1017 1063 ...
1018 1064 except:
1019 1065 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1020 1066 """
1021 1067
1022 1068 def __call__(self, etype=None, evalue=None, etb=None,
1023 1069 out=None, tb_offset=None):
1024 1070 """Print out a formatted exception traceback.
1025 1071
1026 1072 Optional arguments:
1027 1073 - out: an open file-like object to direct output to.
1028 1074
1029 1075 - tb_offset: the number of frames to skip over in the stack, on a
1030 1076 per-call basis (this overrides temporarily the instance's tb_offset
1031 1077 given at initialization time."""
1032 1078
1033 1079 if out is None:
1034 1080 out = self.ostream
1035 1081 out.flush()
1036 1082 out.write(self.text(etype, evalue, etb, tb_offset))
1037 1083 out.write('\n')
1038 1084 out.flush()
1039 1085 # FIXME: we should remove the auto pdb behavior from here and leave
1040 1086 # that to the clients.
1041 1087 try:
1042 1088 self.debugger()
1043 1089 except KeyboardInterrupt:
1044 1090 print("\nKeyboardInterrupt")
1045 1091
1046 1092 def structured_traceback(self, etype=None, value=None, tb=None,
1047 1093 tb_offset=None, number_of_lines_of_context=5):
1094
1095 etype: type
1096 value: BaseException
1097 # tb: TracebackType or tupleof tb types ?
1048 1098 if etype is None:
1049 1099 etype, value, tb = sys.exc_info()
1050 1100 if isinstance(tb, tuple):
1051 1101 # tb is a tuple if this is a chained exception.
1052 1102 self.tb = tb[0]
1053 1103 else:
1054 1104 self.tb = tb
1055 1105 return FormattedTB.structured_traceback(
1056 1106 self, etype, value, tb, tb_offset, number_of_lines_of_context)
1057 1107
1058 1108
1059 1109 #---------------------------------------------------------------------------
1060 1110
1061 1111 # A simple class to preserve Nathan's original functionality.
1062 1112 class ColorTB(FormattedTB):
1063 1113 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1064 1114
1065 1115 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1066 1116 FormattedTB.__init__(self, color_scheme=color_scheme,
1067 1117 call_pdb=call_pdb, **kwargs)
1068 1118
1069 1119
1070 1120 class SyntaxTB(ListTB):
1071 1121 """Extension which holds some state: the last exception value"""
1072 1122
1073 1123 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1074 1124 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1075 1125 self.last_syntax_error = None
1076 1126
1077 1127 def __call__(self, etype, value, elist):
1078 1128 self.last_syntax_error = value
1079 1129
1080 1130 ListTB.__call__(self, etype, value, elist)
1081 1131
1082 1132 def structured_traceback(self, etype, value, elist, tb_offset=None,
1083 1133 context=5):
1084 1134 # If the source file has been edited, the line in the syntax error can
1085 1135 # be wrong (retrieved from an outdated cache). This replaces it with
1086 1136 # the current value.
1087 1137 if isinstance(value, SyntaxError) \
1088 1138 and isinstance(value.filename, str) \
1089 1139 and isinstance(value.lineno, int):
1090 1140 linecache.checkcache(value.filename)
1091 1141 newtext = linecache.getline(value.filename, value.lineno)
1092 1142 if newtext:
1093 1143 value.text = newtext
1094 1144 self.last_syntax_error = value
1095 1145 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1096 1146 tb_offset=tb_offset, context=context)
1097 1147
1098 1148 def clear_err_state(self):
1099 1149 """Return the current error state and clear it"""
1100 1150 e = self.last_syntax_error
1101 1151 self.last_syntax_error = None
1102 1152 return e
1103 1153
1104 1154 def stb2text(self, stb):
1105 1155 """Convert a structured traceback (a list) to a string."""
1106 1156 return ''.join(stb)
1107 1157
1108 1158
1109 1159 # some internal-use functions
1110 1160 def text_repr(value):
1111 1161 """Hopefully pretty robust repr equivalent."""
1112 1162 # this is pretty horrible but should always return *something*
1113 1163 try:
1114 1164 return pydoc.text.repr(value)
1115 1165 except KeyboardInterrupt:
1116 1166 raise
1117 1167 except:
1118 1168 try:
1119 1169 return repr(value)
1120 1170 except KeyboardInterrupt:
1121 1171 raise
1122 1172 except:
1123 1173 try:
1124 1174 # all still in an except block so we catch
1125 1175 # getattr raising
1126 1176 name = getattr(value, '__name__', None)
1127 1177 if name:
1128 1178 # ick, recursion
1129 1179 return text_repr(name)
1130 1180 klass = getattr(value, '__class__', None)
1131 1181 if klass:
1132 1182 return '%s instance' % text_repr(klass)
1133 1183 except KeyboardInterrupt:
1134 1184 raise
1135 1185 except:
1136 1186 return 'UNRECOVERABLE REPR FAILURE'
1137 1187
1138 1188
1139 1189 def eqrepr(value, repr=text_repr):
1140 1190 return '=%s' % repr(value)
1141 1191
1142 1192
1143 1193 def nullrepr(value, repr=text_repr):
1144 1194 return ''
General Comments 0
You need to be logged in to leave comments. Login now