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