##// END OF EJS Templates
[PR]: Make values public (_tb_highlight & _tb_highlight_style) (#14518)...
M Bussonnier -
r28868:ea2fce1f merge
parent child Browse files
Show More
@@ -1,1547 +1,1547
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 # Find chained exceptions if we have a traceback (not for exception-only mode)
556 556 if etb is not None:
557 557 exception = self.get_parts_of_chained_exception(evalue)
558 558
559 559 if exception and (id(exception[1]) not in chained_exc_ids):
560 560 chained_exception_message = (
561 561 self.prepare_chained_exception_message(evalue.__cause__)[0]
562 562 if evalue is not None
563 563 else ""
564 564 )
565 565 etype, evalue, etb = exception
566 566 # Trace exception to avoid infinite 'cause' loop
567 567 chained_exc_ids.add(id(exception[1]))
568 568 chained_exceptions_tb_offset = 0
569 569 out_list = (
570 570 self.structured_traceback(
571 571 etype,
572 572 evalue,
573 573 (etb, chained_exc_ids), # type: ignore
574 574 chained_exceptions_tb_offset,
575 575 context,
576 576 )
577 577 + chained_exception_message
578 578 + out_list
579 579 )
580 580
581 581 return out_list
582 582
583 583 def _format_list(self, extracted_list):
584 584 """Format a list of traceback entry tuples for printing.
585 585
586 586 Given a list of tuples as returned by extract_tb() or
587 587 extract_stack(), return a list of strings ready for printing.
588 588 Each string in the resulting list corresponds to the item with the
589 589 same index in the argument list. Each string ends in a newline;
590 590 the strings may contain internal newlines as well, for those items
591 591 whose source text line is not None.
592 592
593 593 Lifted almost verbatim from traceback.py
594 594 """
595 595
596 596 Colors = self.Colors
597 597 output_list = []
598 598 for ind, (filename, lineno, name, line) in enumerate(extracted_list):
599 599 normalCol, nameCol, fileCol, lineCol = (
600 600 # Emphasize the last entry
601 601 (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line)
602 602 if ind == len(extracted_list) - 1
603 603 else (Colors.Normal, Colors.name, Colors.filename, "")
604 604 )
605 605
606 606 fns = _format_filename(filename, fileCol, normalCol, lineno=lineno)
607 607 item = f"{normalCol} {fns}"
608 608
609 609 if name != "<module>":
610 610 item += f" in {nameCol}{name}{normalCol}\n"
611 611 else:
612 612 item += "\n"
613 613 if line:
614 614 item += f"{lineCol} {line.strip()}{normalCol}\n"
615 615 output_list.append(item)
616 616
617 617 return output_list
618 618
619 619 def _format_exception_only(self, etype, value):
620 620 """Format the exception part of a traceback.
621 621
622 622 The arguments are the exception type and value such as given by
623 623 sys.exc_info()[:2]. The return value is a list of strings, each ending
624 624 in a newline. Normally, the list contains a single string; however,
625 625 for SyntaxError exceptions, it contains several lines that (when
626 626 printed) display detailed information about where the syntax error
627 627 occurred. The message indicating which exception occurred is the
628 628 always last string in the list.
629 629
630 630 Also lifted nearly verbatim from traceback.py
631 631 """
632 632 have_filedata = False
633 633 Colors = self.Colors
634 634 output_list = []
635 635 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
636 636 if value is None:
637 637 # Not sure if this can still happen in Python 2.6 and above
638 638 output_list.append(stype + "\n")
639 639 else:
640 640 if issubclass(etype, SyntaxError):
641 641 have_filedata = True
642 642 if not value.filename: value.filename = "<string>"
643 643 if value.lineno:
644 644 lineno = value.lineno
645 645 textline = linecache.getline(value.filename, value.lineno)
646 646 else:
647 647 lineno = "unknown"
648 648 textline = ""
649 649 output_list.append(
650 650 "%s %s%s\n"
651 651 % (
652 652 Colors.normalEm,
653 653 _format_filename(
654 654 value.filename,
655 655 Colors.filenameEm,
656 656 Colors.normalEm,
657 657 lineno=(None if lineno == "unknown" else lineno),
658 658 ),
659 659 Colors.Normal,
660 660 )
661 661 )
662 662 if textline == "":
663 663 textline = py3compat.cast_unicode(value.text, "utf-8")
664 664
665 665 if textline is not None:
666 666 i = 0
667 667 while i < len(textline) and textline[i].isspace():
668 668 i += 1
669 669 output_list.append(
670 670 "%s %s%s\n" % (Colors.line, textline.strip(), Colors.Normal)
671 671 )
672 672 if value.offset is not None:
673 673 s = ' '
674 674 for c in textline[i:value.offset - 1]:
675 675 if c.isspace():
676 676 s += c
677 677 else:
678 678 s += " "
679 679 output_list.append(
680 680 "%s%s^%s\n" % (Colors.caret, s, Colors.Normal)
681 681 )
682 682
683 683 try:
684 684 s = value.msg
685 685 except Exception:
686 686 s = self._some_str(value)
687 687 if s:
688 688 output_list.append(
689 689 "%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s)
690 690 )
691 691 else:
692 692 output_list.append("%s\n" % stype)
693 693
694 694 # PEP-678 notes
695 695 output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
696 696
697 697 # sync with user hooks
698 698 if have_filedata:
699 699 ipinst = get_ipython()
700 700 if ipinst is not None:
701 701 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
702 702
703 703 return output_list
704 704
705 705 def get_exception_only(self, etype, value):
706 706 """Only print the exception type and message, without a traceback.
707 707
708 708 Parameters
709 709 ----------
710 710 etype : exception type
711 711 value : exception value
712 712 """
713 713 return ListTB.structured_traceback(self, etype, value)
714 714
715 715 def show_exception_only(self, etype, evalue):
716 716 """Only print the exception type and message, without a traceback.
717 717
718 718 Parameters
719 719 ----------
720 720 etype : exception type
721 721 evalue : exception value
722 722 """
723 723 # This method needs to use __call__ from *this* class, not the one from
724 724 # a subclass whose signature or behavior may be different
725 725 ostream = self.ostream
726 726 ostream.flush()
727 727 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
728 728 ostream.flush()
729 729
730 730 def _some_str(self, value):
731 731 # Lifted from traceback.py
732 732 try:
733 733 return py3compat.cast_unicode(str(value))
734 734 except:
735 735 return u'<unprintable %s object>' % type(value).__name__
736 736
737 737
738 738 class FrameInfo:
739 739 """
740 740 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
741 741 really long frames.
742 742 """
743 743
744 744 description: Optional[str]
745 745 filename: Optional[str]
746 746 lineno: Tuple[int]
747 747 # number of context lines to use
748 748 context: Optional[int]
749 749 raw_lines: List[str]
750 750
751 751 @classmethod
752 752 def _from_stack_data_FrameInfo(cls, frame_info):
753 753 return cls(
754 754 getattr(frame_info, "description", None),
755 755 getattr(frame_info, "filename", None), # type: ignore[arg-type]
756 756 getattr(frame_info, "lineno", None), # type: ignore[arg-type]
757 757 getattr(frame_info, "frame", None),
758 758 getattr(frame_info, "code", None),
759 759 sd=frame_info,
760 760 context=None,
761 761 )
762 762
763 763 def __init__(
764 764 self,
765 765 description: Optional[str],
766 766 filename: str,
767 767 lineno: Tuple[int],
768 768 frame,
769 769 code,
770 770 *,
771 771 sd=None,
772 772 context=None,
773 773 ):
774 774 self.description = description
775 775 self.filename = filename
776 776 self.lineno = lineno
777 777 self.frame = frame
778 778 self.code = code
779 779 self._sd = sd
780 780 self.context = context
781 781
782 782 # self.lines = []
783 783 if sd is None:
784 784 try:
785 785 # return a list of source lines and a starting line number
786 786 self.raw_lines = inspect.getsourcelines(frame)[0]
787 787 except OSError:
788 788 self.raw_lines = [
789 789 "'Could not get source, probably due dynamically evaluated source code.'"
790 790 ]
791 791
792 792 @property
793 793 def variables_in_executing_piece(self):
794 794 if self._sd:
795 795 return self._sd.variables_in_executing_piece
796 796 else:
797 797 return []
798 798
799 799 @property
800 800 def lines(self):
801 801 from executing.executing import NotOneValueFound
802 802
803 803 try:
804 804 return self._sd.lines
805 805 except NotOneValueFound:
806 806
807 807 class Dummy:
808 808 lineno = 0
809 809 is_current = False
810 810
811 811 def render(self, *, pygmented):
812 812 return "<Error retrieving source code with stack_data see ipython/ipython#13598>"
813 813
814 814 return [Dummy()]
815 815
816 816 @property
817 817 def executing(self):
818 818 if self._sd:
819 819 return self._sd.executing
820 820 else:
821 821 return None
822 822
823 823
824 824 # ----------------------------------------------------------------------------
825 825 class VerboseTB(TBTools):
826 826 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
827 827 of HTML. Requires inspect and pydoc. Crazy, man.
828 828
829 829 Modified version which optionally strips the topmost entries from the
830 830 traceback, to be used with alternate interpreters (because their own code
831 831 would appear in the traceback)."""
832 832
833 _tb_highlight = "bg:ansiyellow"
834 _tb_highlight_style = "default"
833 tb_highlight = "bg:ansiyellow"
834 tb_highlight_style = "default"
835 835
836 836 def __init__(
837 837 self,
838 838 color_scheme: str = "Linux",
839 839 call_pdb: bool = False,
840 840 ostream=None,
841 841 tb_offset: int = 0,
842 842 long_header: bool = False,
843 843 include_vars: bool = True,
844 844 check_cache=None,
845 845 debugger_cls=None,
846 846 parent=None,
847 847 config=None,
848 848 ):
849 849 """Specify traceback offset, headers and color scheme.
850 850
851 851 Define how many frames to drop from the tracebacks. Calling it with
852 852 tb_offset=1 allows use of this handler in interpreters which will have
853 853 their own code at the top of the traceback (VerboseTB will first
854 854 remove that frame before printing the traceback info)."""
855 855 TBTools.__init__(
856 856 self,
857 857 color_scheme=color_scheme,
858 858 call_pdb=call_pdb,
859 859 ostream=ostream,
860 860 parent=parent,
861 861 config=config,
862 862 debugger_cls=debugger_cls,
863 863 )
864 864 self.tb_offset = tb_offset
865 865 self.long_header = long_header
866 866 self.include_vars = include_vars
867 867 # By default we use linecache.checkcache, but the user can provide a
868 868 # different check_cache implementation. This was formerly used by the
869 869 # IPython kernel for interactive code, but is no longer necessary.
870 870 if check_cache is None:
871 871 check_cache = linecache.checkcache
872 872 self.check_cache = check_cache
873 873
874 874 self.skip_hidden = True
875 875
876 876 def format_record(self, frame_info: FrameInfo):
877 877 """Format a single stack frame"""
878 878 assert isinstance(frame_info, FrameInfo)
879 879 Colors = self.Colors # just a shorthand + quicker name lookup
880 880 ColorsNormal = Colors.Normal # used a lot
881 881
882 882 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
883 883 return ' %s[... skipping similar frames: %s]%s\n' % (
884 884 Colors.excName, frame_info.description, ColorsNormal)
885 885
886 886 indent = " " * INDENT_SIZE
887 887 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
888 888 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
889 889 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
890 890 Colors.vName,
891 891 Colors.valEm,
892 892 ColorsNormal,
893 893 )
894 894 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
895 895
896 896 link = _format_filename(
897 897 frame_info.filename,
898 898 Colors.filenameEm,
899 899 ColorsNormal,
900 900 lineno=frame_info.lineno,
901 901 )
902 902 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
903 903 if frame_info.executing is not None:
904 904 func = frame_info.executing.code_qualname()
905 905 else:
906 906 func = "?"
907 907 if func == "<module>":
908 908 call = ""
909 909 else:
910 910 # Decide whether to include variable details or not
911 911 var_repr = eqrepr if self.include_vars else nullrepr
912 912 try:
913 913 scope = inspect.formatargvalues(
914 914 args, varargs, varkw, locals_, formatvalue=var_repr
915 915 )
916 916 call = tpl_call.format(file=func, scope=scope)
917 917 except KeyError:
918 918 # This happens in situations like errors inside generator
919 919 # expressions, where local variables are listed in the
920 920 # line, but can't be extracted from the frame. I'm not
921 921 # 100% sure this isn't actually a bug in inspect itself,
922 922 # but since there's no info for us to compute with, the
923 923 # best we can do is report the failure and move on. Here
924 924 # we must *not* call any traceback construction again,
925 925 # because that would mess up use of %debug later on. So we
926 926 # simply report the failure and move on. The only
927 927 # limitation will be that this frame won't have locals
928 928 # listed in the call signature. Quite subtle problem...
929 929 # I can't think of a good way to validate this in a unit
930 930 # test, but running a script consisting of:
931 931 # dict( (k,v.strip()) for (k,v) in range(10) )
932 932 # will illustrate the error, if this exception catch is
933 933 # disabled.
934 934 call = tpl_call_fail % func
935 935
936 936 lvals = ''
937 937 lvals_list = []
938 938 if self.include_vars:
939 939 try:
940 940 # we likely want to fix stackdata at some point, but
941 941 # still need a workaround.
942 942 fibp = frame_info.variables_in_executing_piece
943 943 for var in fibp:
944 944 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
945 945 except Exception:
946 946 lvals_list.append(
947 947 "Exception trying to inspect frame. No more locals available."
948 948 )
949 949 if lvals_list:
950 950 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
951 951
952 952 result = f'{link}{", " if call else ""}{call}\n'
953 953 if frame_info._sd is None:
954 954 # fast fallback if file is too long
955 955 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
956 956 link = tpl_link % util_path.compress_user(frame_info.filename)
957 957 level = "%s %s\n" % (link, call)
958 958 _line_format = PyColorize.Parser(
959 959 style=self.color_scheme_table.active_scheme_name, parent=self
960 960 ).format2
961 961 first_line = frame_info.code.co_firstlineno
962 962 current_line = frame_info.lineno[0]
963 963 raw_lines = frame_info.raw_lines
964 964 index = current_line - first_line
965 965
966 966 if index >= frame_info.context:
967 967 start = max(index - frame_info.context, 0)
968 968 stop = index + frame_info.context
969 969 index = frame_info.context
970 970 else:
971 971 start = 0
972 972 stop = index + frame_info.context
973 973 raw_lines = raw_lines[start:stop]
974 974
975 975 return "%s%s" % (
976 976 level,
977 977 "".join(
978 978 _simple_format_traceback_lines(
979 979 current_line,
980 980 index,
981 981 raw_lines,
982 982 Colors,
983 983 lvals,
984 984 _line_format,
985 985 )
986 986 ),
987 987 )
988 988 # result += "\n".join(frame_info.raw_lines)
989 989 else:
990 990 result += "".join(
991 991 _format_traceback_lines(
992 992 frame_info.lines, Colors, self.has_colors, lvals
993 993 )
994 994 )
995 995 return result
996 996
997 997 def prepare_header(self, etype: str, long_version: bool = False):
998 998 colors = self.Colors # just a shorthand + quicker name lookup
999 999 colorsnormal = colors.Normal # used a lot
1000 1000 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
1001 1001 width = min(75, get_terminal_size()[0])
1002 1002 if long_version:
1003 1003 # Header with the exception type, python version, and date
1004 1004 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
1005 1005 date = time.ctime(time.time())
1006 1006
1007 1007 head = "%s%s%s\n%s%s%s\n%s" % (
1008 1008 colors.topline,
1009 1009 "-" * width,
1010 1010 colorsnormal,
1011 1011 exc,
1012 1012 " " * (width - len(etype) - len(pyver)),
1013 1013 pyver,
1014 1014 date.rjust(width),
1015 1015 )
1016 1016 head += (
1017 1017 "\nA problem occurred executing Python code. Here is the sequence of function"
1018 1018 "\ncalls leading up to the error, with the most recent (innermost) call last."
1019 1019 )
1020 1020 else:
1021 1021 # Simplified header
1022 1022 head = "%s%s" % (
1023 1023 exc,
1024 1024 "Traceback (most recent call last)".rjust(width - len(etype)),
1025 1025 )
1026 1026
1027 1027 return head
1028 1028
1029 1029 def format_exception(self, etype, evalue):
1030 1030 colors = self.Colors # just a shorthand + quicker name lookup
1031 1031 colorsnormal = colors.Normal # used a lot
1032 1032 # Get (safely) a string form of the exception info
1033 1033 try:
1034 1034 etype_str, evalue_str = map(str, (etype, evalue))
1035 1035 except:
1036 1036 # User exception is improperly defined.
1037 1037 etype, evalue = str, sys.exc_info()[:2]
1038 1038 etype_str, evalue_str = map(str, (etype, evalue))
1039 1039
1040 1040 # PEP-678 notes
1041 1041 notes = getattr(evalue, "__notes__", [])
1042 1042 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
1043 1043 notes = [_safe_string(notes, "__notes__", func=repr)]
1044 1044
1045 1045 # ... and format it
1046 1046 return [
1047 1047 "{}{}{}: {}".format(
1048 1048 colors.excName,
1049 1049 etype_str,
1050 1050 colorsnormal,
1051 1051 py3compat.cast_unicode(evalue_str),
1052 1052 ),
1053 1053 *(
1054 1054 "{}{}".format(
1055 1055 colorsnormal, _safe_string(py3compat.cast_unicode(n), "note")
1056 1056 )
1057 1057 for n in notes
1058 1058 ),
1059 1059 ]
1060 1060
1061 1061 def format_exception_as_a_whole(
1062 1062 self,
1063 1063 etype: type,
1064 1064 evalue: Optional[BaseException],
1065 1065 etb: Optional[TracebackType],
1066 1066 number_of_lines_of_context,
1067 1067 tb_offset: Optional[int],
1068 1068 ):
1069 1069 """Formats the header, traceback and exception message for a single exception.
1070 1070
1071 1071 This may be called multiple times by Python 3 exception chaining
1072 1072 (PEP 3134).
1073 1073 """
1074 1074 # some locals
1075 1075 orig_etype = etype
1076 1076 try:
1077 1077 etype = etype.__name__ # type: ignore
1078 1078 except AttributeError:
1079 1079 pass
1080 1080
1081 1081 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1082 1082 assert isinstance(tb_offset, int)
1083 1083 head = self.prepare_header(str(etype), self.long_header)
1084 1084 records = (
1085 1085 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
1086 1086 )
1087 1087
1088 1088 frames = []
1089 1089 skipped = 0
1090 1090 lastrecord = len(records) - 1
1091 1091 for i, record in enumerate(records):
1092 1092 if (
1093 1093 not isinstance(record._sd, stack_data.RepeatedFrames)
1094 1094 and self.skip_hidden
1095 1095 ):
1096 1096 if (
1097 1097 record.frame.f_locals.get("__tracebackhide__", 0)
1098 1098 and i != lastrecord
1099 1099 ):
1100 1100 skipped += 1
1101 1101 continue
1102 1102 if skipped:
1103 1103 Colors = self.Colors # just a shorthand + quicker name lookup
1104 1104 ColorsNormal = Colors.Normal # used a lot
1105 1105 frames.append(
1106 1106 " %s[... skipping hidden %s frame]%s\n"
1107 1107 % (Colors.excName, skipped, ColorsNormal)
1108 1108 )
1109 1109 skipped = 0
1110 1110 frames.append(self.format_record(record))
1111 1111 if skipped:
1112 1112 Colors = self.Colors # just a shorthand + quicker name lookup
1113 1113 ColorsNormal = Colors.Normal # used a lot
1114 1114 frames.append(
1115 1115 " %s[... skipping hidden %s frame]%s\n"
1116 1116 % (Colors.excName, skipped, ColorsNormal)
1117 1117 )
1118 1118
1119 1119 formatted_exception = self.format_exception(etype, evalue)
1120 1120 if records:
1121 1121 frame_info = records[-1]
1122 1122 ipinst = get_ipython()
1123 1123 if ipinst is not None:
1124 1124 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
1125 1125
1126 1126 return [[head] + frames + formatted_exception]
1127 1127
1128 1128 def get_records(
1129 1129 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
1130 1130 ):
1131 1131 assert etb is not None
1132 1132 context = number_of_lines_of_context - 1
1133 1133 after = context // 2
1134 1134 before = context - after
1135 1135 if self.has_colors:
1136 style = get_style_by_name(self._tb_highlight_style)
1137 style = stack_data.style_with_executing_node(style, self._tb_highlight)
1136 style = get_style_by_name(self.tb_highlight_style)
1137 style = stack_data.style_with_executing_node(style, self.tb_highlight)
1138 1138 formatter = Terminal256Formatter(style=style)
1139 1139 else:
1140 1140 formatter = None
1141 1141 options = stack_data.Options(
1142 1142 before=before,
1143 1143 after=after,
1144 1144 pygments_formatter=formatter,
1145 1145 )
1146 1146
1147 1147 # Let's estimate the amount of code we will have to parse/highlight.
1148 1148 cf: Optional[TracebackType] = etb
1149 1149 max_len = 0
1150 1150 tbs = []
1151 1151 while cf is not None:
1152 1152 try:
1153 1153 mod = inspect.getmodule(cf.tb_frame)
1154 1154 if mod is not None:
1155 1155 mod_name = mod.__name__
1156 1156 root_name, *_ = mod_name.split(".")
1157 1157 if root_name == "IPython":
1158 1158 cf = cf.tb_next
1159 1159 continue
1160 1160 max_len = get_line_number_of_frame(cf.tb_frame)
1161 1161
1162 1162 except OSError:
1163 1163 max_len = 0
1164 1164 max_len = max(max_len, max_len)
1165 1165 tbs.append(cf)
1166 1166 cf = getattr(cf, "tb_next", None)
1167 1167
1168 1168 if max_len > FAST_THRESHOLD:
1169 1169 FIs = []
1170 1170 for tb in tbs:
1171 1171 frame = tb.tb_frame # type: ignore
1172 1172 lineno = (frame.f_lineno,)
1173 1173 code = frame.f_code
1174 1174 filename = code.co_filename
1175 1175 # TODO: Here we need to use before/after/
1176 1176 FIs.append(
1177 1177 FrameInfo(
1178 1178 "Raw frame", filename, lineno, frame, code, context=context
1179 1179 )
1180 1180 )
1181 1181 return FIs
1182 1182 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1183 1183 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1184 1184 return res
1185 1185
1186 1186 def structured_traceback(
1187 1187 self,
1188 1188 etype: type,
1189 1189 evalue: Optional[BaseException],
1190 1190 etb: Optional[TracebackType] = None,
1191 1191 tb_offset: Optional[int] = None,
1192 1192 number_of_lines_of_context: int = 5,
1193 1193 ):
1194 1194 """Return a nice text document describing the traceback."""
1195 1195 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1196 1196 tb_offset)
1197 1197
1198 1198 colors = self.Colors # just a shorthand + quicker name lookup
1199 1199 colorsnormal = colors.Normal # used a lot
1200 1200 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1201 1201 structured_traceback_parts = [head]
1202 1202 chained_exceptions_tb_offset = 0
1203 1203 lines_of_context = 3
1204 1204 formatted_exceptions = formatted_exception
1205 1205 exception = self.get_parts_of_chained_exception(evalue)
1206 1206 if exception:
1207 1207 assert evalue is not None
1208 1208 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1209 1209 etype, evalue, etb = exception
1210 1210 else:
1211 1211 evalue = None
1212 1212 chained_exc_ids = set()
1213 1213 while evalue:
1214 1214 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1215 1215 chained_exceptions_tb_offset)
1216 1216 exception = self.get_parts_of_chained_exception(evalue)
1217 1217
1218 1218 if exception and not id(exception[1]) in chained_exc_ids:
1219 1219 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1220 1220 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1221 1221 etype, evalue, etb = exception
1222 1222 else:
1223 1223 evalue = None
1224 1224
1225 1225 # we want to see exceptions in a reversed order:
1226 1226 # the first exception should be on top
1227 1227 for formatted_exception in reversed(formatted_exceptions):
1228 1228 structured_traceback_parts += formatted_exception
1229 1229
1230 1230 return structured_traceback_parts
1231 1231
1232 1232 def debugger(self, force: bool = False):
1233 1233 """Call up the pdb debugger if desired, always clean up the tb
1234 1234 reference.
1235 1235
1236 1236 Keywords:
1237 1237
1238 1238 - force(False): by default, this routine checks the instance call_pdb
1239 1239 flag and does not actually invoke the debugger if the flag is false.
1240 1240 The 'force' option forces the debugger to activate even if the flag
1241 1241 is false.
1242 1242
1243 1243 If the call_pdb flag is set, the pdb interactive debugger is
1244 1244 invoked. In all cases, the self.tb reference to the current traceback
1245 1245 is deleted to prevent lingering references which hamper memory
1246 1246 management.
1247 1247
1248 1248 Note that each call to pdb() does an 'import readline', so if your app
1249 1249 requires a special setup for the readline completers, you'll have to
1250 1250 fix that by hand after invoking the exception handler."""
1251 1251
1252 1252 if force or self.call_pdb:
1253 1253 if self.pdb is None:
1254 1254 self.pdb = self.debugger_cls()
1255 1255 # the system displayhook may have changed, restore the original
1256 1256 # for pdb
1257 1257 display_trap = DisplayTrap(hook=sys.__displayhook__)
1258 1258 with display_trap:
1259 1259 self.pdb.reset()
1260 1260 # Find the right frame so we don't pop up inside ipython itself
1261 1261 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
1262 1262 etb = self.tb # type: ignore[has-type]
1263 1263 else:
1264 1264 etb = self.tb = sys.last_traceback
1265 1265 while self.tb is not None and self.tb.tb_next is not None:
1266 1266 assert self.tb.tb_next is not None
1267 1267 self.tb = self.tb.tb_next
1268 1268 if etb and etb.tb_next:
1269 1269 etb = etb.tb_next
1270 1270 self.pdb.botframe = etb.tb_frame
1271 1271 # last_value should be deprecated, but last-exc sometimme not set
1272 1272 # please check why later and remove the getattr.
1273 1273 exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value) # type: ignore[attr-defined]
1274 1274 if exc:
1275 1275 self.pdb.interaction(None, exc)
1276 1276 else:
1277 1277 self.pdb.interaction(None, etb)
1278 1278
1279 1279 if hasattr(self, 'tb'):
1280 1280 del self.tb
1281 1281
1282 1282 def handler(self, info=None):
1283 1283 (etype, evalue, etb) = info or sys.exc_info()
1284 1284 self.tb = etb
1285 1285 ostream = self.ostream
1286 1286 ostream.flush()
1287 1287 ostream.write(self.text(etype, evalue, etb))
1288 1288 ostream.write('\n')
1289 1289 ostream.flush()
1290 1290
1291 1291 # Changed so an instance can just be called as VerboseTB_inst() and print
1292 1292 # out the right info on its own.
1293 1293 def __call__(self, etype=None, evalue=None, etb=None):
1294 1294 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1295 1295 if etb is None:
1296 1296 self.handler()
1297 1297 else:
1298 1298 self.handler((etype, evalue, etb))
1299 1299 try:
1300 1300 self.debugger()
1301 1301 except KeyboardInterrupt:
1302 1302 print("\nKeyboardInterrupt")
1303 1303
1304 1304
1305 1305 #----------------------------------------------------------------------------
1306 1306 class FormattedTB(VerboseTB, ListTB):
1307 1307 """Subclass ListTB but allow calling with a traceback.
1308 1308
1309 1309 It can thus be used as a sys.excepthook for Python > 2.1.
1310 1310
1311 1311 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1312 1312
1313 1313 Allows a tb_offset to be specified. This is useful for situations where
1314 1314 one needs to remove a number of topmost frames from the traceback (such as
1315 1315 occurs with python programs that themselves execute other python code,
1316 1316 like Python shells). """
1317 1317
1318 1318 mode: str
1319 1319
1320 1320 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1321 1321 ostream=None,
1322 1322 tb_offset=0, long_header=False, include_vars=False,
1323 1323 check_cache=None, debugger_cls=None,
1324 1324 parent=None, config=None):
1325 1325
1326 1326 # NEVER change the order of this list. Put new modes at the end:
1327 1327 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1328 1328 self.verbose_modes = self.valid_modes[1:3]
1329 1329
1330 1330 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1331 1331 ostream=ostream, tb_offset=tb_offset,
1332 1332 long_header=long_header, include_vars=include_vars,
1333 1333 check_cache=check_cache, debugger_cls=debugger_cls,
1334 1334 parent=parent, config=config)
1335 1335
1336 1336 # Different types of tracebacks are joined with different separators to
1337 1337 # form a single string. They are taken from this dict
1338 1338 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1339 1339 Minimal='')
1340 1340 # set_mode also sets the tb_join_char attribute
1341 1341 self.set_mode(mode)
1342 1342
1343 1343 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1344 1344 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1345 1345 mode = self.mode
1346 1346 if mode in self.verbose_modes:
1347 1347 # Verbose modes need a full traceback
1348 1348 return VerboseTB.structured_traceback(
1349 1349 self, etype, value, tb, tb_offset, number_of_lines_of_context
1350 1350 )
1351 1351 elif mode == 'Minimal':
1352 1352 return ListTB.get_exception_only(self, etype, value)
1353 1353 else:
1354 1354 # We must check the source cache because otherwise we can print
1355 1355 # out-of-date source code.
1356 1356 self.check_cache()
1357 1357 # Now we can extract and format the exception
1358 1358 return ListTB.structured_traceback(
1359 1359 self, etype, value, tb, tb_offset, number_of_lines_of_context
1360 1360 )
1361 1361
1362 1362 def stb2text(self, stb):
1363 1363 """Convert a structured traceback (a list) to a string."""
1364 1364 return self.tb_join_char.join(stb)
1365 1365
1366 1366 def set_mode(self, mode: Optional[str] = None):
1367 1367 """Switch to the desired mode.
1368 1368
1369 1369 If mode is not specified, cycles through the available modes."""
1370 1370
1371 1371 if not mode:
1372 1372 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1373 1373 len(self.valid_modes)
1374 1374 self.mode = self.valid_modes[new_idx]
1375 1375 elif mode not in self.valid_modes:
1376 1376 raise ValueError(
1377 1377 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1378 1378 "Valid modes: " + str(self.valid_modes)
1379 1379 )
1380 1380 else:
1381 1381 assert isinstance(mode, str)
1382 1382 self.mode = mode
1383 1383 # include variable details only in 'Verbose' mode
1384 1384 self.include_vars = (self.mode == self.valid_modes[2])
1385 1385 # Set the join character for generating text tracebacks
1386 1386 self.tb_join_char = self._join_chars[self.mode]
1387 1387
1388 1388 # some convenient shortcuts
1389 1389 def plain(self):
1390 1390 self.set_mode(self.valid_modes[0])
1391 1391
1392 1392 def context(self):
1393 1393 self.set_mode(self.valid_modes[1])
1394 1394
1395 1395 def verbose(self):
1396 1396 self.set_mode(self.valid_modes[2])
1397 1397
1398 1398 def minimal(self):
1399 1399 self.set_mode(self.valid_modes[3])
1400 1400
1401 1401
1402 1402 #----------------------------------------------------------------------------
1403 1403 class AutoFormattedTB(FormattedTB):
1404 1404 """A traceback printer which can be called on the fly.
1405 1405
1406 1406 It will find out about exceptions by itself.
1407 1407
1408 1408 A brief example::
1409 1409
1410 1410 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1411 1411 try:
1412 1412 ...
1413 1413 except:
1414 1414 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1415 1415 """
1416 1416
1417 1417 def __call__(self, etype=None, evalue=None, etb=None,
1418 1418 out=None, tb_offset=None):
1419 1419 """Print out a formatted exception traceback.
1420 1420
1421 1421 Optional arguments:
1422 1422 - out: an open file-like object to direct output to.
1423 1423
1424 1424 - tb_offset: the number of frames to skip over in the stack, on a
1425 1425 per-call basis (this overrides temporarily the instance's tb_offset
1426 1426 given at initialization time."""
1427 1427
1428 1428 if out is None:
1429 1429 out = self.ostream
1430 1430 out.flush()
1431 1431 out.write(self.text(etype, evalue, etb, tb_offset))
1432 1432 out.write('\n')
1433 1433 out.flush()
1434 1434 # FIXME: we should remove the auto pdb behavior from here and leave
1435 1435 # that to the clients.
1436 1436 try:
1437 1437 self.debugger()
1438 1438 except KeyboardInterrupt:
1439 1439 print("\nKeyboardInterrupt")
1440 1440
1441 1441 def structured_traceback(
1442 1442 self,
1443 1443 etype: type,
1444 1444 evalue: Optional[BaseException],
1445 1445 etb: Optional[TracebackType] = None,
1446 1446 tb_offset: Optional[int] = None,
1447 1447 number_of_lines_of_context: int = 5,
1448 1448 ):
1449 1449 # tb: TracebackType or tupleof tb types ?
1450 1450 if etype is None:
1451 1451 etype, evalue, etb = sys.exc_info()
1452 1452 if isinstance(etb, tuple):
1453 1453 # tb is a tuple if this is a chained exception.
1454 1454 self.tb = etb[0]
1455 1455 else:
1456 1456 self.tb = etb
1457 1457 return FormattedTB.structured_traceback(
1458 1458 self, etype, evalue, etb, tb_offset, number_of_lines_of_context
1459 1459 )
1460 1460
1461 1461
1462 1462 #---------------------------------------------------------------------------
1463 1463
1464 1464 # A simple class to preserve Nathan's original functionality.
1465 1465 class ColorTB(FormattedTB):
1466 1466 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1467 1467
1468 1468 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1469 1469 FormattedTB.__init__(self, color_scheme=color_scheme,
1470 1470 call_pdb=call_pdb, **kwargs)
1471 1471
1472 1472
1473 1473 class SyntaxTB(ListTB):
1474 1474 """Extension which holds some state: the last exception value"""
1475 1475
1476 1476 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1477 1477 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1478 1478 self.last_syntax_error = None
1479 1479
1480 1480 def __call__(self, etype, value, elist):
1481 1481 self.last_syntax_error = value
1482 1482
1483 1483 ListTB.__call__(self, etype, value, elist)
1484 1484
1485 1485 def structured_traceback(self, etype, value, elist, tb_offset=None,
1486 1486 context=5):
1487 1487 # If the source file has been edited, the line in the syntax error can
1488 1488 # be wrong (retrieved from an outdated cache). This replaces it with
1489 1489 # the current value.
1490 1490 if isinstance(value, SyntaxError) \
1491 1491 and isinstance(value.filename, str) \
1492 1492 and isinstance(value.lineno, int):
1493 1493 linecache.checkcache(value.filename)
1494 1494 newtext = linecache.getline(value.filename, value.lineno)
1495 1495 if newtext:
1496 1496 value.text = newtext
1497 1497 self.last_syntax_error = value
1498 1498 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1499 1499 tb_offset=tb_offset, context=context)
1500 1500
1501 1501 def clear_err_state(self):
1502 1502 """Return the current error state and clear it"""
1503 1503 e = self.last_syntax_error
1504 1504 self.last_syntax_error = None
1505 1505 return e
1506 1506
1507 1507 def stb2text(self, stb):
1508 1508 """Convert a structured traceback (a list) to a string."""
1509 1509 return ''.join(stb)
1510 1510
1511 1511
1512 1512 # some internal-use functions
1513 1513 def text_repr(value):
1514 1514 """Hopefully pretty robust repr equivalent."""
1515 1515 # this is pretty horrible but should always return *something*
1516 1516 try:
1517 1517 return pydoc.text.repr(value) # type: ignore[call-arg]
1518 1518 except KeyboardInterrupt:
1519 1519 raise
1520 1520 except:
1521 1521 try:
1522 1522 return repr(value)
1523 1523 except KeyboardInterrupt:
1524 1524 raise
1525 1525 except:
1526 1526 try:
1527 1527 # all still in an except block so we catch
1528 1528 # getattr raising
1529 1529 name = getattr(value, '__name__', None)
1530 1530 if name:
1531 1531 # ick, recursion
1532 1532 return text_repr(name)
1533 1533 klass = getattr(value, '__class__', None)
1534 1534 if klass:
1535 1535 return '%s instance' % text_repr(klass)
1536 1536 except KeyboardInterrupt:
1537 1537 raise
1538 1538 except:
1539 1539 return 'UNRECOVERABLE REPR FAILURE'
1540 1540
1541 1541
1542 1542 def eqrepr(value, repr=text_repr):
1543 1543 return '=%s' % repr(value)
1544 1544
1545 1545
1546 1546 def nullrepr(value, repr=text_repr):
1547 1547 return ''
General Comments 0
You need to be logged in to leave comments. Login now