##// END OF EJS Templates
Prevent crash on StackData NotOneValueFound....
Matthias Bussonnier -
Show More
@@ -1,1525 +1,1538 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 from collections.abc import Sequence
93 93 import functools
94 94 import inspect
95 95 import linecache
96 96 import pydoc
97 97 import sys
98 98 import time
99 99 import traceback
100 100 import types
101 101 from types import TracebackType
102 102 from typing import Any, List, Optional, Tuple
103 103
104 104 import stack_data
105 105 from pygments.formatters.terminal256 import Terminal256Formatter
106 106 from pygments.styles import get_style_by_name
107 107
108 108 import IPython.utils.colorable as colorable
109 109 # IPython's own modules
110 110 from IPython import get_ipython
111 111 from IPython.core import debugger
112 112 from IPython.core.display_trap import DisplayTrap
113 113 from IPython.core.excolors import exception_colors
114 114 from IPython.utils import PyColorize
115 115 from IPython.utils import path as util_path
116 116 from IPython.utils import py3compat
117 117 from IPython.utils.terminal import get_terminal_size
118 118
119 119 # Globals
120 120 # amount of space to put line numbers before verbose tracebacks
121 121 INDENT_SIZE = 8
122 122
123 123 # Default color scheme. This is used, for example, by the traceback
124 124 # formatter. When running in an actual IPython instance, the user's rc.colors
125 125 # value is used, but having a module global makes this functionality available
126 126 # to users of ultratb who are NOT running inside ipython.
127 127 DEFAULT_SCHEME = 'NoColor'
128 128 FAST_THRESHOLD = 10_000
129 129
130 130 # ---------------------------------------------------------------------------
131 131 # Code begins
132 132
133 133 # Helper function -- largely belongs to VerboseTB, but we need the same
134 134 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
135 135 # can be recognized properly by ipython.el's py-traceback-line-re
136 136 # (SyntaxErrors have to be treated specially because they have no traceback)
137 137
138 138
139 139 @functools.lru_cache()
140 140 def count_lines_in_py_file(filename: str) -> int:
141 141 """
142 142 Given a filename, returns the number of lines in the file
143 143 if it ends with the extension ".py". Otherwise, returns 0.
144 144 """
145 145 if not filename.endswith(".py"):
146 146 return 0
147 147 else:
148 148 try:
149 149 with open(filename, "r") as file:
150 150 s = sum(1 for line in file)
151 151 except UnicodeError:
152 152 return 0
153 153 return s
154 154
155 155 """
156 156 Given a frame object, returns the total number of lines in the file
157 157 if the filename ends with the extension ".py". Otherwise, returns 0.
158 158 """
159 159
160 160
161 161 def get_line_number_of_frame(frame: types.FrameType) -> int:
162 162 """
163 163 Given a frame object, returns the total number of lines in the file
164 164 containing the frame's code object, or the number of lines in the
165 165 frame's source code if the file is not available.
166 166
167 167 Parameters
168 168 ----------
169 169 frame : FrameType
170 170 The frame object whose line number is to be determined.
171 171
172 172 Returns
173 173 -------
174 174 int
175 175 The total number of lines in the file containing the frame's
176 176 code object, or the number of lines in the frame's source code
177 177 if the file is not available.
178 178 """
179 179 filename = frame.f_code.co_filename
180 180 if filename is None:
181 181 print("No file....")
182 182 lines, first = inspect.getsourcelines(frame)
183 183 return first + len(lines)
184 184 return count_lines_in_py_file(filename)
185 185
186 186
187 187 def _safe_string(value, what, func=str):
188 188 # Copied from cpython/Lib/traceback.py
189 189 try:
190 190 return func(value)
191 191 except:
192 192 return f"<{what} {func.__name__}() failed>"
193 193
194 194
195 195 def _format_traceback_lines(lines, Colors, has_colors: bool, lvals):
196 196 """
197 197 Format tracebacks lines with pointing arrow, leading numbers...
198 198
199 199 Parameters
200 200 ----------
201 201 lines : list[Line]
202 202 Colors
203 203 ColorScheme used.
204 204 lvals : str
205 205 Values of local variables, already colored, to inject just after the error line.
206 206 """
207 207 numbers_width = INDENT_SIZE - 1
208 208 res = []
209 209
210 210 for stack_line in lines:
211 211 if stack_line is stack_data.LINE_GAP:
212 212 res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal))
213 213 continue
214 214
215 215 line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
216 216 lineno = stack_line.lineno
217 217 if stack_line.is_current:
218 218 # This is the line with the error
219 219 pad = numbers_width - len(str(lineno))
220 220 num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
221 221 start_color = Colors.linenoEm
222 222 else:
223 223 num = '%*s' % (numbers_width, lineno)
224 224 start_color = Colors.lineno
225 225
226 226 line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
227 227
228 228 res.append(line)
229 229 if lvals and stack_line.is_current:
230 230 res.append(lvals + '\n')
231 231 return res
232 232
233 233 def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):
234 234 """
235 235 Format tracebacks lines with pointing arrow, leading numbers...
236 236
237 237 Parameters
238 238 ==========
239 239
240 240 lnum: int
241 241 number of the target line of code.
242 242 index: int
243 243 which line in the list should be highlighted.
244 244 lines: list[string]
245 245 Colors:
246 246 ColorScheme used.
247 247 lvals: bytes
248 248 Values of local variables, already colored, to inject just after the error line.
249 249 _line_format: f (str) -> (str, bool)
250 250 return (colorized version of str, failure to do so)
251 251 """
252 252 numbers_width = INDENT_SIZE - 1
253 253 res = []
254 254 for i, line in enumerate(lines, lnum - index):
255 255 # assert isinstance(line, str)
256 256 line = py3compat.cast_unicode(line)
257 257
258 258 new_line, err = _line_format(line, "str")
259 259 if not err:
260 260 line = new_line
261 261
262 262 if i == lnum:
263 263 # This is the line with the error
264 264 pad = numbers_width - len(str(i))
265 265 num = "%s%s" % (debugger.make_arrow(pad), str(lnum))
266 266 line = "%s%s%s %s%s" % (
267 267 Colors.linenoEm,
268 268 num,
269 269 Colors.line,
270 270 line,
271 271 Colors.Normal,
272 272 )
273 273 else:
274 274 num = "%*s" % (numbers_width, i)
275 275 line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line)
276 276
277 277 res.append(line)
278 278 if lvals and i == lnum:
279 279 res.append(lvals + "\n")
280 280 return res
281 281
282 282
283 283 def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
284 284 """
285 285 Format filename lines with custom formatting from caching compiler or `File *.py` by default
286 286
287 287 Parameters
288 288 ----------
289 289 file : str
290 290 ColorFilename
291 291 ColorScheme's filename coloring to be used.
292 292 ColorNormal
293 293 ColorScheme's normal coloring to be used.
294 294 """
295 295 ipinst = get_ipython()
296 296 if (
297 297 ipinst is not None
298 298 and (data := ipinst.compile.format_code_name(file)) is not None
299 299 ):
300 300 label, name = data
301 301 if lineno is None:
302 302 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
303 303 else:
304 304 tpl_link = (
305 305 f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}"
306 306 )
307 307 else:
308 308 label = "File"
309 309 name = util_path.compress_user(
310 310 py3compat.cast_unicode(file, util_path.fs_encoding)
311 311 )
312 312 if lineno is None:
313 313 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
314 314 else:
315 315 # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon?
316 316 tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}"
317 317
318 318 return tpl_link.format(label=label, name=name, lineno=lineno)
319 319
320 320 #---------------------------------------------------------------------------
321 321 # Module classes
322 322 class TBTools(colorable.Colorable):
323 323 """Basic tools used by all traceback printer classes."""
324 324
325 325 # Number of frames to skip when reporting tracebacks
326 326 tb_offset = 0
327 327
328 328 def __init__(
329 329 self,
330 330 color_scheme="NoColor",
331 331 call_pdb=False,
332 332 ostream=None,
333 333 parent=None,
334 334 config=None,
335 335 *,
336 336 debugger_cls=None,
337 337 ):
338 338 # Whether to call the interactive pdb debugger after printing
339 339 # tracebacks or not
340 340 super(TBTools, self).__init__(parent=parent, config=config)
341 341 self.call_pdb = call_pdb
342 342
343 343 # Output stream to write to. Note that we store the original value in
344 344 # a private attribute and then make the public ostream a property, so
345 345 # that we can delay accessing sys.stdout until runtime. The way
346 346 # things are written now, the sys.stdout object is dynamically managed
347 347 # so a reference to it should NEVER be stored statically. This
348 348 # property approach confines this detail to a single location, and all
349 349 # subclasses can simply access self.ostream for writing.
350 350 self._ostream = ostream
351 351
352 352 # Create color table
353 353 self.color_scheme_table = exception_colors()
354 354
355 355 self.set_colors(color_scheme)
356 356 self.old_scheme = color_scheme # save initial value for toggles
357 357 self.debugger_cls = debugger_cls or debugger.Pdb
358 358
359 359 if call_pdb:
360 360 self.pdb = self.debugger_cls()
361 361 else:
362 362 self.pdb = None
363 363
364 364 def _get_ostream(self):
365 365 """Output stream that exceptions are written to.
366 366
367 367 Valid values are:
368 368
369 369 - None: the default, which means that IPython will dynamically resolve
370 370 to sys.stdout. This ensures compatibility with most tools, including
371 371 Windows (where plain stdout doesn't recognize ANSI escapes).
372 372
373 373 - Any object with 'write' and 'flush' attributes.
374 374 """
375 375 return sys.stdout if self._ostream is None else self._ostream
376 376
377 377 def _set_ostream(self, val):
378 378 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
379 379 self._ostream = val
380 380
381 381 ostream = property(_get_ostream, _set_ostream)
382 382
383 383 @staticmethod
384 384 def _get_chained_exception(exception_value):
385 385 cause = getattr(exception_value, "__cause__", None)
386 386 if cause:
387 387 return cause
388 388 if getattr(exception_value, "__suppress_context__", False):
389 389 return None
390 390 return getattr(exception_value, "__context__", None)
391 391
392 392 def get_parts_of_chained_exception(
393 393 self, evalue
394 394 ) -> Optional[Tuple[type, BaseException, TracebackType]]:
395 395 chained_evalue = self._get_chained_exception(evalue)
396 396
397 397 if chained_evalue:
398 398 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
399 399 return None
400 400
401 401 def prepare_chained_exception_message(self, cause) -> List[Any]:
402 402 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
403 403 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
404 404
405 405 if cause:
406 406 message = [[direct_cause]]
407 407 else:
408 408 message = [[exception_during_handling]]
409 409 return message
410 410
411 411 @property
412 412 def has_colors(self) -> bool:
413 413 return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
414 414
415 415 def set_colors(self, *args, **kw):
416 416 """Shorthand access to the color table scheme selector method."""
417 417
418 418 # Set own color table
419 419 self.color_scheme_table.set_active_scheme(*args, **kw)
420 420 # for convenience, set Colors to the active scheme
421 421 self.Colors = self.color_scheme_table.active_colors
422 422 # Also set colors of debugger
423 423 if hasattr(self, 'pdb') and self.pdb is not None:
424 424 self.pdb.set_colors(*args, **kw)
425 425
426 426 def color_toggle(self):
427 427 """Toggle between the currently active color scheme and NoColor."""
428 428
429 429 if self.color_scheme_table.active_scheme_name == 'NoColor':
430 430 self.color_scheme_table.set_active_scheme(self.old_scheme)
431 431 self.Colors = self.color_scheme_table.active_colors
432 432 else:
433 433 self.old_scheme = self.color_scheme_table.active_scheme_name
434 434 self.color_scheme_table.set_active_scheme('NoColor')
435 435 self.Colors = self.color_scheme_table.active_colors
436 436
437 437 def stb2text(self, stb):
438 438 """Convert a structured traceback (a list) to a string."""
439 439 return '\n'.join(stb)
440 440
441 441 def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5):
442 442 """Return formatted traceback.
443 443
444 444 Subclasses may override this if they add extra arguments.
445 445 """
446 446 tb_list = self.structured_traceback(etype, value, tb,
447 447 tb_offset, context)
448 448 return self.stb2text(tb_list)
449 449
450 450 def structured_traceback(
451 451 self,
452 452 etype: type,
453 453 evalue: Optional[BaseException],
454 454 etb: Optional[TracebackType] = None,
455 455 tb_offset: Optional[int] = None,
456 456 number_of_lines_of_context: int = 5,
457 457 ):
458 458 """Return a list of traceback frames.
459 459
460 460 Must be implemented by each class.
461 461 """
462 462 raise NotImplementedError()
463 463
464 464
465 465 #---------------------------------------------------------------------------
466 466 class ListTB(TBTools):
467 467 """Print traceback information from a traceback list, with optional color.
468 468
469 469 Calling requires 3 arguments: (etype, evalue, elist)
470 470 as would be obtained by::
471 471
472 472 etype, evalue, tb = sys.exc_info()
473 473 if tb:
474 474 elist = traceback.extract_tb(tb)
475 475 else:
476 476 elist = None
477 477
478 478 It can thus be used by programs which need to process the traceback before
479 479 printing (such as console replacements based on the code module from the
480 480 standard library).
481 481
482 482 Because they are meant to be called without a full traceback (only a
483 483 list), instances of this class can't call the interactive pdb debugger."""
484 484
485 485
486 486 def __call__(self, etype, value, elist):
487 487 self.ostream.flush()
488 488 self.ostream.write(self.text(etype, value, elist))
489 489 self.ostream.write('\n')
490 490
491 491 def _extract_tb(self, tb):
492 492 if tb:
493 493 return traceback.extract_tb(tb)
494 494 else:
495 495 return None
496 496
497 497 def structured_traceback(
498 498 self,
499 499 etype: type,
500 500 evalue: Optional[BaseException],
501 501 etb: Optional[TracebackType] = None,
502 502 tb_offset: Optional[int] = None,
503 503 context=5,
504 504 ):
505 505 """Return a color formatted string with the traceback info.
506 506
507 507 Parameters
508 508 ----------
509 509 etype : exception type
510 510 Type of the exception raised.
511 511 evalue : object
512 512 Data stored in the exception
513 513 etb : list | TracebackType | None
514 514 If list: List of frames, see class docstring for details.
515 515 If Traceback: Traceback of the exception.
516 516 tb_offset : int, optional
517 517 Number of frames in the traceback to skip. If not given, the
518 518 instance evalue is used (set in constructor).
519 519 context : int, optional
520 520 Number of lines of context information to print.
521 521
522 522 Returns
523 523 -------
524 524 String with formatted exception.
525 525 """
526 526 # This is a workaround to get chained_exc_ids in recursive calls
527 527 # etb should not be a tuple if structured_traceback is not recursive
528 528 if isinstance(etb, tuple):
529 529 etb, chained_exc_ids = etb
530 530 else:
531 531 chained_exc_ids = set()
532 532
533 533 if isinstance(etb, list):
534 534 elist = etb
535 535 elif etb is not None:
536 536 elist = self._extract_tb(etb)
537 537 else:
538 538 elist = []
539 539 tb_offset = self.tb_offset if tb_offset is None else tb_offset
540 540 assert isinstance(tb_offset, int)
541 541 Colors = self.Colors
542 542 out_list = []
543 543 if elist:
544 544
545 545 if tb_offset and len(elist) > tb_offset:
546 546 elist = elist[tb_offset:]
547 547
548 548 out_list.append('Traceback %s(most recent call last)%s:' %
549 549 (Colors.normalEm, Colors.Normal) + '\n')
550 550 out_list.extend(self._format_list(elist))
551 551 # The exception info should be a single entry in the list.
552 552 lines = ''.join(self._format_exception_only(etype, evalue))
553 553 out_list.append(lines)
554 554
555 555 exception = self.get_parts_of_chained_exception(evalue)
556 556
557 557 if exception and (id(exception[1]) not in chained_exc_ids):
558 558 chained_exception_message = (
559 559 self.prepare_chained_exception_message(evalue.__cause__)[0]
560 560 if evalue is not None
561 561 else ""
562 562 )
563 563 etype, evalue, etb = exception
564 564 # Trace exception to avoid infinite 'cause' loop
565 565 chained_exc_ids.add(id(exception[1]))
566 566 chained_exceptions_tb_offset = 0
567 567 out_list = (
568 568 self.structured_traceback(
569 569 etype,
570 570 evalue,
571 571 (etb, chained_exc_ids), # type: ignore
572 572 chained_exceptions_tb_offset,
573 573 context,
574 574 )
575 575 + chained_exception_message
576 576 + out_list)
577 577
578 578 return out_list
579 579
580 580 def _format_list(self, extracted_list):
581 581 """Format a list of traceback entry tuples for printing.
582 582
583 583 Given a list of tuples as returned by extract_tb() or
584 584 extract_stack(), return a list of strings ready for printing.
585 585 Each string in the resulting list corresponds to the item with the
586 586 same index in the argument list. Each string ends in a newline;
587 587 the strings may contain internal newlines as well, for those items
588 588 whose source text line is not None.
589 589
590 590 Lifted almost verbatim from traceback.py
591 591 """
592 592
593 593 Colors = self.Colors
594 594 output_list = []
595 595 for ind, (filename, lineno, name, line) in enumerate(extracted_list):
596 596 normalCol, nameCol, fileCol, lineCol = (
597 597 # Emphasize the last entry
598 598 (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line)
599 599 if ind == len(extracted_list) - 1
600 600 else (Colors.Normal, Colors.name, Colors.filename, "")
601 601 )
602 602
603 603 fns = _format_filename(filename, fileCol, normalCol, lineno=lineno)
604 604 item = f"{normalCol} {fns}"
605 605
606 606 if name != "<module>":
607 607 item += f" in {nameCol}{name}{normalCol}\n"
608 608 else:
609 609 item += "\n"
610 610 if line:
611 611 item += f"{lineCol} {line.strip()}{normalCol}\n"
612 612 output_list.append(item)
613 613
614 614 return output_list
615 615
616 616 def _format_exception_only(self, etype, value):
617 617 """Format the exception part of a traceback.
618 618
619 619 The arguments are the exception type and value such as given by
620 620 sys.exc_info()[:2]. The return value is a list of strings, each ending
621 621 in a newline. Normally, the list contains a single string; however,
622 622 for SyntaxError exceptions, it contains several lines that (when
623 623 printed) display detailed information about where the syntax error
624 624 occurred. The message indicating which exception occurred is the
625 625 always last string in the list.
626 626
627 627 Also lifted nearly verbatim from traceback.py
628 628 """
629 629 have_filedata = False
630 630 Colors = self.Colors
631 631 output_list = []
632 632 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
633 633 if value is None:
634 634 # Not sure if this can still happen in Python 2.6 and above
635 635 output_list.append(stype + "\n")
636 636 else:
637 637 if issubclass(etype, SyntaxError):
638 638 have_filedata = True
639 639 if not value.filename: value.filename = "<string>"
640 640 if value.lineno:
641 641 lineno = value.lineno
642 642 textline = linecache.getline(value.filename, value.lineno)
643 643 else:
644 644 lineno = "unknown"
645 645 textline = ""
646 646 output_list.append(
647 647 "%s %s%s\n"
648 648 % (
649 649 Colors.normalEm,
650 650 _format_filename(
651 651 value.filename,
652 652 Colors.filenameEm,
653 653 Colors.normalEm,
654 654 lineno=(None if lineno == "unknown" else lineno),
655 655 ),
656 656 Colors.Normal,
657 657 )
658 658 )
659 659 if textline == "":
660 660 textline = py3compat.cast_unicode(value.text, "utf-8")
661 661
662 662 if textline is not None:
663 663 i = 0
664 664 while i < len(textline) and textline[i].isspace():
665 665 i += 1
666 666 output_list.append(
667 667 "%s %s%s\n" % (Colors.line, textline.strip(), Colors.Normal)
668 668 )
669 669 if value.offset is not None:
670 670 s = ' '
671 671 for c in textline[i:value.offset - 1]:
672 672 if c.isspace():
673 673 s += c
674 674 else:
675 675 s += " "
676 676 output_list.append(
677 677 "%s%s^%s\n" % (Colors.caret, s, Colors.Normal)
678 678 )
679 679
680 680 try:
681 681 s = value.msg
682 682 except Exception:
683 683 s = self._some_str(value)
684 684 if s:
685 685 output_list.append(
686 686 "%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s)
687 687 )
688 688 else:
689 689 output_list.append("%s\n" % stype)
690 690
691 691 # PEP-678 notes
692 692 output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
693 693
694 694 # sync with user hooks
695 695 if have_filedata:
696 696 ipinst = get_ipython()
697 697 if ipinst is not None:
698 698 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
699 699
700 700 return output_list
701 701
702 702 def get_exception_only(self, etype, value):
703 703 """Only print the exception type and message, without a traceback.
704 704
705 705 Parameters
706 706 ----------
707 707 etype : exception type
708 708 value : exception value
709 709 """
710 710 return ListTB.structured_traceback(self, etype, value)
711 711
712 712 def show_exception_only(self, etype, evalue):
713 713 """Only print the exception type and message, without a traceback.
714 714
715 715 Parameters
716 716 ----------
717 717 etype : exception type
718 718 evalue : exception value
719 719 """
720 720 # This method needs to use __call__ from *this* class, not the one from
721 721 # a subclass whose signature or behavior may be different
722 722 ostream = self.ostream
723 723 ostream.flush()
724 724 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
725 725 ostream.flush()
726 726
727 727 def _some_str(self, value):
728 728 # Lifted from traceback.py
729 729 try:
730 730 return py3compat.cast_unicode(str(value))
731 731 except:
732 732 return u'<unprintable %s object>' % type(value).__name__
733 733
734 734
735 735 class FrameInfo:
736 736 """
737 737 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
738 738 really long frames.
739 739 """
740 740
741 741 description: Optional[str]
742 742 filename: Optional[str]
743 743 lineno: Tuple[int]
744 744 # number of context lines to use
745 745 context: Optional[int]
746 746
747 747 @classmethod
748 748 def _from_stack_data_FrameInfo(cls, frame_info):
749 749 return cls(
750 750 getattr(frame_info, "description", None),
751 751 getattr(frame_info, "filename", None), # type: ignore[arg-type]
752 752 getattr(frame_info, "lineno", None), # type: ignore[arg-type]
753 753 getattr(frame_info, "frame", None),
754 754 getattr(frame_info, "code", None),
755 755 sd=frame_info,
756 756 context=None,
757 757 )
758 758
759 759 def __init__(
760 760 self,
761 761 description: Optional[str],
762 762 filename: str,
763 763 lineno: Tuple[int],
764 764 frame,
765 765 code,
766 766 *,
767 767 sd=None,
768 768 context=None,
769 769 ):
770 770 self.description = description
771 771 self.filename = filename
772 772 self.lineno = lineno
773 773 self.frame = frame
774 774 self.code = code
775 775 self._sd = sd
776 776 self.context = context
777 777
778 778 # self.lines = []
779 779 if sd is None:
780 780 ix = inspect.getsourcelines(frame)
781 781 self.raw_lines = ix[0]
782 782
783 783 @property
784 784 def variables_in_executing_piece(self):
785 785 if self._sd:
786 786 return self._sd.variables_in_executing_piece
787 787 else:
788 788 return []
789 789
790 790 @property
791 791 def lines(self):
792 from executing.executing import NotOneValueFound
793
794 try:
792 795 return self._sd.lines
796 except NotOneValueFound:
797
798 class Dummy:
799 lineno = 0
800 is_current = False
801
802 def render(self, *, pygmented):
803 return "<Error retrieving source code with stack_data see ipython/ipython#13598>"
804
805 return [Dummy()]
793 806
794 807 @property
795 808 def executing(self):
796 809 if self._sd:
797 810 return self._sd.executing
798 811 else:
799 812 return None
800 813
801 814
802 815 # ----------------------------------------------------------------------------
803 816 class VerboseTB(TBTools):
804 817 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
805 818 of HTML. Requires inspect and pydoc. Crazy, man.
806 819
807 820 Modified version which optionally strips the topmost entries from the
808 821 traceback, to be used with alternate interpreters (because their own code
809 822 would appear in the traceback)."""
810 823
811 824 _tb_highlight = "bg:ansiyellow"
812 825 _tb_highlight_style = "default"
813 826
814 827 def __init__(
815 828 self,
816 829 color_scheme: str = "Linux",
817 830 call_pdb: bool = False,
818 831 ostream=None,
819 832 tb_offset: int = 0,
820 833 long_header: bool = False,
821 834 include_vars: bool = True,
822 835 check_cache=None,
823 836 debugger_cls=None,
824 837 parent=None,
825 838 config=None,
826 839 ):
827 840 """Specify traceback offset, headers and color scheme.
828 841
829 842 Define how many frames to drop from the tracebacks. Calling it with
830 843 tb_offset=1 allows use of this handler in interpreters which will have
831 844 their own code at the top of the traceback (VerboseTB will first
832 845 remove that frame before printing the traceback info)."""
833 846 TBTools.__init__(
834 847 self,
835 848 color_scheme=color_scheme,
836 849 call_pdb=call_pdb,
837 850 ostream=ostream,
838 851 parent=parent,
839 852 config=config,
840 853 debugger_cls=debugger_cls,
841 854 )
842 855 self.tb_offset = tb_offset
843 856 self.long_header = long_header
844 857 self.include_vars = include_vars
845 858 # By default we use linecache.checkcache, but the user can provide a
846 859 # different check_cache implementation. This was formerly used by the
847 860 # IPython kernel for interactive code, but is no longer necessary.
848 861 if check_cache is None:
849 862 check_cache = linecache.checkcache
850 863 self.check_cache = check_cache
851 864
852 865 self.skip_hidden = True
853 866
854 867 def format_record(self, frame_info: FrameInfo):
855 868 """Format a single stack frame"""
856 869 assert isinstance(frame_info, FrameInfo)
857 870 Colors = self.Colors # just a shorthand + quicker name lookup
858 871 ColorsNormal = Colors.Normal # used a lot
859 872
860 873 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
861 874 return ' %s[... skipping similar frames: %s]%s\n' % (
862 875 Colors.excName, frame_info.description, ColorsNormal)
863 876
864 877 indent = " " * INDENT_SIZE
865 878 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
866 879 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
867 880 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
868 881 Colors.vName,
869 882 Colors.valEm,
870 883 ColorsNormal,
871 884 )
872 885 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
873 886
874 887 link = _format_filename(
875 888 frame_info.filename,
876 889 Colors.filenameEm,
877 890 ColorsNormal,
878 891 lineno=frame_info.lineno,
879 892 )
880 893 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
881 894 if frame_info.executing is not None:
882 895 func = frame_info.executing.code_qualname()
883 896 else:
884 897 func = "?"
885 898 if func == "<module>":
886 899 call = ""
887 900 else:
888 901 # Decide whether to include variable details or not
889 902 var_repr = eqrepr if self.include_vars else nullrepr
890 903 try:
891 904 scope = inspect.formatargvalues(
892 905 args, varargs, varkw, locals_, formatvalue=var_repr
893 906 )
894 907 call = tpl_call.format(file=func, scope=scope)
895 908 except KeyError:
896 909 # This happens in situations like errors inside generator
897 910 # expressions, where local variables are listed in the
898 911 # line, but can't be extracted from the frame. I'm not
899 912 # 100% sure this isn't actually a bug in inspect itself,
900 913 # but since there's no info for us to compute with, the
901 914 # best we can do is report the failure and move on. Here
902 915 # we must *not* call any traceback construction again,
903 916 # because that would mess up use of %debug later on. So we
904 917 # simply report the failure and move on. The only
905 918 # limitation will be that this frame won't have locals
906 919 # listed in the call signature. Quite subtle problem...
907 920 # I can't think of a good way to validate this in a unit
908 921 # test, but running a script consisting of:
909 922 # dict( (k,v.strip()) for (k,v) in range(10) )
910 923 # will illustrate the error, if this exception catch is
911 924 # disabled.
912 925 call = tpl_call_fail % func
913 926
914 927 lvals = ''
915 928 lvals_list = []
916 929 if self.include_vars:
917 930 try:
918 931 # we likely want to fix stackdata at some point, but
919 932 # still need a workaround.
920 933 fibp = frame_info.variables_in_executing_piece
921 934 for var in fibp:
922 935 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
923 936 except Exception:
924 937 lvals_list.append(
925 938 "Exception trying to inspect frame. No more locals available."
926 939 )
927 940 if lvals_list:
928 941 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
929 942
930 943 result = f'{link}{", " if call else ""}{call}\n'
931 944 if frame_info._sd is None:
932 945 # fast fallback if file is too long
933 946 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
934 947 link = tpl_link % util_path.compress_user(frame_info.filename)
935 948 level = "%s %s\n" % (link, call)
936 949 _line_format = PyColorize.Parser(
937 950 style=self.color_scheme_table.active_scheme_name, parent=self
938 951 ).format2
939 952 first_line = frame_info.code.co_firstlineno
940 953 current_line = frame_info.lineno[0]
941 954 raw_lines = frame_info.raw_lines
942 955 index = current_line - first_line
943 956
944 957 if index >= frame_info.context:
945 958 start = max(index - frame_info.context, 0)
946 959 stop = index + frame_info.context
947 960 index = frame_info.context
948 961 else:
949 962 start = 0
950 963 stop = index + frame_info.context
951 964 raw_lines = raw_lines[start:stop]
952 965
953 966 return "%s%s" % (
954 967 level,
955 968 "".join(
956 969 _simple_format_traceback_lines(
957 970 current_line,
958 971 index,
959 972 raw_lines,
960 973 Colors,
961 974 lvals,
962 975 _line_format,
963 976 )
964 977 ),
965 978 )
966 979 # result += "\n".join(frame_info.raw_lines)
967 980 else:
968 981 result += "".join(
969 982 _format_traceback_lines(
970 983 frame_info.lines, Colors, self.has_colors, lvals
971 984 )
972 985 )
973 986 return result
974 987
975 988 def prepare_header(self, etype: str, long_version: bool = False):
976 989 colors = self.Colors # just a shorthand + quicker name lookup
977 990 colorsnormal = colors.Normal # used a lot
978 991 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
979 992 width = min(75, get_terminal_size()[0])
980 993 if long_version:
981 994 # Header with the exception type, python version, and date
982 995 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
983 996 date = time.ctime(time.time())
984 997
985 998 head = "%s%s%s\n%s%s%s\n%s" % (
986 999 colors.topline,
987 1000 "-" * width,
988 1001 colorsnormal,
989 1002 exc,
990 1003 " " * (width - len(etype) - len(pyver)),
991 1004 pyver,
992 1005 date.rjust(width),
993 1006 )
994 1007 head += (
995 1008 "\nA problem occurred executing Python code. Here is the sequence of function"
996 1009 "\ncalls leading up to the error, with the most recent (innermost) call last."
997 1010 )
998 1011 else:
999 1012 # Simplified header
1000 1013 head = "%s%s" % (
1001 1014 exc,
1002 1015 "Traceback (most recent call last)".rjust(width - len(etype)),
1003 1016 )
1004 1017
1005 1018 return head
1006 1019
1007 1020 def format_exception(self, etype, evalue):
1008 1021 colors = self.Colors # just a shorthand + quicker name lookup
1009 1022 colorsnormal = colors.Normal # used a lot
1010 1023 # Get (safely) a string form of the exception info
1011 1024 try:
1012 1025 etype_str, evalue_str = map(str, (etype, evalue))
1013 1026 except:
1014 1027 # User exception is improperly defined.
1015 1028 etype, evalue = str, sys.exc_info()[:2]
1016 1029 etype_str, evalue_str = map(str, (etype, evalue))
1017 1030
1018 1031 # PEP-678 notes
1019 1032 notes = getattr(evalue, "__notes__", [])
1020 1033 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
1021 1034 notes = [_safe_string(notes, "__notes__", func=repr)]
1022 1035
1023 1036 # ... and format it
1024 1037 return [
1025 1038 "{}{}{}: {}".format(
1026 1039 colors.excName,
1027 1040 etype_str,
1028 1041 colorsnormal,
1029 1042 py3compat.cast_unicode(evalue_str),
1030 1043 ),
1031 1044 *(
1032 1045 "{}{}".format(
1033 1046 colorsnormal, _safe_string(py3compat.cast_unicode(n), "note")
1034 1047 )
1035 1048 for n in notes
1036 1049 ),
1037 1050 ]
1038 1051
1039 1052 def format_exception_as_a_whole(
1040 1053 self,
1041 1054 etype: type,
1042 1055 evalue: Optional[BaseException],
1043 1056 etb: Optional[TracebackType],
1044 1057 number_of_lines_of_context,
1045 1058 tb_offset: Optional[int],
1046 1059 ):
1047 1060 """Formats the header, traceback and exception message for a single exception.
1048 1061
1049 1062 This may be called multiple times by Python 3 exception chaining
1050 1063 (PEP 3134).
1051 1064 """
1052 1065 # some locals
1053 1066 orig_etype = etype
1054 1067 try:
1055 1068 etype = etype.__name__ # type: ignore
1056 1069 except AttributeError:
1057 1070 pass
1058 1071
1059 1072 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1060 1073 assert isinstance(tb_offset, int)
1061 1074 head = self.prepare_header(str(etype), self.long_header)
1062 1075 records = (
1063 1076 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
1064 1077 )
1065 1078
1066 1079 frames = []
1067 1080 skipped = 0
1068 1081 lastrecord = len(records) - 1
1069 1082 for i, record in enumerate(records):
1070 1083 if (
1071 1084 not isinstance(record._sd, stack_data.RepeatedFrames)
1072 1085 and self.skip_hidden
1073 1086 ):
1074 1087 if (
1075 1088 record.frame.f_locals.get("__tracebackhide__", 0)
1076 1089 and i != lastrecord
1077 1090 ):
1078 1091 skipped += 1
1079 1092 continue
1080 1093 if skipped:
1081 1094 Colors = self.Colors # just a shorthand + quicker name lookup
1082 1095 ColorsNormal = Colors.Normal # used a lot
1083 1096 frames.append(
1084 1097 " %s[... skipping hidden %s frame]%s\n"
1085 1098 % (Colors.excName, skipped, ColorsNormal)
1086 1099 )
1087 1100 skipped = 0
1088 1101 frames.append(self.format_record(record))
1089 1102 if skipped:
1090 1103 Colors = self.Colors # just a shorthand + quicker name lookup
1091 1104 ColorsNormal = Colors.Normal # used a lot
1092 1105 frames.append(
1093 1106 " %s[... skipping hidden %s frame]%s\n"
1094 1107 % (Colors.excName, skipped, ColorsNormal)
1095 1108 )
1096 1109
1097 1110 formatted_exception = self.format_exception(etype, evalue)
1098 1111 if records:
1099 1112 frame_info = records[-1]
1100 1113 ipinst = get_ipython()
1101 1114 if ipinst is not None:
1102 1115 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
1103 1116
1104 1117 return [[head] + frames + formatted_exception]
1105 1118
1106 1119 def get_records(
1107 1120 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
1108 1121 ):
1109 1122 assert etb is not None
1110 1123 context = number_of_lines_of_context - 1
1111 1124 after = context // 2
1112 1125 before = context - after
1113 1126 if self.has_colors:
1114 1127 style = get_style_by_name(self._tb_highlight_style)
1115 1128 style = stack_data.style_with_executing_node(style, self._tb_highlight)
1116 1129 formatter = Terminal256Formatter(style=style)
1117 1130 else:
1118 1131 formatter = None
1119 1132 options = stack_data.Options(
1120 1133 before=before,
1121 1134 after=after,
1122 1135 pygments_formatter=formatter,
1123 1136 )
1124 1137
1125 1138 # Let's estimate the amount of code we will have to parse/highlight.
1126 1139 cf: Optional[TracebackType] = etb
1127 1140 max_len = 0
1128 1141 tbs = []
1129 1142 while cf is not None:
1130 1143 try:
1131 1144 mod = inspect.getmodule(cf.tb_frame)
1132 1145 if mod is not None:
1133 1146 mod_name = mod.__name__
1134 1147 root_name, *_ = mod_name.split(".")
1135 1148 if root_name == "IPython":
1136 1149 cf = cf.tb_next
1137 1150 continue
1138 1151 max_len = get_line_number_of_frame(cf.tb_frame)
1139 1152
1140 1153 except OSError:
1141 1154 max_len = 0
1142 1155 max_len = max(max_len, max_len)
1143 1156 tbs.append(cf)
1144 1157 cf = getattr(cf, "tb_next", None)
1145 1158
1146 1159 if max_len > FAST_THRESHOLD:
1147 1160 FIs = []
1148 1161 for tb in tbs:
1149 1162 frame = tb.tb_frame # type: ignore
1150 1163 lineno = (frame.f_lineno,)
1151 1164 code = frame.f_code
1152 1165 filename = code.co_filename
1153 1166 # TODO: Here we need to use before/after/
1154 1167 FIs.append(
1155 1168 FrameInfo(
1156 1169 "Raw frame", filename, lineno, frame, code, context=context
1157 1170 )
1158 1171 )
1159 1172 return FIs
1160 1173 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1161 1174 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1162 1175 return res
1163 1176
1164 1177 def structured_traceback(
1165 1178 self,
1166 1179 etype: type,
1167 1180 evalue: Optional[BaseException],
1168 1181 etb: Optional[TracebackType] = None,
1169 1182 tb_offset: Optional[int] = None,
1170 1183 number_of_lines_of_context: int = 5,
1171 1184 ):
1172 1185 """Return a nice text document describing the traceback."""
1173 1186 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1174 1187 tb_offset)
1175 1188
1176 1189 colors = self.Colors # just a shorthand + quicker name lookup
1177 1190 colorsnormal = colors.Normal # used a lot
1178 1191 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1179 1192 structured_traceback_parts = [head]
1180 1193 chained_exceptions_tb_offset = 0
1181 1194 lines_of_context = 3
1182 1195 formatted_exceptions = formatted_exception
1183 1196 exception = self.get_parts_of_chained_exception(evalue)
1184 1197 if exception:
1185 1198 assert evalue is not None
1186 1199 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1187 1200 etype, evalue, etb = exception
1188 1201 else:
1189 1202 evalue = None
1190 1203 chained_exc_ids = set()
1191 1204 while evalue:
1192 1205 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1193 1206 chained_exceptions_tb_offset)
1194 1207 exception = self.get_parts_of_chained_exception(evalue)
1195 1208
1196 1209 if exception and not id(exception[1]) in chained_exc_ids:
1197 1210 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1198 1211 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1199 1212 etype, evalue, etb = exception
1200 1213 else:
1201 1214 evalue = None
1202 1215
1203 1216 # we want to see exceptions in a reversed order:
1204 1217 # the first exception should be on top
1205 1218 for formatted_exception in reversed(formatted_exceptions):
1206 1219 structured_traceback_parts += formatted_exception
1207 1220
1208 1221 return structured_traceback_parts
1209 1222
1210 1223 def debugger(self, force: bool = False):
1211 1224 """Call up the pdb debugger if desired, always clean up the tb
1212 1225 reference.
1213 1226
1214 1227 Keywords:
1215 1228
1216 1229 - force(False): by default, this routine checks the instance call_pdb
1217 1230 flag and does not actually invoke the debugger if the flag is false.
1218 1231 The 'force' option forces the debugger to activate even if the flag
1219 1232 is false.
1220 1233
1221 1234 If the call_pdb flag is set, the pdb interactive debugger is
1222 1235 invoked. In all cases, the self.tb reference to the current traceback
1223 1236 is deleted to prevent lingering references which hamper memory
1224 1237 management.
1225 1238
1226 1239 Note that each call to pdb() does an 'import readline', so if your app
1227 1240 requires a special setup for the readline completers, you'll have to
1228 1241 fix that by hand after invoking the exception handler."""
1229 1242
1230 1243 if force or self.call_pdb:
1231 1244 if self.pdb is None:
1232 1245 self.pdb = self.debugger_cls()
1233 1246 # the system displayhook may have changed, restore the original
1234 1247 # for pdb
1235 1248 display_trap = DisplayTrap(hook=sys.__displayhook__)
1236 1249 with display_trap:
1237 1250 self.pdb.reset()
1238 1251 # Find the right frame so we don't pop up inside ipython itself
1239 1252 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
1240 1253 etb = self.tb # type: ignore[has-type]
1241 1254 else:
1242 1255 etb = self.tb = sys.last_traceback
1243 1256 while self.tb is not None and self.tb.tb_next is not None:
1244 1257 assert self.tb.tb_next is not None
1245 1258 self.tb = self.tb.tb_next
1246 1259 if etb and etb.tb_next:
1247 1260 etb = etb.tb_next
1248 1261 self.pdb.botframe = etb.tb_frame
1249 1262 # last_value should be deprecated, but last-exc sometimme not set
1250 1263 # please check why later and remove the getattr.
1251 1264 exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value) # type: ignore[attr-defined]
1252 1265 if exc:
1253 1266 self.pdb.interaction(None, exc)
1254 1267 else:
1255 1268 self.pdb.interaction(None, etb)
1256 1269
1257 1270 if hasattr(self, 'tb'):
1258 1271 del self.tb
1259 1272
1260 1273 def handler(self, info=None):
1261 1274 (etype, evalue, etb) = info or sys.exc_info()
1262 1275 self.tb = etb
1263 1276 ostream = self.ostream
1264 1277 ostream.flush()
1265 1278 ostream.write(self.text(etype, evalue, etb))
1266 1279 ostream.write('\n')
1267 1280 ostream.flush()
1268 1281
1269 1282 # Changed so an instance can just be called as VerboseTB_inst() and print
1270 1283 # out the right info on its own.
1271 1284 def __call__(self, etype=None, evalue=None, etb=None):
1272 1285 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1273 1286 if etb is None:
1274 1287 self.handler()
1275 1288 else:
1276 1289 self.handler((etype, evalue, etb))
1277 1290 try:
1278 1291 self.debugger()
1279 1292 except KeyboardInterrupt:
1280 1293 print("\nKeyboardInterrupt")
1281 1294
1282 1295
1283 1296 #----------------------------------------------------------------------------
1284 1297 class FormattedTB(VerboseTB, ListTB):
1285 1298 """Subclass ListTB but allow calling with a traceback.
1286 1299
1287 1300 It can thus be used as a sys.excepthook for Python > 2.1.
1288 1301
1289 1302 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1290 1303
1291 1304 Allows a tb_offset to be specified. This is useful for situations where
1292 1305 one needs to remove a number of topmost frames from the traceback (such as
1293 1306 occurs with python programs that themselves execute other python code,
1294 1307 like Python shells). """
1295 1308
1296 1309 mode: str
1297 1310
1298 1311 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1299 1312 ostream=None,
1300 1313 tb_offset=0, long_header=False, include_vars=False,
1301 1314 check_cache=None, debugger_cls=None,
1302 1315 parent=None, config=None):
1303 1316
1304 1317 # NEVER change the order of this list. Put new modes at the end:
1305 1318 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1306 1319 self.verbose_modes = self.valid_modes[1:3]
1307 1320
1308 1321 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1309 1322 ostream=ostream, tb_offset=tb_offset,
1310 1323 long_header=long_header, include_vars=include_vars,
1311 1324 check_cache=check_cache, debugger_cls=debugger_cls,
1312 1325 parent=parent, config=config)
1313 1326
1314 1327 # Different types of tracebacks are joined with different separators to
1315 1328 # form a single string. They are taken from this dict
1316 1329 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1317 1330 Minimal='')
1318 1331 # set_mode also sets the tb_join_char attribute
1319 1332 self.set_mode(mode)
1320 1333
1321 1334 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1322 1335 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1323 1336 mode = self.mode
1324 1337 if mode in self.verbose_modes:
1325 1338 # Verbose modes need a full traceback
1326 1339 return VerboseTB.structured_traceback(
1327 1340 self, etype, value, tb, tb_offset, number_of_lines_of_context
1328 1341 )
1329 1342 elif mode == 'Minimal':
1330 1343 return ListTB.get_exception_only(self, etype, value)
1331 1344 else:
1332 1345 # We must check the source cache because otherwise we can print
1333 1346 # out-of-date source code.
1334 1347 self.check_cache()
1335 1348 # Now we can extract and format the exception
1336 1349 return ListTB.structured_traceback(
1337 1350 self, etype, value, tb, tb_offset, number_of_lines_of_context
1338 1351 )
1339 1352
1340 1353 def stb2text(self, stb):
1341 1354 """Convert a structured traceback (a list) to a string."""
1342 1355 return self.tb_join_char.join(stb)
1343 1356
1344 1357 def set_mode(self, mode: Optional[str] = None):
1345 1358 """Switch to the desired mode.
1346 1359
1347 1360 If mode is not specified, cycles through the available modes."""
1348 1361
1349 1362 if not mode:
1350 1363 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1351 1364 len(self.valid_modes)
1352 1365 self.mode = self.valid_modes[new_idx]
1353 1366 elif mode not in self.valid_modes:
1354 1367 raise ValueError(
1355 1368 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1356 1369 "Valid modes: " + str(self.valid_modes)
1357 1370 )
1358 1371 else:
1359 1372 assert isinstance(mode, str)
1360 1373 self.mode = mode
1361 1374 # include variable details only in 'Verbose' mode
1362 1375 self.include_vars = (self.mode == self.valid_modes[2])
1363 1376 # Set the join character for generating text tracebacks
1364 1377 self.tb_join_char = self._join_chars[self.mode]
1365 1378
1366 1379 # some convenient shortcuts
1367 1380 def plain(self):
1368 1381 self.set_mode(self.valid_modes[0])
1369 1382
1370 1383 def context(self):
1371 1384 self.set_mode(self.valid_modes[1])
1372 1385
1373 1386 def verbose(self):
1374 1387 self.set_mode(self.valid_modes[2])
1375 1388
1376 1389 def minimal(self):
1377 1390 self.set_mode(self.valid_modes[3])
1378 1391
1379 1392
1380 1393 #----------------------------------------------------------------------------
1381 1394 class AutoFormattedTB(FormattedTB):
1382 1395 """A traceback printer which can be called on the fly.
1383 1396
1384 1397 It will find out about exceptions by itself.
1385 1398
1386 1399 A brief example::
1387 1400
1388 1401 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1389 1402 try:
1390 1403 ...
1391 1404 except:
1392 1405 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1393 1406 """
1394 1407
1395 1408 def __call__(self, etype=None, evalue=None, etb=None,
1396 1409 out=None, tb_offset=None):
1397 1410 """Print out a formatted exception traceback.
1398 1411
1399 1412 Optional arguments:
1400 1413 - out: an open file-like object to direct output to.
1401 1414
1402 1415 - tb_offset: the number of frames to skip over in the stack, on a
1403 1416 per-call basis (this overrides temporarily the instance's tb_offset
1404 1417 given at initialization time."""
1405 1418
1406 1419 if out is None:
1407 1420 out = self.ostream
1408 1421 out.flush()
1409 1422 out.write(self.text(etype, evalue, etb, tb_offset))
1410 1423 out.write('\n')
1411 1424 out.flush()
1412 1425 # FIXME: we should remove the auto pdb behavior from here and leave
1413 1426 # that to the clients.
1414 1427 try:
1415 1428 self.debugger()
1416 1429 except KeyboardInterrupt:
1417 1430 print("\nKeyboardInterrupt")
1418 1431
1419 1432 def structured_traceback(
1420 1433 self,
1421 1434 etype: type,
1422 1435 evalue: Optional[BaseException],
1423 1436 etb: Optional[TracebackType] = None,
1424 1437 tb_offset: Optional[int] = None,
1425 1438 number_of_lines_of_context: int = 5,
1426 1439 ):
1427 1440 # tb: TracebackType or tupleof tb types ?
1428 1441 if etype is None:
1429 1442 etype, evalue, etb = sys.exc_info()
1430 1443 if isinstance(etb, tuple):
1431 1444 # tb is a tuple if this is a chained exception.
1432 1445 self.tb = etb[0]
1433 1446 else:
1434 1447 self.tb = etb
1435 1448 return FormattedTB.structured_traceback(
1436 1449 self, etype, evalue, etb, tb_offset, number_of_lines_of_context
1437 1450 )
1438 1451
1439 1452
1440 1453 #---------------------------------------------------------------------------
1441 1454
1442 1455 # A simple class to preserve Nathan's original functionality.
1443 1456 class ColorTB(FormattedTB):
1444 1457 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1445 1458
1446 1459 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1447 1460 FormattedTB.__init__(self, color_scheme=color_scheme,
1448 1461 call_pdb=call_pdb, **kwargs)
1449 1462
1450 1463
1451 1464 class SyntaxTB(ListTB):
1452 1465 """Extension which holds some state: the last exception value"""
1453 1466
1454 1467 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1455 1468 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1456 1469 self.last_syntax_error = None
1457 1470
1458 1471 def __call__(self, etype, value, elist):
1459 1472 self.last_syntax_error = value
1460 1473
1461 1474 ListTB.__call__(self, etype, value, elist)
1462 1475
1463 1476 def structured_traceback(self, etype, value, elist, tb_offset=None,
1464 1477 context=5):
1465 1478 # If the source file has been edited, the line in the syntax error can
1466 1479 # be wrong (retrieved from an outdated cache). This replaces it with
1467 1480 # the current value.
1468 1481 if isinstance(value, SyntaxError) \
1469 1482 and isinstance(value.filename, str) \
1470 1483 and isinstance(value.lineno, int):
1471 1484 linecache.checkcache(value.filename)
1472 1485 newtext = linecache.getline(value.filename, value.lineno)
1473 1486 if newtext:
1474 1487 value.text = newtext
1475 1488 self.last_syntax_error = value
1476 1489 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1477 1490 tb_offset=tb_offset, context=context)
1478 1491
1479 1492 def clear_err_state(self):
1480 1493 """Return the current error state and clear it"""
1481 1494 e = self.last_syntax_error
1482 1495 self.last_syntax_error = None
1483 1496 return e
1484 1497
1485 1498 def stb2text(self, stb):
1486 1499 """Convert a structured traceback (a list) to a string."""
1487 1500 return ''.join(stb)
1488 1501
1489 1502
1490 1503 # some internal-use functions
1491 1504 def text_repr(value):
1492 1505 """Hopefully pretty robust repr equivalent."""
1493 1506 # this is pretty horrible but should always return *something*
1494 1507 try:
1495 1508 return pydoc.text.repr(value) # type: ignore[call-arg]
1496 1509 except KeyboardInterrupt:
1497 1510 raise
1498 1511 except:
1499 1512 try:
1500 1513 return repr(value)
1501 1514 except KeyboardInterrupt:
1502 1515 raise
1503 1516 except:
1504 1517 try:
1505 1518 # all still in an except block so we catch
1506 1519 # getattr raising
1507 1520 name = getattr(value, '__name__', None)
1508 1521 if name:
1509 1522 # ick, recursion
1510 1523 return text_repr(name)
1511 1524 klass = getattr(value, '__class__', None)
1512 1525 if klass:
1513 1526 return '%s instance' % text_repr(klass)
1514 1527 except KeyboardInterrupt:
1515 1528 raise
1516 1529 except:
1517 1530 return 'UNRECOVERABLE REPR FAILURE'
1518 1531
1519 1532
1520 1533 def eqrepr(value, repr=text_repr):
1521 1534 return '=%s' % repr(value)
1522 1535
1523 1536
1524 1537 def nullrepr(value, repr=text_repr):
1525 1538 return ''
General Comments 0
You need to be logged in to leave comments. Login now