1"""
2Verbose and colourful traceback formatting.
3
4**ColorTB**
5
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.
10
11Installation instructions for ColorTB::
12
13 import sys,ultratb
14 sys.excepthook = ultratb.ColorTB()
15
16**VerboseTB**
17
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.
25
26.. note::
27
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).
34
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).
39
40.. note::
41
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.
45
46Installation instructions for VerboseTB::
47
48 import sys,ultratb
49 sys.excepthook = ultratb.VerboseTB()
50
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'.
53
54
55Inheritance diagram:
56
57.. inheritance-diagram:: IPython.core.ultratb
58 :parts: 3
59"""
60
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# *****************************************************************************
68
69import functools
70import inspect
71import linecache
72import sys
73import time
74import traceback
75import types
76import warnings
77from collections.abc import Sequence
78from types import TracebackType
79from typing import Any, Callable, List, Optional, Tuple
80
81import stack_data
82from pygments.formatters.terminal256 import Terminal256Formatter
83from pygments.token import Token
84
85from IPython import get_ipython
86from IPython.utils import path as util_path
87from IPython.utils import py3compat
88from IPython.utils.PyColorize import Parser, Theme, TokenStream, theme_table
89from IPython.utils.terminal import get_terminal_size
90
91from .display_trap import DisplayTrap
92from .doctb import DocTB
93from .tbtools import (
94 FrameInfo,
95 TBTools,
96 _format_traceback_lines,
97 _safe_string,
98 _simple_format_traceback_lines,
99 _tokens_filename,
100 eqrepr,
101 get_line_number_of_frame,
102 nullrepr,
103 text_repr,
104)
105
106# Globals
107# amount of space to put line numbers before verbose tracebacks
108INDENT_SIZE = 8
109
110# When files are too long do not use stackdata to get frames.
111# it is too long.
112FAST_THRESHOLD = 10_000
113
114# ---------------------------------------------------------------------------
115class ListTB(TBTools):
116 """Print traceback information from a traceback list, with optional color.
117
118 Calling requires 3 arguments: (etype, evalue, elist)
119 as would be obtained by::
120
121 etype, evalue, tb = sys.exc_info()
122 if tb:
123 elist = traceback.extract_tb(tb)
124 else:
125 elist = None
126
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).
130
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."""
133
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")
143
144 def _extract_tb(self, tb: TracebackType | None) -> traceback.StackSummary | None:
145 if tb:
146 return traceback.extract_tb(tb)
147 else:
148 return None
149
150 def structured_traceback(
151 self,
152 etype: type,
153 evalue: Optional[BaseException],
154 etb: Optional[TracebackType] = None,
155 tb_offset: Optional[int] = None,
156 context: int = 5,
157 ) -> list[str]:
158 """Return a color formatted string with the traceback info.
159
160 Parameters
161 ----------
162 etype : exception type
163 Type of the exception raised.
164 evalue : object
165 Data stored in the exception
166 etb : list | TracebackType | None
167 If list: List of frames, see class docstring for details.
168 If Traceback: Traceback of the exception.
169 tb_offset : int, optional
170 Number of frames in the traceback to skip. If not given, the
171 instance evalue is used (set in constructor).
172 context : int, optional
173 Number of lines of context information to print.
174
175 Returns
176 -------
177 String with formatted exception.
178 """
179 # This is a workaround to get chained_exc_ids in recursive calls
180 # etb should not be a tuple if structured_traceback is not recursive
181 if isinstance(etb, tuple):
182 etb, chained_exc_ids = etb
183 else:
184 chained_exc_ids = set()
185 elist: list[Any]
186 if isinstance(etb, list):
187 elist = etb
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:]
198
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)
214
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)
218
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
237
238 out_list = ol1 + ol2 + out_list
239
240 return out_list
241
242 def _format_list(self, extracted_list: list[Any]) -> list[str]:
243 """Format a list of traceback entry tuples for printing.
244
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.
251
252 Lifted almost verbatim from traceback.py
253 """
254
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
259
260 item = theme_table[self._theme_name].format(
261 [(Token.NormalEm if em else Token.Normal, " ")]
262 + _tokens_filename(em, filename, lineno=lineno)
263 )
264
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)
286
287 return output_list
288
289 def _format_exception_only(
290 self, etype: type[BaseException], value: BaseException | None
291 ) -> list[str]:
292 """Format the exception part of a traceback.
293
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.
301
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 textline = py3compat.cast_unicode(value.text, "utf-8")
340
341 if textline is not None:
342 i = 0
343 while i < len(textline) and textline[i].isspace():
344 i += 1
345 output_list.append(
346 theme_table[self._theme_name].format(
347 [
348 (Token.Line, " "),
349 (Token.Line, textline.strip()),
350 (Token, "\n"),
351 ]
352 )
353 )
354 if value.offset is not None:
355 s = " "
356 for c in textline[i : value.offset - 1]:
357 if c.isspace():
358 s += c
359 else:
360 s += " "
361 output_list.append(
362 theme_table[self._theme_name].format(
363 [(Token.Caret, s + "^"), (Token, "\n")]
364 )
365 )
366
367 try:
368 assert hasattr(value, "msg")
369 s = value.msg
370 except Exception:
371 s = self._some_str(value)
372 if s:
373 output_list.append(
374 theme_table[self._theme_name].format(
375 stype_tokens
376 + [
377 (Token.ExcName, ":"),
378 (Token, " "),
379 (Token, s),
380 (Token, "\n"),
381 ]
382 )
383 )
384 else:
385 output_list.append("%s\n" % stype)
386
387 # PEP-678 notes
388 output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
389
390 # sync with user hooks
391 if have_filedata:
392 ipinst = get_ipython()
393 if ipinst is not None:
394 assert value is not None
395 assert hasattr(value, "lineno")
396 assert hasattr(value, "filename")
397 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
398
399 return output_list
400
401 def get_exception_only(self, etype, value):
402 """Only print the exception type and message, without a traceback.
403
404 Parameters
405 ----------
406 etype : exception type
407 value : exception value
408 """
409 return ListTB.structured_traceback(self, etype, value)
410
411 def show_exception_only(
412 self, etype: BaseException | None, evalue: TracebackType | None
413 ) -> None:
414 """Only print the exception type and message, without a traceback.
415
416 Parameters
417 ----------
418 etype : exception type
419 evalue : exception value
420 """
421 # This method needs to use __call__ from *this* class, not the one from
422 # a subclass whose signature or behavior may be different
423 ostream = self.ostream
424 ostream.flush()
425 ostream.write("\n".join(self.get_exception_only(etype, evalue)))
426 ostream.flush()
427
428 def _some_str(self, value: Any) -> str:
429 # Lifted from traceback.py
430 try:
431 return py3compat.cast_unicode(str(value))
432 except:
433 return "<unprintable %s object>" % type(value).__name__
434
435
436_sentinel = object()
437_default = "default"
438
439
440# ----------------------------------------------------------------------------
441class VerboseTB(TBTools):
442 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
443 of HTML. Requires inspect and pydoc. Crazy, man.
444
445 Modified version which optionally strips the topmost entries from the
446 traceback, to be used with alternate interpreters (because their own code
447 would appear in the traceback)."""
448
449 tb_highlight = "bg:ansiyellow"
450 tb_highlight_style = "default"
451
452 _mode: str
453
454 def __init__(
455 self,
456 # TODO: no default ?
457 theme_name: str = _default,
458 call_pdb: bool = False,
459 ostream: Any = None,
460 tb_offset: int = 0,
461 long_header: bool = False,
462 include_vars: bool = True,
463 check_cache: Callable[[], None] | None = None,
464 debugger_cls: type | None = None,
465 *,
466 color_scheme: Any = _sentinel,
467 ):
468 """Specify traceback offset, headers and color scheme.
469
470 Define how many frames to drop from the tracebacks. Calling it with
471 tb_offset=1 allows use of this handler in interpreters which will have
472 their own code at the top of the traceback (VerboseTB will first
473 remove that frame before printing the traceback info)."""
474 if color_scheme is not _sentinel:
475 assert isinstance(color_scheme, str)
476 theme_name = color_scheme.lower()
477
478 warnings.warn(
479 "color_scheme is deprecated as of IPython 9.0 and replaced by "
480 "theme_name (which should be lowercase). As you passed a "
481 "color_scheme value I will try to see if I have corresponding "
482 "theme.",
483 stacklevel=2,
484 category=DeprecationWarning,
485 )
486
487 if theme_name != _default:
488 warnings.warn(
489 "You passed both `theme_name` and `color_scheme` "
490 "(deprecated) to VerboseTB constructor. `theme_name` will "
491 "be ignored for the time being.",
492 stacklevel=2,
493 category=DeprecationWarning,
494 )
495
496 if theme_name == _default:
497 theme_name = "linux"
498
499 assert isinstance(theme_name, str)
500 super().__init__(
501 theme_name=theme_name,
502 call_pdb=call_pdb,
503 ostream=ostream,
504 debugger_cls=debugger_cls,
505 )
506 self.tb_offset = tb_offset
507 self.long_header = long_header
508 self.include_vars = include_vars
509 # By default we use linecache.checkcache, but the user can provide a
510 # different check_cache implementation. This was formerly used by the
511 # IPython kernel for interactive code, but is no longer necessary.
512 if check_cache is None:
513 check_cache = linecache.checkcache
514 self.check_cache = check_cache
515
516 self.skip_hidden = True
517
518 def format_record(self, frame_info: FrameInfo) -> str:
519 """Format a single stack frame"""
520 assert isinstance(frame_info, FrameInfo)
521
522 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
523 return theme_table[self._theme_name].format(
524 [
525 (Token, " "),
526 (
527 Token.ExcName,
528 "[... skipping similar frames: %s]" % frame_info.description,
529 ),
530 (Token, "\n"),
531 ]
532 )
533
534 indent: str = " " * INDENT_SIZE
535
536 assert isinstance(frame_info.lineno, int)
537 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
538 if frame_info.executing is not None:
539 func = frame_info.executing.code_qualname()
540 else:
541 func = "?"
542 if func == "<module>":
543 call = ""
544 else:
545 # Decide whether to include variable details or not
546 var_repr = eqrepr if self.include_vars else nullrepr
547 try:
548 scope = inspect.formatargvalues(
549 args, varargs, varkw, locals_, formatvalue=var_repr
550 )
551 assert isinstance(scope, str)
552 call = theme_table[self._theme_name].format(
553 [(Token, "in "), (Token.VName, func), (Token.ValEm, scope)]
554 )
555 except KeyError:
556 # This happens in situations like errors inside generator
557 # expressions, where local variables are listed in the
558 # line, but can't be extracted from the frame. I'm not
559 # 100% sure this isn't actually a bug in inspect itself,
560 # but since there's no info for us to compute with, the
561 # best we can do is report the failure and move on. Here
562 # we must *not* call any traceback construction again,
563 # because that would mess up use of %debug later on. So we
564 # simply report the failure and move on. The only
565 # limitation will be that this frame won't have locals
566 # listed in the call signature. Quite subtle problem...
567 # I can't think of a good way to validate this in a unit
568 # test, but running a script consisting of:
569 # dict( (k,v.strip()) for (k,v) in range(10) )
570 # will illustrate the error, if this exception catch is
571 # disabled.
572 call = theme_table[self._theme_name].format(
573 [
574 (Token, "in "),
575 (Token.VName, func),
576 (Token.ValEm, "(***failed resolving arguments***)"),
577 ]
578 )
579
580 lvals_toks: list[TokenStream] = []
581 if self.include_vars:
582 try:
583 # we likely want to fix stackdata at some point, but
584 # still need a workaround.
585 fibp = frame_info.variables_in_executing_piece
586 for var in fibp:
587 lvals_toks.append(
588 [
589 (Token, var.name),
590 (Token, " "),
591 (Token.ValEm, "= "),
592 (Token.ValEm, repr(var.value)),
593 ]
594 )
595 except Exception:
596 lvals_toks.append(
597 [
598 (
599 Token,
600 "Exception trying to inspect frame. No more locals available.",
601 ),
602 ]
603 )
604
605 if frame_info._sd is None:
606 # fast fallback if file is too long
607 assert frame_info.filename is not None
608 level_tokens = [
609 (Token.FilenameEm, util_path.compress_user(frame_info.filename)),
610 (Token, " "),
611 (Token, call),
612 (Token, "\n"),
613 ]
614
615 _line_format = Parser(theme_name=self._theme_name).format2
616 assert isinstance(frame_info.code, types.CodeType)
617 first_line: int = frame_info.code.co_firstlineno
618 current_line: int = frame_info.lineno
619 raw_lines: list[str] = frame_info.raw_lines
620 index: int = current_line - first_line
621 assert frame_info.context is not None
622 if index >= frame_info.context:
623 start = max(index - frame_info.context, 0)
624 stop = index + frame_info.context
625 index = frame_info.context
626 else:
627 start = 0
628 stop = index + frame_info.context
629 raw_lines = raw_lines[start:stop]
630
631 # Jan 2025: may need _line_format(py3ompat.cast_unicode(s))
632 raw_color_err = [(s, _line_format(s, "str")) for s in raw_lines]
633
634 tb_tokens = _simple_format_traceback_lines(
635 current_line,
636 index,
637 raw_color_err,
638 lvals_toks,
639 theme=theme_table[self._theme_name],
640 )
641 _tb_lines: str = theme_table[self._theme_name].format(tb_tokens)
642
643 return theme_table[self._theme_name].format(level_tokens + tb_tokens)
644 else:
645 result = theme_table[self._theme_name].format(
646 _tokens_filename(True, frame_info.filename, lineno=frame_info.lineno)
647 )
648 result += ", " if call else ""
649 result += f"{call}\n"
650 result += theme_table[self._theme_name].format(
651 _format_traceback_lines(
652 frame_info.lines,
653 theme_table[self._theme_name],
654 self.has_colors,
655 lvals_toks,
656 )
657 )
658 return result
659
660 def prepare_header(self, etype: str, long_version: bool = False) -> str:
661 width = min(75, get_terminal_size()[0])
662 if long_version:
663 # Header with the exception type, python version, and date
664 pyver = "Python " + sys.version.split()[0] + ": " + sys.executable
665 date = time.ctime(time.time())
666 theme = theme_table[self._theme_name]
667 head = theme.format(
668 [
669 (Token.Topline, theme.symbols["top_line"] * width),
670 (Token, "\n"),
671 (Token.ExcName, etype),
672 (Token, " " * (width - len(etype) - len(pyver))),
673 (Token, pyver),
674 (Token, "\n"),
675 (Token, date.rjust(width)),
676 ]
677 )
678 head += (
679 "\nA problem occurred executing Python code. Here is the sequence of function"
680 "\ncalls leading up to the error, with the most recent (innermost) call last."
681 )
682 else:
683 # Simplified header
684 head = theme_table[self._theme_name].format(
685 [
686 (Token.ExcName, etype),
687 (
688 Token,
689 "Traceback (most recent call last)".rjust(width - len(etype)),
690 ),
691 ]
692 )
693
694 return head
695
696 def format_exception(self, etype, evalue):
697 # Get (safely) a string form of the exception info
698 try:
699 etype_str, evalue_str = map(str, (etype, evalue))
700 except:
701 # User exception is improperly defined.
702 etype, evalue = str, sys.exc_info()[:2]
703 etype_str, evalue_str = map(str, (etype, evalue))
704
705 # PEP-678 notes
706 notes = getattr(evalue, "__notes__", [])
707 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
708 notes = [_safe_string(notes, "__notes__", func=repr)]
709
710 # ... and format it
711 return [
712 theme_table[self._theme_name].format(
713 [(Token.ExcName, etype_str), (Token, ": "), (Token, evalue_str)]
714 ),
715 *(
716 theme_table[self._theme_name].format(
717 [(Token, _safe_string(py3compat.cast_unicode(n), "note"))]
718 )
719 for n in notes
720 ),
721 ]
722
723 def format_exception_as_a_whole(
724 self,
725 etype: type,
726 evalue: Optional[BaseException],
727 etb: Optional[TracebackType],
728 context: int,
729 tb_offset: Optional[int],
730 ) -> list[list[str]]:
731 """Formats the header, traceback and exception message for a single exception.
732
733 This may be called multiple times by Python 3 exception chaining
734 (PEP 3134).
735 """
736 # some locals
737 orig_etype = etype
738 try:
739 etype = etype.__name__ # type: ignore[assignment]
740 except AttributeError:
741 pass
742
743 tb_offset = self.tb_offset if tb_offset is None else tb_offset
744 assert isinstance(tb_offset, int)
745 head = self.prepare_header(str(etype), self.long_header)
746 records = self.get_records(etb, context, tb_offset) if etb else []
747
748 frames = []
749 skipped = 0
750 lastrecord = len(records) - 1
751 for i, record in enumerate(records):
752 if (
753 not isinstance(record._sd, stack_data.RepeatedFrames)
754 and self.skip_hidden
755 ):
756 if (
757 record.frame.f_locals.get("__tracebackhide__", 0)
758 and i != lastrecord
759 ):
760 skipped += 1
761 continue
762 if skipped:
763 frames.append(
764 theme_table[self._theme_name].format(
765 [
766 (Token, " "),
767 (Token.ExcName, "[... skipping hidden %s frame]" % skipped),
768 (Token, "\n"),
769 ]
770 )
771 )
772 skipped = 0
773 frames.append(self.format_record(record))
774 if skipped:
775 frames.append(
776 theme_table[self._theme_name].format(
777 [
778 (Token, " "),
779 (Token.ExcName, "[... skipping hidden %s frame]" % skipped),
780 (Token, "\n"),
781 ]
782 )
783 )
784
785 formatted_exception = self.format_exception(etype, evalue)
786 if records:
787 frame_info = records[-1]
788 ipinst = get_ipython()
789 if ipinst is not None:
790 ipinst.hooks.synchronize_with_editor(
791 frame_info.filename, frame_info.lineno, 0
792 )
793
794 return [[head] + frames + formatted_exception]
795
796 def get_records(self, etb: TracebackType, context: int, tb_offset: int) -> Any:
797 assert etb is not None
798 context = context - 1
799 after = context // 2
800 before = context - after
801 if self.has_colors:
802 base_style = theme_table[self._theme_name].as_pygments_style()
803 style = stack_data.style_with_executing_node(base_style, self.tb_highlight)
804 formatter = Terminal256Formatter(style=style)
805 else:
806 formatter = None
807 options = stack_data.Options(
808 before=before,
809 after=after,
810 pygments_formatter=formatter,
811 )
812
813 # Let's estimate the amount of code we will have to parse/highlight.
814 cf: Optional[TracebackType] = etb
815 max_len = 0
816 tbs = []
817 while cf is not None:
818 try:
819 mod = inspect.getmodule(cf.tb_frame)
820 if mod is not None:
821 mod_name = mod.__name__
822 root_name, *_ = mod_name.split(".")
823 if root_name == "IPython":
824 cf = cf.tb_next
825 continue
826 max_len = get_line_number_of_frame(cf.tb_frame)
827
828 except OSError:
829 max_len = 0
830 max_len = max(max_len, max_len)
831 tbs.append(cf)
832 cf = getattr(cf, "tb_next", None)
833
834 if max_len > FAST_THRESHOLD:
835 FIs: list[FrameInfo] = []
836 for tb in tbs:
837 frame = tb.tb_frame # type: ignore[union-attr]
838 lineno = frame.f_lineno
839 code = frame.f_code
840 filename = code.co_filename
841 # TODO: Here we need to use before/after/
842 FIs.append(
843 FrameInfo(
844 "Raw frame", filename, lineno, frame, code, context=context
845 )
846 )
847 return FIs
848 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
849 res2 = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
850 return res2
851
852 def structured_traceback(
853 self,
854 etype: type,
855 evalue: Optional[BaseException],
856 etb: Optional[TracebackType] = None,
857 tb_offset: Optional[int] = None,
858 context: int = 5,
859 ) -> list[str]:
860 """Return a nice text document describing the traceback."""
861 formatted_exceptions: list[list[str]] = self.format_exception_as_a_whole(
862 etype, evalue, etb, context, tb_offset
863 )
864
865 termsize = min(75, get_terminal_size()[0])
866 theme = theme_table[self._theme_name]
867 head: str = theme.format(
868 [
869 (
870 Token.Topline,
871 theme.symbols["top_line"] * termsize,
872 ),
873 ]
874 )
875 structured_traceback_parts: list[str] = [head]
876 chained_exceptions_tb_offset = 0
877 lines_of_context = 3
878 exception = self.get_parts_of_chained_exception(evalue)
879 if exception:
880 assert evalue is not None
881 formatted_exceptions += self.prepare_chained_exception_message(
882 evalue.__cause__
883 )
884 etype, evalue, etb = exception
885 else:
886 evalue = None
887 chained_exc_ids = set()
888 while evalue:
889 formatted_exceptions += self.format_exception_as_a_whole(
890 etype, evalue, etb, lines_of_context, chained_exceptions_tb_offset
891 )
892 exception = self.get_parts_of_chained_exception(evalue)
893
894 if exception and id(exception[1]) not in chained_exc_ids:
895 chained_exc_ids.add(
896 id(exception[1])
897 ) # trace exception to avoid infinite 'cause' loop
898 formatted_exceptions += self.prepare_chained_exception_message(
899 evalue.__cause__
900 )
901 etype, evalue, etb = exception
902 else:
903 evalue = None
904
905 # we want to see exceptions in a reversed order:
906 # the first exception should be on top
907 for fx in reversed(formatted_exceptions):
908 structured_traceback_parts += fx
909
910 return structured_traceback_parts
911
912 def debugger(self, force: bool = False) -> None:
913 """Call up the pdb debugger if desired, always clean up the tb
914 reference.
915
916 Keywords:
917
918 - force(False): by default, this routine checks the instance call_pdb
919 flag and does not actually invoke the debugger if the flag is false.
920 The 'force' option forces the debugger to activate even if the flag
921 is false.
922
923 If the call_pdb flag is set, the pdb interactive debugger is
924 invoked. In all cases, the self.tb reference to the current traceback
925 is deleted to prevent lingering references which hamper memory
926 management.
927
928 Note that each call to pdb() does an 'import readline', so if your app
929 requires a special setup for the readline completers, you'll have to
930 fix that by hand after invoking the exception handler."""
931
932 if force or self.call_pdb:
933 if self.pdb is None:
934 self.pdb = self.debugger_cls()
935 # the system displayhook may have changed, restore the original
936 # for pdb
937 display_trap = DisplayTrap(hook=sys.__displayhook__)
938 with display_trap:
939 self.pdb.reset()
940 # Find the right frame so we don't pop up inside ipython itself
941 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
942 etb = self.tb # type: ignore[has-type]
943 else:
944 etb = self.tb = sys.last_traceback
945 while self.tb is not None and self.tb.tb_next is not None:
946 assert self.tb.tb_next is not None
947 self.tb = self.tb.tb_next
948 if etb and etb.tb_next:
949 etb = etb.tb_next
950 self.pdb.botframe = etb.tb_frame
951 # last_value should be deprecated, but last-exc sometimme not set
952 # please check why later and remove the getattr.
953 exc = (
954 sys.last_value
955 if sys.version_info < (3, 12)
956 else getattr(sys, "last_exc", sys.last_value)
957 ) # type: ignore[attr-defined]
958 if exc:
959 self.pdb.interaction(None, exc)
960 else:
961 self.pdb.interaction(None, etb)
962
963 if hasattr(self, "tb"):
964 del self.tb
965
966 def handler(self, info=None):
967 (etype, evalue, etb) = info or sys.exc_info()
968 self.tb = etb
969 ostream = self.ostream
970 ostream.flush()
971 ostream.write(self.text(etype, evalue, etb)) # type:ignore[arg-type]
972 ostream.write("\n")
973 ostream.flush()
974
975 # Changed so an instance can just be called as VerboseTB_inst() and print
976 # out the right info on its own.
977 def __call__(self, etype=None, evalue=None, etb=None):
978 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
979 if etb is None:
980 self.handler()
981 else:
982 self.handler((etype, evalue, etb))
983 try:
984 self.debugger()
985 except KeyboardInterrupt:
986 print("\nKeyboardInterrupt")
987
988
989# ----------------------------------------------------------------------------
990class FormattedTB(VerboseTB, ListTB):
991 """Subclass ListTB but allow calling with a traceback.
992
993 It can thus be used as a sys.excepthook for Python > 2.1.
994
995 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
996
997 Allows a tb_offset to be specified. This is useful for situations where
998 one needs to remove a number of topmost frames from the traceback (such as
999 occurs with python programs that themselves execute other python code,
1000 like Python shells)."""
1001
1002 mode: str
1003
1004 def __init__(
1005 self,
1006 mode="Plain",
1007 # TODO: no default
1008 theme_name="linux",
1009 call_pdb=False,
1010 ostream=None,
1011 tb_offset=0,
1012 long_header=False,
1013 include_vars=False,
1014 check_cache=None,
1015 debugger_cls=None,
1016 ):
1017 # NEVER change the order of this list. Put new modes at the end:
1018 self.valid_modes = ["Plain", "Context", "Verbose", "Minimal", "Docs"]
1019 self.verbose_modes = self.valid_modes[1:3]
1020
1021 VerboseTB.__init__(
1022 self,
1023 theme_name=theme_name,
1024 call_pdb=call_pdb,
1025 ostream=ostream,
1026 tb_offset=tb_offset,
1027 long_header=long_header,
1028 include_vars=include_vars,
1029 check_cache=check_cache,
1030 debugger_cls=debugger_cls,
1031 )
1032
1033 # Different types of tracebacks are joined with different separators to
1034 # form a single string. They are taken from this dict
1035 self._join_chars = dict(
1036 Plain="", Context="\n", Verbose="\n", Minimal="", Docs=""
1037 )
1038 # set_mode also sets the tb_join_char attribute
1039 self.set_mode(mode)
1040
1041 def structured_traceback(
1042 self,
1043 etype: type,
1044 evalue: BaseException | None,
1045 etb: TracebackType | None = None,
1046 tb_offset: int | None = None,
1047 context: int = 5,
1048 ) -> list[str]:
1049 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1050 mode = self.mode
1051 if mode in self.verbose_modes:
1052 # Verbose modes need a full traceback
1053 return VerboseTB.structured_traceback(
1054 self, etype, evalue, etb, tb_offset, context
1055 )
1056 elif mode == "Docs":
1057 # return DocTB
1058 return DocTB(
1059 theme_name=self._theme_name,
1060 call_pdb=self.call_pdb,
1061 ostream=self.ostream,
1062 tb_offset=tb_offset,
1063 long_header=self.long_header,
1064 include_vars=self.include_vars,
1065 check_cache=self.check_cache,
1066 debugger_cls=self.debugger_cls,
1067 ).structured_traceback(
1068 etype, evalue, etb, tb_offset, 1
1069 ) # type: ignore[arg-type]
1070
1071 elif mode == "Minimal":
1072 return ListTB.get_exception_only(self, etype, evalue)
1073 else:
1074 # We must check the source cache because otherwise we can print
1075 # out-of-date source code.
1076 self.check_cache()
1077 # Now we can extract and format the exception
1078 return ListTB.structured_traceback(
1079 self, etype, evalue, etb, tb_offset, context
1080 )
1081
1082 def stb2text(self, stb: list[str]) -> str:
1083 """Convert a structured traceback (a list) to a string."""
1084 return self.tb_join_char.join(stb)
1085
1086 def set_mode(self, mode: Optional[str] = None) -> None:
1087 """Switch to the desired mode.
1088
1089 If mode is not specified, cycles through the available modes."""
1090
1091 if not mode:
1092 new_idx = (self.valid_modes.index(self.mode) + 1) % len(self.valid_modes)
1093 self.mode = self.valid_modes[new_idx]
1094 elif mode not in self.valid_modes:
1095 raise ValueError(
1096 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1097 "Valid modes: " + str(self.valid_modes)
1098 )
1099 else:
1100 assert isinstance(mode, str)
1101 self.mode = mode
1102 # include variable details only in 'Verbose' mode
1103 self.include_vars = self.mode == self.valid_modes[2]
1104 # Set the join character for generating text tracebacks
1105 self.tb_join_char = self._join_chars[self.mode]
1106
1107 # some convenient shortcuts
1108 def plain(self) -> None:
1109 self.set_mode(self.valid_modes[0])
1110
1111 def context(self) -> None:
1112 self.set_mode(self.valid_modes[1])
1113
1114 def verbose(self) -> None:
1115 self.set_mode(self.valid_modes[2])
1116
1117 def minimal(self) -> None:
1118 self.set_mode(self.valid_modes[3])
1119
1120
1121# ----------------------------------------------------------------------------
1122class AutoFormattedTB(FormattedTB):
1123 """A traceback printer which can be called on the fly.
1124
1125 It will find out about exceptions by itself.
1126
1127 A brief example::
1128
1129 AutoTB = AutoFormattedTB(mode = 'Verbose', theme_name='linux')
1130 try:
1131 ...
1132 except:
1133 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1134 """
1135
1136 def __call__(
1137 self,
1138 etype: type | None = None,
1139 evalue: BaseException | None = None,
1140 etb: TracebackType | None = None,
1141 out: Any = None,
1142 tb_offset: int | None = None,
1143 ) -> None:
1144 """Print out a formatted exception traceback.
1145
1146 Optional arguments:
1147 - out: an open file-like object to direct output to.
1148
1149 - tb_offset: the number of frames to skip over in the stack, on a
1150 per-call basis (this overrides temporarily the instance's tb_offset
1151 given at initialization time."""
1152
1153 if out is None:
1154 out = self.ostream
1155 out.flush()
1156 out.write(self.text(etype, evalue, etb, tb_offset)) # type:ignore[arg-type]
1157 out.write("\n")
1158 out.flush()
1159 # FIXME: we should remove the auto pdb behavior from here and leave
1160 # that to the clients.
1161 try:
1162 self.debugger()
1163 except KeyboardInterrupt:
1164 print("\nKeyboardInterrupt")
1165
1166 def structured_traceback(
1167 self,
1168 etype: type,
1169 evalue: Optional[BaseException],
1170 etb: Optional[TracebackType] = None,
1171 tb_offset: Optional[int] = None,
1172 context: int = 5,
1173 ) -> list[str]:
1174 # tb: TracebackType or tupleof tb types ?
1175 if etype is None:
1176 etype, evalue, etb = sys.exc_info()
1177 if isinstance(etb, tuple):
1178 # tb is a tuple if this is a chained exception.
1179 self.tb = etb[0]
1180 else:
1181 self.tb = etb
1182 return FormattedTB.structured_traceback(
1183 self, etype, evalue, etb, tb_offset, context
1184 )
1185
1186
1187# ---------------------------------------------------------------------------
1188
1189
1190# A simple class to preserve Nathan's original functionality.
1191class ColorTB(FormattedTB):
1192 """Deprecated since IPython 9.0."""
1193
1194 def __init__(self, *args, **kwargs):
1195 warnings.warn(
1196 "Deprecated since IPython 9.0 use FormattedTB directly ColorTB is just an alias",
1197 DeprecationWarning,
1198 stacklevel=2,
1199 )
1200
1201 super().__init__(*args, **kwargs)
1202
1203
1204class SyntaxTB(ListTB):
1205 """Extension which holds some state: the last exception value"""
1206
1207 last_syntax_error: BaseException | None
1208
1209 def __init__(self, *, theme_name):
1210 super().__init__(theme_name=theme_name)
1211 self.last_syntax_error = None
1212
1213 def __call__(self, etype, value, elist):
1214 self.last_syntax_error = value
1215
1216 super().__call__(etype, value, elist)
1217
1218 def structured_traceback(
1219 self,
1220 etype: type,
1221 evalue: BaseException | None,
1222 etb: TracebackType | None = None,
1223 tb_offset: int | None = None,
1224 context: int = 5,
1225 ) -> list[str]:
1226 value = evalue
1227 # If the source file has been edited, the line in the syntax error can
1228 # be wrong (retrieved from an outdated cache). This replaces it with
1229 # the current value.
1230 if (
1231 isinstance(value, SyntaxError)
1232 and isinstance(value.filename, str)
1233 and isinstance(value.lineno, int)
1234 ):
1235 linecache.checkcache(value.filename)
1236 newtext = linecache.getline(value.filename, value.lineno)
1237 if newtext:
1238 value.text = newtext
1239 self.last_syntax_error = value
1240 return super(SyntaxTB, self).structured_traceback(
1241 etype, value, etb, tb_offset=tb_offset, context=context
1242 )
1243
1244 def clear_err_state(self) -> Any | None:
1245 """Return the current error state and clear it"""
1246 e = self.last_syntax_error
1247 self.last_syntax_error = None
1248 return e
1249
1250 def stb2text(self, stb: list[str]) -> str:
1251 """Convert a structured traceback (a list) to a string."""
1252 return "".join(stb)