Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/ultratb.py: 14%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Verbose and colourful traceback formatting.
4**ColorTB**
6I've always found it a bit hard to visually parse tracebacks in Python. The
7ColorTB class is a solution to that problem. It colors the different parts of a
8traceback in a manner similar to what you would expect from a syntax-highlighting
9text editor.
11Installation instructions for ColorTB::
13 import sys,ultratb
14 sys.excepthook = ultratb.ColorTB()
16**VerboseTB**
18I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
19of useful info when a traceback occurs. Ping originally had it spit out HTML
20and intended it for CGI programmers, but why should they have all the fun? I
21altered it to spit out colored text to the terminal. It's a bit overwhelming,
22but kind of neat, and maybe useful for long-running programs that you believe
23are bug-free. If a crash *does* occur in that type of program you want details.
24Give it a shot--you'll love it or you'll hate it.
26.. note::
28 The Verbose mode prints the variables currently visible where the exception
29 happened (shortening their strings if too long). This can potentially be
30 very slow, if you happen to have a huge data structure whose string
31 representation is complex to compute. Your computer may appear to freeze for
32 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
33 with Ctrl-C (maybe hitting it more than once).
35 If you encounter this kind of situation often, you may want to use the
36 Verbose_novars mode instead of the regular Verbose, which avoids formatting
37 variables (but otherwise includes the information and context given by
38 Verbose).
40.. note::
42 The verbose mode print all variables in the stack, which means it can
43 potentially leak sensitive information like access keys, or unencrypted
44 password.
46Installation instructions for VerboseTB::
48 import sys,ultratb
49 sys.excepthook = ultratb.VerboseTB()
51Note: Much of the code in this module was lifted verbatim from the standard
52library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
55Inheritance diagram:
57.. inheritance-diagram:: IPython.core.ultratb
58 :parts: 3
59"""
61# *****************************************************************************
62# Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
63# Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
64#
65# Distributed under the terms of the BSD License. The full license is in
66# the file COPYING, distributed as part of this software.
67# *****************************************************************************
69import inspect
70import linecache
71import sys
72import time
73import traceback
74import types
75import warnings
76from collections.abc import Sequence
77from types import TracebackType
78from typing import Any
79from collections.abc import Callable
81import stack_data
82from pygments.formatters.terminal256 import Terminal256Formatter
83from pygments.token import Token
85from IPython.core.getipython import get_ipython
86from IPython.utils.PyColorize import Parser, TokenStream, theme_table
87from IPython.utils.terminal import get_terminal_size
89from .display_trap import DisplayTrap
90from .doctb import DocTB
91from .tbtools import (
92 FrameInfo,
93 TBTools,
94 _format_traceback_lines,
95 _safe_string,
96 _simple_format_traceback_lines,
97 _tokens_filename,
98 eqrepr,
99 get_line_number_of_frame,
100 nullrepr,
101)
103# Globals
104# amount of space to put line numbers before verbose tracebacks
105INDENT_SIZE = 8
107# When files are too long do not use stackdata to get frames.
108# it is too long.
109FAST_THRESHOLD = 10_000
111# ---------------------------------------------------------------------------
112class ListTB(TBTools):
113 """Print traceback information from a traceback list, with optional color.
115 Calling requires 3 arguments: (etype, evalue, elist)
116 as would be obtained by::
118 etype, evalue, tb = sys.exc_info()
119 if tb:
120 elist = traceback.extract_tb(tb)
121 else:
122 elist = None
124 It can thus be used by programs which need to process the traceback before
125 printing (such as console replacements based on the code module from the
126 standard library).
128 Because they are meant to be called without a full traceback (only a
129 list), instances of this class can't call the interactive pdb debugger."""
131 def __call__(
132 self,
133 etype: type[BaseException],
134 evalue: BaseException | None,
135 etb: TracebackType | None,
136 ) -> None:
137 self.ostream.flush()
138 self.ostream.write(self.text(etype, evalue, etb))
139 self.ostream.write("\n")
141 def _extract_tb(self, tb: TracebackType | None) -> traceback.StackSummary | None:
142 if tb:
143 return traceback.extract_tb(tb)
144 else:
145 return None
147 def structured_traceback(
148 self,
149 etype: type,
150 evalue: BaseException | None,
151 etb: TracebackType | None = None,
152 tb_offset: int | None = None,
153 context: int = 5,
154 ) -> list[str]:
155 """Return a color formatted string with the traceback info.
157 Parameters
158 ----------
159 etype : exception type
160 Type of the exception raised.
161 evalue : object
162 Data stored in the exception
163 etb : list | TracebackType | None
164 If list: List of frames, see class docstring for details.
165 If Traceback: Traceback of the exception.
166 tb_offset : int, optional
167 Number of frames in the traceback to skip. If not given, the
168 instance evalue is used (set in constructor).
169 context : int, optional
170 Number of lines of context information to print.
172 Returns
173 -------
174 String with formatted exception.
175 """
176 # This is a workaround to get chained_exc_ids in recursive calls
177 # etb should not be a tuple if structured_traceback is not recursive
178 # (see the recursive self.structured_traceback() call below), and can
179 # also be a pre-built list of frames per the docstring above; neither
180 # is expressible in the public `TracebackType | None` signature.
181 if isinstance(etb, tuple):
182 etb, chained_exc_ids = etb # type: ignore[unreachable]
183 else:
184 chained_exc_ids = set()
185 elist: list[Any]
186 if isinstance(etb, list):
187 elist = etb # type: ignore[unreachable]
188 elif etb is not None:
189 elist = self._extract_tb(etb) # type: ignore[assignment]
190 else:
191 elist = []
192 tb_offset = self.tb_offset if tb_offset is None else tb_offset
193 assert isinstance(tb_offset, int)
194 out_list: list[str] = []
195 if elist:
196 if tb_offset and len(elist) > tb_offset:
197 elist = elist[tb_offset:]
199 out_list.append(
200 theme_table[self._theme_name].format(
201 [
202 (Token, "Traceback"),
203 (Token, " "),
204 (Token.NormalEm, "(most recent call last)"),
205 (Token, ":"),
206 (Token, "\n"),
207 ]
208 ),
209 )
210 out_list.extend(self._format_list(elist))
211 # The exception info should be a single entry in the list.
212 lines = "".join(self._format_exception_only(etype, evalue))
213 out_list.append(lines)
215 # Find chained exceptions if we have a traceback (not for exception-only mode)
216 if etb is not None:
217 exception = self.get_parts_of_chained_exception(evalue)
219 if exception and (id(exception[1]) not in chained_exc_ids):
220 chained_exception_message: list[str] = (
221 self.prepare_chained_exception_message(evalue.__cause__)[0]
222 if evalue is not None
223 else [""]
224 )
225 etype, evalue, etb = exception
226 # Trace exception to avoid infinite 'cause' loop
227 chained_exc_ids.add(id(exception[1]))
228 chained_exceptions_tb_offset = 0
229 ol1 = self.structured_traceback(
230 etype,
231 evalue,
232 (etb, chained_exc_ids), # type: ignore[arg-type]
233 chained_exceptions_tb_offset,
234 context,
235 )
236 ol2 = chained_exception_message
238 out_list = ol1 + ol2 + out_list
240 return out_list
242 def _format_list(self, extracted_list: list[Any]) -> list[str]:
243 """Format a list of traceback entry tuples for printing.
245 Given a list of tuples as returned by extract_tb() or
246 extract_stack(), return a list of strings ready for printing.
247 Each string in the resulting list corresponds to the item with the
248 same index in the argument list. Each string ends in a newline;
249 the strings may contain internal newlines as well, for those items
250 whose source text line is not None.
252 Lifted almost verbatim from traceback.py
253 """
255 output_list = []
256 for ind, (filename, lineno, name, line) in enumerate(extracted_list):
257 # Will emphasize the last entry
258 em = True if ind == len(extracted_list) - 1 else False
260 item = theme_table[self._theme_name].format(
261 [(Token.NormalEm if em else Token.Normal, " ")]
262 + _tokens_filename(em, filename, lineno=lineno)
263 )
265 # This seem to be only in xmode plain (%run sinpleer), investigate why not share with verbose.
266 # look at _tokens_filename in forma_record.
267 if name != "<module>":
268 item += theme_table[self._theme_name].format(
269 [
270 (Token.NormalEm if em else Token.Normal, " in "),
271 (Token.TB.NameEm if em else Token.TB.Name, name),
272 ]
273 )
274 item += theme_table[self._theme_name].format(
275 [(Token.NormalEm if em else Token, "\n")]
276 )
277 if line:
278 item += theme_table[self._theme_name].format(
279 [
280 (Token.Line if em else Token, " "),
281 (Token.Line if em else Token, line.strip()),
282 (Token, "\n"),
283 ]
284 )
285 output_list.append(item)
287 return output_list
289 def _format_exception_only(
290 self, etype: type[BaseException], value: BaseException | None
291 ) -> list[str]:
292 """Format the exception part of a traceback.
294 The arguments are the exception type and value such as given by
295 sys.exc_info()[:2]. The return value is a list of strings, each ending
296 in a newline. Normally, the list contains a single string; however,
297 for SyntaxError exceptions, it contains several lines that (when
298 printed) display detailed information about where the syntax error
299 occurred. The message indicating which exception occurred is the
300 always last string in the list.
302 Also lifted nearly verbatim from traceback.py
303 """
304 have_filedata = False
305 output_list = []
306 stype_tokens = [(Token.ExcName, etype.__name__)]
307 stype: str = theme_table[self._theme_name].format(stype_tokens)
308 if value is None:
309 # Not sure if this can still happen in Python 2.6 and above
310 output_list.append(stype + "\n")
311 else:
312 if issubclass(etype, SyntaxError):
313 assert hasattr(value, "filename")
314 assert hasattr(value, "lineno")
315 assert hasattr(value, "text")
316 assert hasattr(value, "offset")
317 assert hasattr(value, "msg")
318 have_filedata = True
319 if not value.filename:
320 value.filename = "<string>"
321 if value.lineno:
322 lineno = value.lineno
323 textline = linecache.getline(value.filename, value.lineno)
324 else:
325 lineno = "unknown"
326 textline = ""
327 output_list.append(
328 theme_table[self._theme_name].format(
329 [(Token, " ")]
330 + _tokens_filename(
331 True,
332 value.filename,
333 lineno=(None if lineno == "unknown" else lineno),
334 )
335 + [(Token, "\n")]
336 )
337 )
338 if textline == "":
339 # sep 2025:
340 # textline = py3compat.cast_unicode(value.text, "utf-8")
341 if value.text is None:
342 textline = ""
343 else:
344 assert isinstance(value.text, str)
345 textline = value.text
347 if textline is not None:
348 i = 0
349 while i < len(textline) and textline[i].isspace():
350 i += 1
351 output_list.append(
352 theme_table[self._theme_name].format(
353 [
354 (Token.Line, " "),
355 (Token.Line, textline.strip()),
356 (Token, "\n"),
357 ]
358 )
359 )
360 if value.offset is not None:
361 s = " "
362 for c in textline[i : value.offset - 1]:
363 if c.isspace():
364 s += c
365 else:
366 s += " "
367 output_list.append(
368 theme_table[self._theme_name].format(
369 [(Token.Caret, s + "^"), (Token, "\n")]
370 )
371 )
372 s = value.msg
373 else:
374 s = self._some_str(value)
375 if s:
376 output_list.append(
377 theme_table[self._theme_name].format(
378 stype_tokens
379 + [
380 (Token.ExcName, ":"),
381 (Token, " "),
382 (Token, s),
383 (Token, "\n"),
384 ]
385 )
386 )
387 else:
388 output_list.append("%s\n" % stype)
390 # PEP-678 notes
391 output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
393 # sync with user hooks
394 if have_filedata:
395 ipinst = get_ipython()
396 if ipinst is not None:
397 assert value is not None
398 assert hasattr(value, "lineno")
399 assert hasattr(value, "filename")
400 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
402 return output_list
404 def get_exception_only(self, etype, value):
405 """Only print the exception type and message, without a traceback.
407 Parameters
408 ----------
409 etype : exception type
410 value : exception value
411 """
412 return ListTB.structured_traceback(self, etype, value)
414 def structured_traceback_doctest(
415 self,
416 etype,
417 evalue,
418 etb=None,
419 tb_offset=None,
420 context=5,
421 ):
422 """Return a doctest-freindly traceback.
424 Shows only the header, an ellipsis, and the excepttion line.
425 """
426 # Handle chained exception rule
427 if isinstance(etb, tuple):
428 etb, chained_exc_ids = etb
429 else:
430 chained_exc_ids = set()
432 have_traceback = etb is not None
434 out_list = []
435 if have_traceback:
436 out_list.append("Traceback (most recent call last):\n")
437 out_list.append(" ...\n")
439 lines = "".join(self._format_exception_only(etype, evalue))
440 out_list.append(lines)
442 # Handle chained exceptions
443 if etb is not None:
444 exception = self.get_parts_of_chained_exception(evalue)
445 if exception and (id(exception[1]) not in chained_exc_ids):
446 chained_exception_message = (self.prepare_chained_exception_message(evalue.__cause__)[0] if evalue is not None else [""])
447 etype, evalue, etb = exception
448 chained_exc_ids.add(id(exception[1]))
449 chained_tb = self.structured_traceback_doctest(etype, evalue, (etb, chained_exc_ids), 0, context)
450 out_list = chained_tb + chained_exception_message + out_list
452 return out_list
454 def show_exception_only(
455 self, etype: BaseException | None, evalue: TracebackType | None
456 ) -> None:
457 """Only print the exception type and message, without a traceback.
459 Parameters
460 ----------
461 etype : exception type
462 evalue : exception value
463 """
464 # This method needs to use __call__ from *this* class, not the one from
465 # a subclass whose signature or behavior may be different
466 ostream = self.ostream
467 ostream.flush()
468 ostream.write("\n".join(self.get_exception_only(etype, evalue)))
469 ostream.flush()
471 def _some_str(self, value: Any) -> str:
472 # Lifted from traceback.py
473 try:
474 return str(value)
475 except Exception:
476 return "<unprintable %s object>" % type(value).__name__
479_sentinel = object()
480_default = "default"
483# ----------------------------------------------------------------------------
484class VerboseTB(TBTools):
485 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
486 of HTML. Requires inspect and pydoc. Crazy, man.
488 Modified version which optionally strips the topmost entries from the
489 traceback, to be used with alternate interpreters (because their own code
490 would appear in the traceback)."""
492 tb_highlight = "bg:ansiyellow"
493 tb_highlight_style = "default"
495 _mode: str
497 def __init__(
498 self,
499 # TODO: no default ?
500 theme_name: str = _default,
501 call_pdb: bool = False,
502 ostream: Any = None,
503 tb_offset: int = 0,
504 long_header: bool = False,
505 include_vars: bool = True,
506 check_cache: Callable[[], None] | None = None,
507 debugger_cls: type | None = None,
508 *,
509 color_scheme: Any = _sentinel,
510 ):
511 """Specify traceback offset, headers and color scheme.
513 Define how many frames to drop from the tracebacks. Calling it with
514 tb_offset=1 allows use of this handler in interpreters which will have
515 their own code at the top of the traceback (VerboseTB will first
516 remove that frame before printing the traceback info)."""
517 if color_scheme is not _sentinel:
518 assert isinstance(color_scheme, str)
519 theme_name = color_scheme.lower()
521 warnings.warn(
522 "color_scheme is deprecated as of IPython 9.0 and replaced by "
523 "theme_name (which should be lowercase). As you passed a "
524 "color_scheme value I will try to see if I have corresponding "
525 "theme.",
526 stacklevel=2,
527 category=DeprecationWarning,
528 )
530 if theme_name != _default:
531 warnings.warn(
532 "You passed both `theme_name` and `color_scheme` "
533 "(deprecated since IPython 9.0) to VerboseTB constructor. `theme_name` will "
534 "be ignored for the time being.",
535 stacklevel=2,
536 category=DeprecationWarning,
537 )
539 if theme_name == _default:
540 theme_name = "linux"
542 assert isinstance(theme_name, str)
543 super().__init__(
544 theme_name=theme_name,
545 call_pdb=call_pdb,
546 ostream=ostream,
547 debugger_cls=debugger_cls,
548 )
549 self.tb_offset = tb_offset
550 self.long_header = long_header
551 self.include_vars = include_vars
552 # By default we use linecache.checkcache, but the user can provide a
553 # different check_cache implementation. This was formerly used by the
554 # IPython kernel for interactive code, but is no longer necessary.
555 if check_cache is None:
556 check_cache = linecache.checkcache
557 self.check_cache = check_cache
559 self.skip_hidden = True
561 def format_record(self, frame_info: FrameInfo) -> str:
562 """Format a single stack frame"""
563 assert isinstance(frame_info, FrameInfo)
565 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
566 return theme_table[self._theme_name].format(
567 [
568 (Token, " "),
569 (
570 Token.ExcName,
571 "[... skipping similar frames: %s]" % frame_info.description,
572 ),
573 (Token, "\n"),
574 ]
575 )
577 indent: str = " " * INDENT_SIZE
579 assert isinstance(frame_info.lineno, int)
580 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
581 func: str
582 if frame_info.executing is not None:
583 func = frame_info.executing.code_qualname()
584 elif frame_info.code is not None:
585 func = (
586 getattr(frame_info.code, "co_qualname", None) or frame_info.code.co_name
587 )
588 else:
589 func = "?"
590 if func == "<module>":
591 call = ""
592 else:
593 # Decide whether to include variable details or not
594 var_repr = eqrepr if self.include_vars else nullrepr
595 try:
596 scope = inspect.formatargvalues(
597 args, varargs, varkw, locals_, formatvalue=var_repr
598 )
599 assert isinstance(scope, str)
600 call = theme_table[self._theme_name].format(
601 [(Token, "in "), (Token.VName, func), (Token.ValEm, scope)]
602 )
603 except KeyError:
604 # This happens in situations like errors inside generator
605 # expressions, where local variables are listed in the
606 # line, but can't be extracted from the frame. I'm not
607 # 100% sure this isn't actually a bug in inspect itself,
608 # but since there's no info for us to compute with, the
609 # best we can do is report the failure and move on. Here
610 # we must *not* call any traceback construction again,
611 # because that would mess up use of %debug later on. So we
612 # simply report the failure and move on. The only
613 # limitation will be that this frame won't have locals
614 # listed in the call signature. Quite subtle problem...
615 # I can't think of a good way to validate this in a unit
616 # test, but running a script consisting of:
617 # dict( (k,v.strip()) for (k,v) in range(10) )
618 # will illustrate the error, if this exception catch is
619 # disabled.
620 call = theme_table[self._theme_name].format(
621 [
622 (Token, "in "),
623 (Token.VName, func),
624 (Token.ValEm, "(***failed resolving arguments***)"),
625 ]
626 )
628 lvals_toks: list[TokenStream] = []
629 if self.include_vars:
630 try:
631 # we likely want to fix stackdata at some point, but
632 # still need a workaround.
633 fibp = frame_info.variables_in_executing_piece
634 for var in fibp:
635 lvals_toks.append(
636 [
637 (Token, var.name),
638 (Token, " "),
639 (Token.ValEm, "= "),
640 (Token.ValEm, repr(var.value)),
641 ]
642 )
643 except Exception:
644 lvals_toks.append(
645 [
646 (
647 Token,
648 "Exception trying to inspect frame. No more locals available.",
649 ),
650 ]
651 )
653 if frame_info._sd is None:
654 # fast fallback if file is too long
655 assert frame_info.filename is not None
656 level_tokens = (
657 _tokens_filename(True, frame_info.filename, lineno=frame_info.lineno)
658 + [
659 (Token, ", " if call else ""),
660 (Token, call),
661 (Token, "\n"),
662 ]
663 )
665 _line_format = Parser(theme_name=self._theme_name).format2
666 assert isinstance(frame_info.code, types.CodeType)
667 first_line: int = frame_info.code.co_firstlineno
668 current_line: int = frame_info.lineno
669 raw_lines: list[str] = frame_info.raw_lines
670 index: int = current_line - first_line
671 assert frame_info.context is not None
672 if index >= frame_info.context:
673 start = max(index - frame_info.context, 0)
674 stop = index + frame_info.context
675 index = frame_info.context
676 else:
677 start = 0
678 stop = index + frame_info.context
679 raw_lines = raw_lines[start:stop]
681 # Jan 2025: may need _line_format(py3ompat.cast_unicode(s))
682 raw_color_err = []
683 for s in raw_lines:
684 formatted, is_error = _line_format(s, "str")
685 assert formatted is not None, "format2 should return str when out='str'"
686 raw_color_err.append((s, (formatted, is_error)))
688 tb_tokens = _simple_format_traceback_lines(
689 current_line,
690 index,
691 raw_color_err,
692 lvals_toks,
693 theme=theme_table[self._theme_name],
694 )
695 _tb_lines: str = theme_table[self._theme_name].format(tb_tokens)
697 return theme_table[self._theme_name].format(level_tokens + tb_tokens)
698 else:
699 result = theme_table[self._theme_name].format(
700 _tokens_filename(True, frame_info.filename, lineno=frame_info.lineno)
701 )
702 result += ", " if call else ""
703 result += f"{call}\n"
704 result += theme_table[self._theme_name].format(
705 _format_traceback_lines(
706 frame_info.lines,
707 theme_table[self._theme_name],
708 self.has_colors,
709 lvals_toks,
710 )
711 )
712 return result
714 def prepare_header(self, etype: str, long_version: bool = False) -> str:
715 width = min(75, get_terminal_size()[0])
716 if long_version:
717 # Header with the exception type, python version, and date
718 pyver = "Python " + sys.version.split()[0] + ": " + sys.executable
719 date = time.ctime(time.time())
720 theme = theme_table[self._theme_name]
721 head = theme.format(
722 [
723 (Token.Topline, theme.symbols["top_line"] * width),
724 (Token, "\n"),
725 (Token.ExcName, etype),
726 (Token, " " * (width - len(etype) - len(pyver))),
727 (Token, pyver),
728 (Token, "\n"),
729 (Token, date.rjust(width)),
730 ]
731 )
732 head += (
733 "\nA problem occurred executing Python code. Here is the sequence of function"
734 "\ncalls leading up to the error, with the most recent (innermost) call last."
735 )
736 else:
737 # Simplified header
738 head = theme_table[self._theme_name].format(
739 [
740 (Token.ExcName, etype),
741 (
742 Token,
743 "Traceback (most recent call last)".rjust(width - len(etype)),
744 ),
745 ]
746 )
748 return head
750 def format_exception(self, etype, evalue):
751 # Get (safely) a string form of the exception info
752 try:
753 etype_str, evalue_str = map(str, (etype, evalue))
754 except Exception:
755 # User exception is improperly defined.
756 etype, evalue = str, sys.exc_info()[:2]
757 etype_str, evalue_str = map(str, (etype, evalue))
759 # PEP-678 notes
760 notes = getattr(evalue, "__notes__", [])
761 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
762 notes = [_safe_string(notes, "__notes__", func=repr)]
764 for note in notes:
765 assert isinstance(note, str)
767 str_notes: Sequence[str] = notes
769 # ... and format it
770 return [
771 theme_table[self._theme_name].format(
772 [(Token.ExcName, etype_str), (Token, ": "), (Token, evalue_str)]
773 ),
774 *(
775 theme_table[self._theme_name].format([(Token, note)])
776 for note in str_notes
777 ),
778 ]
780 def format_exception_as_a_whole(
781 self,
782 etype: type,
783 evalue: BaseException | None,
784 etb: TracebackType | None,
785 context: int,
786 tb_offset: int | None,
787 ) -> list[list[str]]:
788 """Formats the header, traceback and exception message for a single exception.
790 This may be called multiple times by Python 3 exception chaining
791 (PEP 3134).
792 """
793 # some locals
794 orig_etype = etype
795 try:
796 etype = etype.__name__ # type: ignore[assignment]
797 except AttributeError:
798 pass
800 tb_offset = self.tb_offset if tb_offset is None else tb_offset
801 assert isinstance(tb_offset, int)
802 head = self.prepare_header(str(etype), self.long_header)
803 records = self.get_records(etb, context, tb_offset) if etb else []
805 frames = []
806 skipped = 0
807 lastrecord = len(records) - 1
808 for i, record in enumerate(records):
809 if (
810 not isinstance(record._sd, stack_data.RepeatedFrames)
811 and self.skip_hidden
812 ):
813 if (
814 record.frame.f_locals.get("__tracebackhide__", 0)
815 and i != lastrecord
816 ):
817 skipped += 1
818 continue
819 if skipped:
820 frames.append(
821 theme_table[self._theme_name].format(
822 [
823 (Token, " "),
824 (Token.ExcName, "[... skipping hidden %s frame]" % skipped),
825 (Token, "\n"),
826 ]
827 )
828 )
829 skipped = 0
830 frames.append(self.format_record(record))
831 if skipped:
832 frames.append(
833 theme_table[self._theme_name].format(
834 [
835 (Token, " "),
836 (Token.ExcName, "[... skipping hidden %s frame]" % skipped),
837 (Token, "\n"),
838 ]
839 )
840 )
842 formatted_exception = self.format_exception(etype, evalue)
843 if records:
844 frame_info = records[-1]
845 ipinst = get_ipython()
846 if ipinst is not None:
847 ipinst.hooks.synchronize_with_editor(
848 frame_info.filename, frame_info.lineno, 0
849 )
851 return [[head] + frames + formatted_exception]
853 def get_records(self, etb: TracebackType, context: int, tb_offset: int) -> Any:
854 assert etb is not None
855 context = context - 1
856 after = context // 2
857 before = context - after
858 if self.has_colors:
859 theme = theme_table[self._theme_name]
860 base_style = theme.as_pygments_style()
861 tb_highlight = theme.extra_style.get(Token.TbHighlight, self.tb_highlight)
862 style = stack_data.style_with_executing_node(base_style, tb_highlight)
863 formatter = Terminal256Formatter(style=style)
864 else:
865 formatter = None
866 options = stack_data.Options(
867 before=before,
868 after=after,
869 pygments_formatter=formatter,
870 )
872 # Collect traceback frames and their module sizes.
873 cf: TracebackType | None = etb
874 tbs: list[tuple[TracebackType, int]] = []
875 while cf is not None:
876 try:
877 mod = inspect.getmodule(cf.tb_frame)
878 if mod is not None:
879 mod_name = mod.__name__
880 root_name, *_ = mod_name.split(".")
881 if root_name == "IPython":
882 cf = cf.tb_next
883 continue
884 frame_len = get_line_number_of_frame(cf.tb_frame)
885 if frame_len == 0:
886 # File not found or not a .py file (e.g. <string> from
887 # exec()). Check if source is actually available; if not,
888 # force the fast path so that FrameInfo's "Could not get
889 # source" fallback is rendered.
890 try:
891 inspect.getsourcelines(cf.tb_frame)
892 except OSError:
893 frame_len = FAST_THRESHOLD + 1
894 except OSError:
895 frame_len = FAST_THRESHOLD + 1
896 assert cf is not None # narrowing for mypy; guarded by while condition
897 tbs.append((cf, frame_len))
898 cf = cf.tb_next
900 # Group consecutive frames by fast/slow and process each group.
901 # Consecutive slow frames must be processed together so that
902 # stack_data can detect RepeatedFrames (recursion collapsing).
903 FIs: list[FrameInfo] = []
904 i = 0
905 while i < len(tbs):
906 tb, frame_len = tbs[i]
907 if frame_len > FAST_THRESHOLD:
908 frame = tb.tb_frame
909 lineno = frame.f_lineno
910 code = frame.f_code
911 filename = code.co_filename
912 FIs.append(
913 FrameInfo(
914 "Raw frame", filename, lineno, frame, code, context=context
915 )
916 )
917 i += 1
918 else:
919 # Collect the consecutive run of slow frames
920 group_start = i
921 while i < len(tbs) and tbs[i][1] <= FAST_THRESHOLD:
922 i += 1
923 # Build set of frame objects in this group for filtering
924 group_frames = {tbs[j][0].tb_frame for j in range(group_start, i)}
925 # Process via stack_data starting from the first tb in the group
926 for sd_fi in stack_data.FrameInfo.stack_data(
927 tbs[group_start][0], options=options
928 ):
929 # stack_data follows tb_next through the full chain,
930 # including IPython frames we skipped during collection.
931 # Filter those out, but always keep RepeatedFrames.
932 if isinstance(sd_fi, stack_data.RepeatedFrames) or sd_fi.frame in group_frames:
933 FIs.append(FrameInfo._from_stack_data_FrameInfo(sd_fi))
935 return FIs
937 def structured_traceback(
938 self,
939 etype: type,
940 evalue: BaseException | None,
941 etb: TracebackType | None = None,
942 tb_offset: int | None = None,
943 context: int = 5,
944 ) -> list[str]:
945 """Return a nice text document describing the traceback."""
946 formatted_exceptions: list[list[str]] = self.format_exception_as_a_whole(
947 etype, evalue, etb, context, tb_offset
948 )
950 termsize = min(75, get_terminal_size()[0])
951 theme = theme_table[self._theme_name]
952 head: str = theme.format(
953 [
954 (
955 Token.Topline,
956 theme.symbols["top_line"] * termsize,
957 ),
958 ]
959 )
960 structured_traceback_parts: list[str] = [head]
961 chained_exceptions_tb_offset = 0
962 lines_of_context = 3
963 exception = self.get_parts_of_chained_exception(evalue)
964 if exception:
965 assert evalue is not None
966 formatted_exceptions += self.prepare_chained_exception_message(
967 evalue.__cause__
968 )
969 etype, evalue, etb = exception
970 else:
971 evalue = None
972 chained_exc_ids = set()
973 while evalue:
974 formatted_exceptions += self.format_exception_as_a_whole(
975 etype, evalue, etb, lines_of_context, chained_exceptions_tb_offset
976 )
977 exception = self.get_parts_of_chained_exception(evalue)
979 if exception and id(exception[1]) not in chained_exc_ids:
980 chained_exc_ids.add(
981 id(exception[1])
982 ) # trace exception to avoid infinite 'cause' loop
983 formatted_exceptions += self.prepare_chained_exception_message(
984 evalue.__cause__
985 )
986 etype, evalue, etb = exception
987 else:
988 evalue = None
990 # we want to see exceptions in a reversed order:
991 # the first exception should be on top
992 for fx in reversed(formatted_exceptions):
993 structured_traceback_parts += fx
995 return structured_traceback_parts
997 def debugger(self, force: bool = False) -> None:
998 """Call up the pdb debugger if desired, always clean up the tb
999 reference.
1001 Keywords:
1003 - force(False): by default, this routine checks the instance call_pdb
1004 flag and does not actually invoke the debugger if the flag is false.
1005 The 'force' option forces the debugger to activate even if the flag
1006 is false.
1008 If the call_pdb flag is set, the pdb interactive debugger is
1009 invoked. In all cases, the self.tb reference to the current traceback
1010 is deleted to prevent lingering references which hamper memory
1011 management.
1013 Note that each call to pdb() does an 'import readline', so if your app
1014 requires a special setup for the readline completers, you'll have to
1015 fix that by hand after invoking the exception handler."""
1017 if force or self.call_pdb:
1018 if self.pdb is None:
1019 self.pdb = self.debugger_cls()
1020 # the system displayhook may have changed, restore the original
1021 # for pdb
1022 display_trap = DisplayTrap(hook=sys.__displayhook__)
1023 with display_trap:
1024 self.pdb.reset()
1025 # Find the right frame so we don't pop up inside ipython itself
1026 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
1027 etb = self.tb # type: ignore[has-type]
1028 else:
1029 etb = self.tb = sys.last_traceback
1030 while self.tb is not None and self.tb.tb_next is not None:
1031 assert self.tb.tb_next is not None
1032 self.tb = self.tb.tb_next
1033 if etb and etb.tb_next:
1034 etb = etb.tb_next
1035 self.pdb.botframe = etb.tb_frame
1036 # last_value should be deprecated, but last-exc sometimme not set
1037 # please check why later and remove the getattr.
1038 exc = (
1039 sys.last_value
1040 if sys.version_info < (3, 12)
1041 else getattr(sys, "last_exc", sys.last_value)
1042 )
1043 if exc:
1044 self.pdb.interaction(None, exc)
1045 else:
1046 self.pdb.interaction(None, etb)
1048 if hasattr(self, "tb"):
1049 del self.tb
1051 def handler(self, info=None):
1052 (etype, evalue, etb) = info or sys.exc_info()
1053 self.tb = etb
1054 ostream = self.ostream
1055 ostream.flush()
1056 ostream.write(self.text(etype, evalue, etb)) # type:ignore[arg-type]
1057 ostream.write("\n")
1058 ostream.flush()
1060 # Changed so an instance can just be called as VerboseTB_inst() and print
1061 # out the right info on its own.
1062 def __call__(self, etype=None, evalue=None, etb=None):
1063 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1064 if etb is None:
1065 self.handler()
1066 else:
1067 self.handler((etype, evalue, etb))
1068 try:
1069 self.debugger()
1070 except KeyboardInterrupt:
1071 print("\nKeyboardInterrupt")
1074# ----------------------------------------------------------------------------
1075class FormattedTB(VerboseTB, ListTB):
1076 """Subclass ListTB but allow calling with a traceback.
1078 It can thus be used as a sys.excepthook for Python > 2.1.
1080 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1082 Allows a tb_offset to be specified. This is useful for situations where
1083 one needs to remove a number of topmost frames from the traceback (such as
1084 occurs with python programs that themselves execute other python code,
1085 like Python shells)."""
1087 mode: str
1089 def __init__(
1090 self,
1091 mode="Plain",
1092 # TODO: no default
1093 theme_name="linux",
1094 call_pdb=False,
1095 ostream=None,
1096 tb_offset=0,
1097 long_header=False,
1098 include_vars=False,
1099 check_cache=None,
1100 debugger_cls=None,
1101 ):
1102 # NEVER change the order of this list. Put new modes at the end:
1103 self.valid_modes = ["Plain", "Context", "Verbose", "Minimal", "Docs", "Doctest"]
1104 self.verbose_modes = self.valid_modes[1:3]
1106 VerboseTB.__init__(
1107 self,
1108 theme_name=theme_name,
1109 call_pdb=call_pdb,
1110 ostream=ostream,
1111 tb_offset=tb_offset,
1112 long_header=long_header,
1113 include_vars=include_vars,
1114 check_cache=check_cache,
1115 debugger_cls=debugger_cls,
1116 )
1118 # Different types of tracebacks are joined with different separators to
1119 # form a single string. They are taken from this dict
1120 self._join_chars = dict(
1121 Plain="", Context="\n", Verbose="\n", Minimal="", Docs="", Doctest=""
1122 )
1123 # set_mode also sets the tb_join_char attribute
1124 self.set_mode(mode)
1126 def structured_traceback(
1127 self,
1128 etype: type,
1129 evalue: BaseException | None,
1130 etb: TracebackType | None = None,
1131 tb_offset: int | None = None,
1132 context: int = 5,
1133 ) -> list[str]:
1134 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1135 mode = self.mode
1136 if mode in self.verbose_modes:
1137 # Verbose modes need a full traceback
1138 return VerboseTB.structured_traceback(
1139 self, etype, evalue, etb, tb_offset, context
1140 )
1141 elif mode == "Docs":
1142 # return DocTB
1143 return DocTB(
1144 theme_name=self._theme_name,
1145 call_pdb=self.call_pdb,
1146 ostream=self.ostream,
1147 tb_offset=tb_offset,
1148 long_header=self.long_header,
1149 include_vars=self.include_vars,
1150 check_cache=self.check_cache,
1151 debugger_cls=self.debugger_cls,
1152 ).structured_traceback(
1153 etype, evalue, etb, tb_offset, 1
1154 )
1156 elif mode == "Minimal":
1157 return ListTB.get_exception_only(self, etype, evalue)
1158 elif mode == "Doctest":
1159 return ListTB.structured_traceback_doctest(self, etype, evalue, etb, tb_offset, context)
1160 else:
1161 # We must check the source cache because otherwise we can print
1162 # out-of-date source code.
1163 self.check_cache()
1164 # Now we can extract and format the exception
1165 return ListTB.structured_traceback(
1166 self, etype, evalue, etb, tb_offset, context
1167 )
1169 def stb2text(self, stb: list[str]) -> str:
1170 """Convert a structured traceback (a list) to a string."""
1171 return self.tb_join_char.join(stb)
1173 def set_mode(self, mode: str | None = None) -> None:
1174 """Switch to the desired mode.
1176 If mode is not specified, cycles through the available modes."""
1178 if not mode:
1179 new_idx = (self.valid_modes.index(self.mode) + 1) % len(self.valid_modes)
1180 self.mode = self.valid_modes[new_idx]
1181 elif mode not in self.valid_modes:
1182 raise ValueError(
1183 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1184 "Valid modes: " + str(self.valid_modes)
1185 )
1186 else:
1187 assert isinstance(mode, str)
1188 self.mode = mode
1189 # include variable details only in 'Verbose' mode
1190 self.include_vars = self.mode == self.valid_modes[2]
1191 # Set the join character for generating text tracebacks
1192 self.tb_join_char = self._join_chars[self.mode]
1194 # some convenient shortcuts
1195 def plain(self) -> None:
1196 self.set_mode(self.valid_modes[0])
1198 def context(self) -> None:
1199 self.set_mode(self.valid_modes[1])
1201 def verbose(self) -> None:
1202 self.set_mode(self.valid_modes[2])
1204 def minimal(self) -> None:
1205 self.set_mode(self.valid_modes[3])
1208# ----------------------------------------------------------------------------
1209class AutoFormattedTB(FormattedTB):
1210 """A traceback printer which can be called on the fly.
1212 It will find out about exceptions by itself.
1214 A brief example::
1216 AutoTB = AutoFormattedTB(mode = 'Verbose', theme_name='linux')
1217 try:
1218 ...
1219 except:
1220 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1221 """
1223 def __call__(
1224 self,
1225 etype: type | None = None,
1226 evalue: BaseException | None = None,
1227 etb: TracebackType | None = None,
1228 out: Any = None,
1229 tb_offset: int | None = None,
1230 ) -> None:
1231 """Print out a formatted exception traceback.
1233 Optional arguments:
1234 - out: an open file-like object to direct output to.
1236 - tb_offset: the number of frames to skip over in the stack, on a
1237 per-call basis (this overrides temporarily the instance's tb_offset
1238 given at initialization time."""
1240 if out is None:
1241 out = self.ostream
1242 out.flush()
1243 out.write(self.text(etype, evalue, etb, tb_offset)) # type:ignore[arg-type]
1244 out.write("\n")
1245 out.flush()
1246 # FIXME: we should remove the auto pdb behavior from here and leave
1247 # that to the clients.
1248 try:
1249 self.debugger()
1250 except KeyboardInterrupt:
1251 print("\nKeyboardInterrupt")
1253 def structured_traceback(
1254 self,
1255 etype: type,
1256 evalue: BaseException | None,
1257 etb: TracebackType | None = None,
1258 tb_offset: int | None = None,
1259 context: int = 5,
1260 ) -> list[str]:
1261 # tb: TracebackType or tupleof tb types ?
1262 # etype can be None when called as structured_traceback(*sys.exc_info())
1263 # with no active exception; etb can be a tuple for a chained exception.
1264 # Neither is expressible in the public signature above.
1265 if etype is None:
1266 etype, evalue, etb = sys.exc_info() # type: ignore[unreachable]
1267 if isinstance(etb, tuple):
1268 # tb is a tuple if this is a chained exception.
1269 self.tb = etb[0] # type: ignore[unreachable]
1270 else:
1271 self.tb = etb
1272 return FormattedTB.structured_traceback(
1273 self, etype, evalue, etb, tb_offset, context
1274 )
1277# ---------------------------------------------------------------------------
1280# A simple class to preserve Nathan's original functionality.
1281class ColorTB(FormattedTB):
1282 """Deprecated since IPython 9.0."""
1284 def __init__(self, *args, **kwargs):
1285 warnings.warn(
1286 "Deprecated since IPython 9.0 use FormattedTB directly ColorTB is just an alias",
1287 DeprecationWarning,
1288 stacklevel=2,
1289 )
1291 super().__init__(*args, **kwargs)
1294class SyntaxTB(ListTB):
1295 """Extension which holds some state: the last exception value"""
1297 last_syntax_error: BaseException | None
1299 def __init__(self, *, theme_name):
1300 super().__init__(theme_name=theme_name)
1301 self.last_syntax_error = None
1303 def __call__(self, etype, value, elist):
1304 self.last_syntax_error = value
1306 super().__call__(etype, value, elist)
1308 def structured_traceback(
1309 self,
1310 etype: type,
1311 evalue: BaseException | None,
1312 etb: TracebackType | None = None,
1313 tb_offset: int | None = None,
1314 context: int = 5,
1315 ) -> list[str]:
1316 value = evalue
1317 # If the source file has been edited, the line in the syntax error can
1318 # be wrong (retrieved from an outdated cache). This replaces it with
1319 # the current value.
1320 if (
1321 isinstance(value, SyntaxError)
1322 and isinstance(value.filename, str)
1323 and isinstance(value.lineno, int)
1324 ):
1325 linecache.checkcache(value.filename)
1326 newtext = linecache.getline(value.filename, value.lineno)
1327 if newtext:
1328 value.text = newtext
1329 self.last_syntax_error = value
1330 return super().structured_traceback(
1331 etype, value, etb, tb_offset=tb_offset, context=context
1332 )
1334 def clear_err_state(self) -> Any | None:
1335 """Return the current error state and clear it"""
1336 e = self.last_syntax_error
1337 self.last_syntax_error = None
1338 return e
1340 def stb2text(self, stb: list[str]) -> str:
1341 """Convert a structured traceback (a list) to a string."""
1342 return "".join(stb)