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