ultratb.py
1544 lines
| 55.1 KiB
| text/x-python
|
PythonLexer
|
r1234 | # -*- coding: utf-8 -*- | ||
""" | ||||
|
r13888 | Verbose and colourful traceback formatting. | ||
|
r1234 | |||
|
r13597 | **ColorTB** | ||
|
r1234 | I've always found it a bit hard to visually parse tracebacks in Python. The | ||
ColorTB class is a solution to that problem. It colors the different parts of a | ||||
traceback in a manner similar to what you would expect from a syntax-highlighting | ||||
text editor. | ||||
|
r12553 | Installation instructions for ColorTB:: | ||
|
r2124 | import sys,ultratb | ||
sys.excepthook = ultratb.ColorTB() | ||||
|
r1234 | |||
|
r13597 | **VerboseTB** | ||
|
r1234 | I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds | ||
of useful info when a traceback occurs. Ping originally had it spit out HTML | ||||
and intended it for CGI programmers, but why should they have all the fun? I | ||||
altered it to spit out colored text to the terminal. It's a bit overwhelming, | ||||
but kind of neat, and maybe useful for long-running programs that you believe | ||||
are bug-free. If a crash *does* occur in that type of program you want details. | ||||
Give it a shot--you'll love it or you'll hate it. | ||||
|
r12553 | .. note:: | ||
|
r1234 | |||
The Verbose mode prints the variables currently visible where the exception | ||||
happened (shortening their strings if too long). This can potentially be | ||||
very slow, if you happen to have a huge data structure whose string | ||||
representation is complex to compute. Your computer may appear to freeze for | ||||
a while with cpu usage at 100%. If this occurs, you can cancel the traceback | ||||
with Ctrl-C (maybe hitting it more than once). | ||||
If you encounter this kind of situation often, you may want to use the | ||||
Verbose_novars mode instead of the regular Verbose, which avoids formatting | ||||
variables (but otherwise includes the information and context given by | ||||
Verbose). | ||||
|
r4872 | |||
|
r22096 | .. note:: | ||
The verbose mode print all variables in the stack, which means it can | ||||
|
r25146 | potentially leak sensitive information like access keys, or unencrypted | ||
|
r22096 | password. | ||
|
r1234 | |||
|
r17156 | Installation instructions for VerboseTB:: | ||
|
r12553 | |||
|
r2124 | import sys,ultratb | ||
sys.excepthook = ultratb.VerboseTB() | ||||
|
r1234 | |||
Note: Much of the code in this module was lifted verbatim from the standard | ||||
library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'. | ||||
|
r12553 | Color schemes | ||
------------- | ||||
|
r1234 | The colors are defined in the class TBTools through the use of the | ||
ColorSchemeTable class. Currently the following exist: | ||||
- NoColor: allows all of this module to be used in any terminal (the color | ||||
|
r12553 | escapes are just dummy blank strings). | ||
|
r1234 | |||
- Linux: is meant to look good in a terminal like the Linux console (black | ||||
|
r12553 | or very dark background). | ||
|
r1234 | |||
- LightBG: similar to Linux but swaps dark/light colors to be more readable | ||||
|
r12553 | in light background terminals. | ||
|
r1234 | |||
|
r22609 | - Neutral: a neutral color scheme that should be readable on both light and | ||
dark background | ||||
|
r1234 | You can implement other color schemes easily, the syntax is fairly | ||
self-explanatory. Please send back new schemes you develop to the author for | ||||
possible inclusion in future releases. | ||||
|
r8795 | |||
Inheritance diagram: | ||||
.. inheritance-diagram:: IPython.core.ultratb | ||||
:parts: 3 | ||||
|
r1853 | """ | ||
|
r1234 | |||
|
r17158 | #***************************************************************************** | ||
|
r17156 | # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu> | ||
|
r17157 | # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu> | ||
|
r1234 | # | ||
|
r17157 | # Distributed under the terms of the BSD License. The full license is in | ||
# the file COPYING, distributed as part of this software. | ||||
|
r1234 | #***************************************************************************** | ||
|
r2231 | |||
|
r28242 | from collections.abc import Sequence | ||
|
r28208 | import functools | ||
|
r1234 | import inspect | ||
import linecache | ||||
import pydoc | ||||
import sys | ||||
import time | ||||
import traceback | ||||
|
r28208 | import types | ||
|
r27361 | from types import TracebackType | ||
|
r28208 | from typing import Any, List, Optional, Tuple | ||
|
r1234 | |||
|
r25466 | import stack_data | ||
|
r25506 | from pygments.formatters.terminal256 import Terminal256Formatter | ||
|
r25548 | from pygments.styles import get_style_by_name | ||
|
r4758 | |||
|
r28208 | import IPython.utils.colorable as colorable | ||
|
r1234 | # IPython's own modules | ||
|
r10581 | from IPython import get_ipython | ||
from IPython.core import debugger | ||||
|
r2231 | from IPython.core.display_trap import DisplayTrap | ||
|
r2021 | from IPython.core.excolors import exception_colors | ||
|
r28123 | from IPython.utils import PyColorize | ||
|
r8299 | from IPython.utils import path as util_path | ||
|
r4758 | from IPython.utils import py3compat | ||
|
r22150 | from IPython.utils.terminal import get_terminal_size | ||
|
r24336 | |||
|
r1234 | # Globals | ||
# amount of space to put line numbers before verbose tracebacks | ||||
INDENT_SIZE = 8 | ||||
# Default color scheme. This is used, for example, by the traceback | ||||
# formatter. When running in an actual IPython instance, the user's rc.colors | ||||
|
r17156 | # value is used, but having a module global makes this functionality available | ||
|
r2124 | # to users of ultratb who are NOT running inside ipython. | ||
|
r1234 | DEFAULT_SCHEME = 'NoColor' | ||
|
r28123 | FAST_THRESHOLD = 10_000 | ||
|
r1234 | |||
|
r17158 | # --------------------------------------------------------------------------- | ||
|
r1234 | # Code begins | ||
# Helper function -- largely belongs to VerboseTB, but we need the same | ||||
# functionality to produce a pseudo verbose TB for SyntaxErrors, so that they | ||||
# can be recognized properly by ipython.el's py-traceback-line-re | ||||
# (SyntaxErrors have to be treated specially because they have no traceback) | ||||
|
r17156 | |||
|
r28208 | @functools.lru_cache() | ||
def count_lines_in_py_file(filename: str) -> int: | ||||
""" | ||||
Given a filename, returns the number of lines in the file | ||||
if it ends with the extension ".py". Otherwise, returns 0. | ||||
""" | ||||
if not filename.endswith(".py"): | ||||
return 0 | ||||
else: | ||||
try: | ||||
with open(filename, "r") as file: | ||||
s = sum(1 for line in file) | ||||
except UnicodeError: | ||||
return 0 | ||||
return s | ||||
""" | ||||
Given a frame object, returns the total number of lines in the file | ||||
if the filename ends with the extension ".py". Otherwise, returns 0. | ||||
""" | ||||
def get_line_number_of_frame(frame: types.FrameType) -> int: | ||||
""" | ||||
Given a frame object, returns the total number of lines in the file | ||||
containing the frame's code object, or the number of lines in the | ||||
frame's source code if the file is not available. | ||||
Parameters | ||||
---------- | ||||
frame : FrameType | ||||
The frame object whose line number is to be determined. | ||||
Returns | ||||
------- | ||||
int | ||||
The total number of lines in the file containing the frame's | ||||
code object, or the number of lines in the frame's source code | ||||
if the file is not available. | ||||
""" | ||||
filename = frame.f_code.co_filename | ||||
if filename is None: | ||||
print("No file....") | ||||
lines, first = inspect.getsourcelines(frame) | ||||
return first + len(lines) | ||||
return count_lines_in_py_file(filename) | ||||
|
r28242 | def _safe_string(value, what, func=str): | ||
# Copied from cpython/Lib/traceback.py | ||||
try: | ||||
return func(value) | ||||
except: | ||||
return f"<{what} {func.__name__}() failed>" | ||||
|
r27361 | def _format_traceback_lines(lines, Colors, has_colors: bool, lvals): | ||
|
r24334 | """ | ||
Format tracebacks lines with pointing arrow, leading numbers... | ||||
Parameters | ||||
|
r25951 | ---------- | ||
lines : list[Line] | ||||
Colors | ||||
|
r24334 | ColorScheme used. | ||
|
r25951 | lvals : str | ||
|
r24334 | Values of local variables, already colored, to inject just after the error line. | ||
""" | ||||
|
r1234 | numbers_width = INDENT_SIZE - 1 | ||
res = [] | ||||
|
r25466 | for stack_line in lines: | ||
|
r25468 | if stack_line is stack_data.LINE_GAP: | ||
res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal)) | ||||
continue | ||||
|
r25506 | line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n' | ||
|
r25466 | lineno = stack_line.lineno | ||
if stack_line.is_current: | ||||
|
r1234 | # This is the line with the error | ||
|
r25466 | pad = numbers_width - len(str(lineno)) | ||
num = '%s%s' % (debugger.make_arrow(pad), str(lineno)) | ||||
|
r25492 | start_color = Colors.linenoEm | ||
|
r1234 | else: | ||
|
r25466 | num = '%*s' % (numbers_width, lineno) | ||
|
r25492 | start_color = Colors.lineno | ||
|
r26685 | |||
|
r25492 | line = '%s%s%s %s' % (start_color, num, Colors.Normal, line) | ||
|
r1234 | |||
res.append(line) | ||||
|
r25466 | if lvals and stack_line.is_current: | ||
|
r1234 | res.append(lvals + '\n') | ||
return res | ||||
|
r28123 | def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format): | ||
""" | ||||
Format tracebacks lines with pointing arrow, leading numbers... | ||||
Parameters | ||||
========== | ||||
lnum: int | ||||
number of the target line of code. | ||||
index: int | ||||
which line in the list should be highlighted. | ||||
lines: list[string] | ||||
Colors: | ||||
ColorScheme used. | ||||
lvals: bytes | ||||
Values of local variables, already colored, to inject just after the error line. | ||||
_line_format: f (str) -> (str, bool) | ||||
return (colorized version of str, failure to do so) | ||||
""" | ||||
numbers_width = INDENT_SIZE - 1 | ||||
res = [] | ||||
|
r28124 | for i, line in enumerate(lines, lnum - index): | ||
|
r28208 | # assert isinstance(line, str) | ||
|
r28123 | line = py3compat.cast_unicode(line) | ||
|
r28124 | new_line, err = _line_format(line, "str") | ||
|
r28123 | if not err: | ||
line = new_line | ||||
if i == lnum: | ||||
# This is the line with the error | ||||
pad = numbers_width - len(str(i)) | ||||
|
r28124 | num = "%s%s" % (debugger.make_arrow(pad), str(lnum)) | ||
line = "%s%s%s %s%s" % ( | ||||
Colors.linenoEm, | ||||
num, | ||||
Colors.line, | ||||
line, | ||||
Colors.Normal, | ||||
) | ||||
|
r28123 | else: | ||
|
r28124 | num = "%*s" % (numbers_width, i) | ||
line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line) | ||||
|
r28123 | |||
res.append(line) | ||||
if lvals and i == lnum: | ||||
|
r28124 | res.append(lvals + "\n") | ||
|
r28123 | return res | ||
|
r1234 | |||
|
r28124 | |||
|
r27356 | def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None): | ||
|
r26688 | """ | ||
|
r27875 | Format filename lines with custom formatting from caching compiler or `File *.py` by default | ||
|
r26688 | |||
Parameters | ||||
---------- | ||||
file : str | ||||
|
r26690 | ColorFilename | ||
ColorScheme's filename coloring to be used. | ||||
ColorNormal | ||||
|
r26688 | ColorScheme's normal coloring to be used. | ||
""" | ||||
ipinst = get_ipython() | ||||
|
r27875 | if ( | ||
ipinst is not None | ||||
and (data := ipinst.compile.format_code_name(file)) is not None | ||||
): | ||||
label, name = data | ||||
|
r27686 | if lineno is None: | ||
|
r27875 | tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}" | ||
|
r27686 | else: | ||
|
r27875 | tpl_link = ( | ||
f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}" | ||||
) | ||||
|
r26688 | else: | ||
|
r27875 | label = "File" | ||
name = util_path.compress_user( | ||||
|
r26688 | py3compat.cast_unicode(file, util_path.fs_encoding) | ||
) | ||||
|
r27356 | if lineno is None: | ||
|
r27875 | tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}" | ||
|
r27356 | else: | ||
|
r27875 | # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon? | ||
tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}" | ||||
|
r26688 | |||
|
r27875 | return tpl_link.format(label=label, name=name, lineno=lineno) | ||
|
r26688 | |||
|
r1234 | #--------------------------------------------------------------------------- | ||
# Module classes | ||||
|
r22109 | class TBTools(colorable.Colorable): | ||
|
r1234 | """Basic tools used by all traceback printer classes.""" | ||
|
r2459 | |||
|
r2838 | # Number of frames to skip when reporting tracebacks | ||
tb_offset = 0 | ||||
|
r27603 | def __init__( | ||
self, | ||||
color_scheme="NoColor", | ||||
call_pdb=False, | ||||
ostream=None, | ||||
parent=None, | ||||
config=None, | ||||
*, | ||||
debugger_cls=None, | ||||
): | ||||
|
r1234 | # Whether to call the interactive pdb debugger after printing | ||
# tracebacks or not | ||||
|
r22109 | super(TBTools, self).__init__(parent=parent, config=config) | ||
|
r1234 | self.call_pdb = call_pdb | ||
|
r2852 | # Output stream to write to. Note that we store the original value in | ||
# a private attribute and then make the public ostream a property, so | ||||
|
r22742 | # that we can delay accessing sys.stdout until runtime. The way | ||
# things are written now, the sys.stdout object is dynamically managed | ||||
|
r2852 | # so a reference to it should NEVER be stored statically. This | ||
# property approach confines this detail to a single location, and all | ||||
# subclasses can simply access self.ostream for writing. | ||||
self._ostream = ostream | ||||
|
r1234 | # Create color table | ||
|
r1845 | self.color_scheme_table = exception_colors() | ||
|
r1234 | |||
self.set_colors(color_scheme) | ||||
self.old_scheme = color_scheme # save initial value for toggles | ||||
|
r27603 | self.debugger_cls = debugger_cls or debugger.Pdb | ||
|
r1234 | |||
if call_pdb: | ||||
|
r27666 | self.pdb = self.debugger_cls() | ||
|
r1234 | else: | ||
self.pdb = None | ||||
|
r2852 | def _get_ostream(self): | ||
"""Output stream that exceptions are written to. | ||||
Valid values are: | ||||
|
r4872 | |||
|
r2852 | - None: the default, which means that IPython will dynamically resolve | ||
|
r22742 | to sys.stdout. This ensures compatibility with most tools, including | ||
|
r12553 | Windows (where plain stdout doesn't recognize ANSI escapes). | ||
|
r2852 | |||
- Any object with 'write' and 'flush' attributes. | ||||
""" | ||||
|
r22192 | return sys.stdout if self._ostream is None else self._ostream | ||
|
r2852 | |||
def _set_ostream(self, val): | ||||
assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush')) | ||||
self._ostream = val | ||||
|
r4872 | |||
|
r2852 | ostream = property(_get_ostream, _set_ostream) | ||
|
r27361 | @staticmethod | ||
def _get_chained_exception(exception_value): | ||||
cause = getattr(exception_value, "__cause__", None) | ||||
if cause: | ||||
return cause | ||||
if getattr(exception_value, "__suppress_context__", False): | ||||
return None | ||||
return getattr(exception_value, "__context__", None) | ||||
|
r25301 | |||
|
r27361 | def get_parts_of_chained_exception( | ||
self, evalue | ||||
) -> Optional[Tuple[type, BaseException, TracebackType]]: | ||||
chained_evalue = self._get_chained_exception(evalue) | ||||
|
r25301 | |||
if chained_evalue: | ||||
return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__ | ||||
|
r27361 | return None | ||
|
r25301 | |||
|
r27361 | def prepare_chained_exception_message(self, cause) -> List[Any]: | ||
|
r25301 | direct_cause = "\nThe above exception was the direct cause of the following exception:\n" | ||
exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n" | ||||
if cause: | ||||
message = [[direct_cause]] | ||||
else: | ||||
message = [[exception_during_handling]] | ||||
return message | ||||
|
r25506 | @property | ||
|
r27361 | def has_colors(self) -> bool: | ||
|
r25506 | return self.color_scheme_table.active_scheme_name.lower() != "nocolor" | ||
|
r17156 | def set_colors(self, *args, **kw): | ||
|
r1234 | """Shorthand access to the color table scheme selector method.""" | ||
# Set own color table | ||||
|
r17156 | self.color_scheme_table.set_active_scheme(*args, **kw) | ||
|
r1234 | # for convenience, set Colors to the active scheme | ||
self.Colors = self.color_scheme_table.active_colors | ||||
# Also set colors of debugger | ||||
|
r17156 | if hasattr(self, 'pdb') and self.pdb is not None: | ||
self.pdb.set_colors(*args, **kw) | ||||
|
r1234 | |||
def color_toggle(self): | ||||
"""Toggle between the currently active color scheme and NoColor.""" | ||||
|
r4872 | |||
|
r1234 | if self.color_scheme_table.active_scheme_name == 'NoColor': | ||
self.color_scheme_table.set_active_scheme(self.old_scheme) | ||||
self.Colors = self.color_scheme_table.active_colors | ||||
else: | ||||
self.old_scheme = self.color_scheme_table.active_scheme_name | ||||
self.color_scheme_table.set_active_scheme('NoColor') | ||||
self.Colors = self.color_scheme_table.active_colors | ||||
|
r2839 | def stb2text(self, stb): | ||
"""Convert a structured traceback (a list) to a string.""" | ||||
return '\n'.join(stb) | ||||
|
r27361 | def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5): | ||
|
r2838 | """Return formatted traceback. | ||
Subclasses may override this if they add extra arguments. | ||||
""" | ||||
tb_list = self.structured_traceback(etype, value, tb, | ||||
tb_offset, context) | ||||
|
r2839 | return self.stb2text(tb_list) | ||
|
r2838 | |||
|
r27361 | def structured_traceback( | ||
|
r28167 | self, | ||
etype: type, | ||||
evalue: Optional[BaseException], | ||||
etb: Optional[TracebackType] = None, | ||||
tb_offset: Optional[int] = None, | ||||
|
r28208 | number_of_lines_of_context: int = 5, | ||
|
r27361 | ): | ||
|
r2838 | """Return a list of traceback frames. | ||
Must be implemented by each class. | ||||
""" | ||||
raise NotImplementedError() | ||||
|
r1234 | #--------------------------------------------------------------------------- | ||
class ListTB(TBTools): | ||||
"""Print traceback information from a traceback list, with optional color. | ||||
|
r4872 | |||
|
r9244 | Calling requires 3 arguments: (etype, evalue, elist) | ||
as would be obtained by:: | ||||
|
r26685 | |||
|
r1234 | etype, evalue, tb = sys.exc_info() | ||
if tb: | ||||
elist = traceback.extract_tb(tb) | ||||
else: | ||||
elist = None | ||||
It can thus be used by programs which need to process the traceback before | ||||
printing (such as console replacements based on the code module from the | ||||
standard library). | ||||
Because they are meant to be called without a full traceback (only a | ||||
list), instances of this class can't call the interactive pdb debugger.""" | ||||
|
r4872 | |||
|
r1234 | def __call__(self, etype, value, elist): | ||
|
r2860 | self.ostream.flush() | ||
self.ostream.write(self.text(etype, value, elist)) | ||||
self.ostream.write('\n') | ||||
|
r1234 | |||
|
r25370 | def _extract_tb(self, tb): | ||
if tb: | ||||
return traceback.extract_tb(tb) | ||||
else: | ||||
return None | ||||
|
r27361 | def structured_traceback( | ||
self, | ||||
etype: type, | ||||
|
r28167 | evalue: Optional[BaseException], | ||
|
r27361 | etb: Optional[TracebackType] = None, | ||
tb_offset: Optional[int] = None, | ||||
context=5, | ||||
): | ||||
|
r2440 | """Return a color formatted string with the traceback info. | ||
Parameters | ||||
---------- | ||||
etype : exception type | ||||
|
r25951 | Type of the exception raised. | ||
|
r25301 | evalue : object | ||
|
r25951 | Data stored in the exception | ||
|
r27361 | etb : list | TracebackType | None | ||
|
r25951 | If list: List of frames, see class docstring for details. | ||
If Traceback: Traceback of the exception. | ||||
|
r2838 | tb_offset : int, optional | ||
|
r25951 | Number of frames in the traceback to skip. If not given, the | ||
instance evalue is used (set in constructor). | ||||
|
r2838 | context : int, optional | ||
|
r25951 | Number of lines of context information to print. | ||
|
r2838 | |||
|
r2440 | Returns | ||
------- | ||||
String with formatted exception. | ||||
""" | ||||
|
r25307 | # This is a workaround to get chained_exc_ids in recursive calls | ||
# etb should not be a tuple if structured_traceback is not recursive | ||||
|
r25303 | if isinstance(etb, tuple): | ||
etb, chained_exc_ids = etb | ||||
else: | ||||
chained_exc_ids = set() | ||||
|
r25307 | |||
|
r25301 | if isinstance(etb, list): | ||
elist = etb | ||||
elif etb is not None: | ||||
elist = self._extract_tb(etb) | ||||
else: | ||||
elist = [] | ||||
|
r2838 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | ||
|
r27361 | assert isinstance(tb_offset, int) | ||
|
r1234 | Colors = self.Colors | ||
|
r2838 | out_list = [] | ||
|
r1234 | if elist: | ||
|
r2838 | |||
if tb_offset and len(elist) > tb_offset: | ||||
elist = elist[tb_offset:] | ||||
|
r4872 | |||
|
r2838 | out_list.append('Traceback %s(most recent call last)%s:' % | ||
|
r17156 | (Colors.normalEm, Colors.Normal) + '\n') | ||
|
r2838 | out_list.extend(self._format_list(elist)) | ||
# The exception info should be a single entry in the list. | ||||
|
r25301 | lines = ''.join(self._format_exception_only(etype, evalue)) | ||
|
r2838 | out_list.append(lines) | ||
|
r25301 | exception = self.get_parts_of_chained_exception(evalue) | ||
|
r28208 | if exception and (id(exception[1]) not in chained_exc_ids): | ||
|
r28167 | chained_exception_message = ( | ||
self.prepare_chained_exception_message(evalue.__cause__)[0] | ||||
if evalue is not None | ||||
else "" | ||||
) | ||||
|
r25301 | etype, evalue, etb = exception | ||
|
r25306 | # Trace exception to avoid infinite 'cause' loop | ||
chained_exc_ids.add(id(exception[1])) | ||||
|
r25301 | chained_exceptions_tb_offset = 0 | ||
|
r25306 | out_list = ( | ||
self.structured_traceback( | ||||
|
r28208 | etype, | ||
evalue, | ||||
(etb, chained_exc_ids), # type: ignore | ||||
chained_exceptions_tb_offset, | ||||
context, | ||||
) | ||||
|
r25306 | + chained_exception_message | ||
+ out_list) | ||||
|
r25301 | |||
|
r2838 | return out_list | ||
|
r1234 | |||
def _format_list(self, extracted_list): | ||||
"""Format a list of traceback entry tuples for printing. | ||||
Given a list of tuples as returned by extract_tb() or | ||||
extract_stack(), return a list of strings ready for printing. | ||||
Each string in the resulting list corresponds to the item with the | ||||
same index in the argument list. Each string ends in a newline; | ||||
the strings may contain internal newlines as well, for those items | ||||
whose source text line is not None. | ||||
|
r4872 | |||
|
r1234 | Lifted almost verbatim from traceback.py | ||
""" | ||||
Colors = self.Colors | ||||
|
r28278 | output_list = [] | ||
|
r27686 | for ind, (filename, lineno, name, line) in enumerate(extracted_list): | ||
normalCol, nameCol, fileCol, lineCol = ( | ||||
# Emphasize the last entry | ||||
(Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line) | ||||
if ind == len(extracted_list) - 1 | ||||
else (Colors.Normal, Colors.name, Colors.filename, "") | ||||
|
r26689 | ) | ||
|
r27686 | |||
fns = _format_filename(filename, fileCol, normalCol, lineno=lineno) | ||||
item = f"{normalCol} {fns}" | ||||
if name != "<module>": | ||||
item += f" in {nameCol}{name}{normalCol}\n" | ||||
else: | ||||
item += "\n" | ||||
|
r1234 | if line: | ||
|
r27686 | item += f"{lineCol} {line.strip()}{normalCol}\n" | ||
|
r28278 | output_list.append(item) | ||
|
r27686 | |||
|
r28278 | return output_list | ||
|
r4872 | |||
|
r1234 | def _format_exception_only(self, etype, value): | ||
"""Format the exception part of a traceback. | ||||
The arguments are the exception type and value such as given by | ||||
sys.exc_info()[:2]. The return value is a list of strings, each ending | ||||
in a newline. Normally, the list contains a single string; however, | ||||
for SyntaxError exceptions, it contains several lines that (when | ||||
printed) display detailed information about where the syntax error | ||||
occurred. The message indicating which exception occurred is the | ||||
always last string in the list. | ||||
|
r4872 | |||
|
r1234 | Also lifted nearly verbatim from traceback.py | ||
""" | ||||
|
r1568 | have_filedata = False | ||
|
r1234 | Colors = self.Colors | ||
|
r28278 | output_list = [] | ||
|
r23671 | stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal) | ||
|
r1234 | if value is None: | ||
|
r5827 | # Not sure if this can still happen in Python 2.6 and above | ||
|
r28278 | output_list.append(stype + "\n") | ||
|
r1234 | else: | ||
|
r8417 | if issubclass(etype, SyntaxError): | ||
|
r5827 | have_filedata = True | ||
if not value.filename: value.filename = "<string>" | ||||
|
r8590 | if value.lineno: | ||
lineno = value.lineno | ||||
|
r23117 | textline = linecache.getline(value.filename, value.lineno) | ||
|
r8590 | else: | ||
|
r26689 | lineno = "unknown" | ||
textline = "" | ||||
|
r28278 | output_list.append( | ||
|
r27356 | "%s %s%s\n" | ||
|
r26689 | % ( | ||
Colors.normalEm, | ||||
|
r26691 | _format_filename( | ||
|
r27356 | value.filename, | ||
Colors.filenameEm, | ||||
Colors.normalEm, | ||||
lineno=(None if lineno == "unknown" else lineno), | ||||
|
r26691 | ), | ||
|
r26689 | Colors.Normal, | ||
) | ||||
) | ||||
if textline == "": | ||||
|
r23671 | textline = py3compat.cast_unicode(value.text, "utf-8") | ||
|
r8328 | |||
if textline is not None: | ||||
|
r5827 | i = 0 | ||
|
r8328 | while i < len(textline) and textline[i].isspace(): | ||
|
r5831 | i += 1 | ||
|
r28278 | output_list.append( | ||
"%s %s%s\n" % (Colors.line, textline.strip(), Colors.Normal) | ||||
) | ||||
|
r5827 | if value.offset is not None: | ||
s = ' ' | ||||
|
r17156 | for c in textline[i:value.offset - 1]: | ||
|
r5827 | if c.isspace(): | ||
|
r5831 | s += c | ||
|
r5827 | else: | ||
|
r28278 | s += " " | ||
output_list.append( | ||||
"%s%s^%s\n" % (Colors.caret, s, Colors.Normal) | ||||
) | ||||
|
r5827 | |||
try: | ||||
s = value.msg | ||||
except Exception: | ||||
s = self._some_str(value) | ||||
|
r1234 | if s: | ||
|
r28278 | output_list.append( | ||
"%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s) | ||||
) | ||||
|
r1234 | else: | ||
|
r28278 | output_list.append("%s\n" % stype) | ||
|
r1532 | |||
|
r28277 | # PEP-678 notes | ||
|
r28278 | output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", [])) | ||
|
r28277 | |||
|
r2440 | # sync with user hooks | ||
|
r2088 | if have_filedata: | ||
|
r10581 | ipinst = get_ipython() | ||
|
r2124 | if ipinst is not None: | ||
|
r5827 | ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0) | ||
|
r1532 | |||
|
r28278 | return output_list | ||
|
r1234 | |||
|
r2838 | def get_exception_only(self, etype, value): | ||
"""Only print the exception type and message, without a traceback. | ||||
|
r4872 | |||
|
r2838 | Parameters | ||
---------- | ||||
etype : exception type | ||||
|
r27288 | value : exception value | ||
|
r2838 | """ | ||
|
r25301 | return ListTB.structured_traceback(self, etype, value) | ||
|
r2838 | |||
|
r2873 | def show_exception_only(self, etype, evalue): | ||
|
r2440 | """Only print the exception type and message, without a traceback. | ||
|
r4872 | |||
|
r2440 | Parameters | ||
---------- | ||||
etype : exception type | ||||
|
r25951 | evalue : exception value | ||
|
r2440 | """ | ||
# This method needs to use __call__ from *this* class, not the one from | ||||
# a subclass whose signature or behavior may be different | ||||
|
r2852 | ostream = self.ostream | ||
|
r2775 | ostream.flush() | ||
|
r2838 | ostream.write('\n'.join(self.get_exception_only(etype, evalue))) | ||
|
r2775 | ostream.flush() | ||
|
r2440 | |||
|
r1234 | def _some_str(self, value): | ||
# Lifted from traceback.py | ||||
try: | ||||
|
r23671 | return py3compat.cast_unicode(str(value)) | ||
|
r1234 | except: | ||
|
r22269 | return u'<unprintable %s object>' % type(value).__name__ | ||
|
r1234 | |||
|
r17156 | |||
|
r28123 | class FrameInfo: | ||
""" | ||||
Mirror of stack data's FrameInfo, but so that we can bypass highlighting on | ||||
really long frames. | ||||
""" | ||||
|
r28124 | description: Optional[str] | ||
|
r28208 | filename: Optional[str] | ||
lineno: Tuple[int] | ||||
# number of context lines to use | ||||
context: Optional[int] | ||||
|
r28653 | raw_lines: List[str] | ||
|
r28123 | |||
@classmethod | ||||
def _from_stack_data_FrameInfo(cls, frame_info): | ||||
return cls( | ||||
getattr(frame_info, "description", None), | ||||
|
r28208 | getattr(frame_info, "filename", None), # type: ignore[arg-type] | ||
getattr(frame_info, "lineno", None), # type: ignore[arg-type] | ||||
|
r28123 | getattr(frame_info, "frame", None), | ||
getattr(frame_info, "code", None), | ||||
sd=frame_info, | ||||
|
r28208 | context=None, | ||
|
r28123 | ) | ||
|
r28208 | def __init__( | ||
self, | ||||
description: Optional[str], | ||||
filename: str, | ||||
lineno: Tuple[int], | ||||
frame, | ||||
code, | ||||
*, | ||||
sd=None, | ||||
context=None, | ||||
): | ||||
|
r28123 | self.description = description | ||
self.filename = filename | ||||
self.lineno = lineno | ||||
self.frame = frame | ||||
self.code = code | ||||
self._sd = sd | ||||
|
r28208 | self.context = context | ||
|
r28123 | |||
# self.lines = [] | ||||
if sd is None: | ||||
|
r28653 | try: | ||
# return a list of source lines and a starting line number | ||||
self.raw_lines = inspect.getsourcelines(frame)[0] | ||||
except OSError: | ||||
self.raw_lines = [ | ||||
"'Could not get source, probably due dynamically evaluated source code.'" | ||||
] | ||||
|
r28123 | |||
@property | ||||
def variables_in_executing_piece(self): | ||||
if self._sd: | ||||
return self._sd.variables_in_executing_piece | ||||
else: | ||||
return [] | ||||
@property | ||||
def lines(self): | ||||
|
r28572 | from executing.executing import NotOneValueFound | ||
try: | ||||
return self._sd.lines | ||||
except NotOneValueFound: | ||||
class Dummy: | ||||
lineno = 0 | ||||
is_current = False | ||||
def render(self, *, pygmented): | ||||
return "<Error retrieving source code with stack_data see ipython/ipython#13598>" | ||||
return [Dummy()] | ||||
|
r28123 | |||
@property | ||||
def executing(self): | ||||
if self._sd: | ||||
return self._sd.executing | ||||
else: | ||||
return None | ||||
|
r28124 | # ---------------------------------------------------------------------------- | ||
|
r1234 | class VerboseTB(TBTools): | ||
"""A port of Ka-Ping Yee's cgitb.py module that outputs color text instead | ||||
of HTML. Requires inspect and pydoc. Crazy, man. | ||||
Modified version which optionally strips the topmost entries from the | ||||
traceback, to be used with alternate interpreters (because their own code | ||||
would appear in the traceback).""" | ||||
|
r27793 | _tb_highlight = "bg:ansiyellow" | ||
|
r28361 | _tb_highlight_style = "default" | ||
|
r27793 | |||
|
r27361 | def __init__( | ||
self, | ||||
color_scheme: str = "Linux", | ||||
call_pdb: bool = False, | ||||
ostream=None, | ||||
tb_offset: int = 0, | ||||
long_header: bool = False, | ||||
include_vars: bool = True, | ||||
check_cache=None, | ||||
debugger_cls=None, | ||||
parent=None, | ||||
config=None, | ||||
): | ||||
|
r1234 | """Specify traceback offset, headers and color scheme. | ||
Define how many frames to drop from the tracebacks. Calling it with | ||||
tb_offset=1 allows use of this handler in interpreters which will have | ||||
their own code at the top of the traceback (VerboseTB will first | ||||
remove that frame before printing the traceback info).""" | ||||
|
r27603 | TBTools.__init__( | ||
self, | ||||
color_scheme=color_scheme, | ||||
call_pdb=call_pdb, | ||||
ostream=ostream, | ||||
parent=parent, | ||||
config=config, | ||||
debugger_cls=debugger_cls, | ||||
) | ||||
|
r1234 | self.tb_offset = tb_offset | ||
self.long_header = long_header | ||||
self.include_vars = include_vars | ||||
|
r3175 | # By default we use linecache.checkcache, but the user can provide a | ||
|
r27845 | # different check_cache implementation. This was formerly used by the | ||
# IPython kernel for interactive code, but is no longer necessary. | ||||
|
r3175 | if check_cache is None: | ||
check_cache = linecache.checkcache | ||||
self.check_cache = check_cache | ||||
|
r1234 | |||
|
r25839 | self.skip_hidden = True | ||
|
r22391 | |||
|
r28124 | def format_record(self, frame_info: FrameInfo): | ||
|
r21983 | """Format a single stack frame""" | ||
|
r28123 | assert isinstance(frame_info, FrameInfo) | ||
|
r17156 | Colors = self.Colors # just a shorthand + quicker name lookup | ||
ColorsNormal = Colors.Normal # used a lot | ||||
|
r25467 | |||
|
r28123 | if isinstance(frame_info._sd, stack_data.RepeatedFrames): | ||
|
r25467 | return ' %s[... skipping similar frames: %s]%s\n' % ( | ||
Colors.excName, frame_info.description, ColorsNormal) | ||||
|
r26986 | indent = " " * INDENT_SIZE | ||
em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal) | ||||
|
r27356 | tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}" | ||
tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % ( | ||||
|
r26986 | Colors.vName, | ||
Colors.valEm, | ||||
ColorsNormal, | ||||
) | ||||
tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal) | ||||
|
r17157 | |||
|
r27356 | link = _format_filename( | ||
frame_info.filename, | ||||
Colors.filenameEm, | ||||
ColorsNormal, | ||||
lineno=frame_info.lineno, | ||||
) | ||||
|
r25466 | args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame) | ||
|
r28124 | if frame_info.executing is not None: | ||
|
r28123 | func = frame_info.executing.code_qualname() | ||
else: | ||||
|
r28124 | func = "?" | ||
|
r27356 | if func == "<module>": | ||
|
r27686 | call = "" | ||
|
r21983 | else: | ||
# Decide whether to include variable details or not | ||||
|
r24336 | var_repr = eqrepr if self.include_vars else nullrepr | ||
|
r21983 | try: | ||
|
r27356 | scope = inspect.formatargvalues( | ||
args, varargs, varkw, locals_, formatvalue=var_repr | ||||
) | ||||
call = tpl_call.format(file=func, scope=scope) | ||||
|
r21983 | except KeyError: | ||
# This happens in situations like errors inside generator | ||||
# expressions, where local variables are listed in the | ||||
# line, but can't be extracted from the frame. I'm not | ||||
# 100% sure this isn't actually a bug in inspect itself, | ||||
# but since there's no info for us to compute with, the | ||||
# best we can do is report the failure and move on. Here | ||||
# we must *not* call any traceback construction again, | ||||
# because that would mess up use of %debug later on. So we | ||||
# simply report the failure and move on. The only | ||||
# limitation will be that this frame won't have locals | ||||
# listed in the call signature. Quite subtle problem... | ||||
# I can't think of a good way to validate this in a unit | ||||
# test, but running a script consisting of: | ||||
# dict( (k,v.strip()) for (k,v) in range(10) ) | ||||
# will illustrate the error, if this exception catch is | ||||
# disabled. | ||||
call = tpl_call_fail % func | ||||
|
r24334 | lvals = '' | ||
lvals_list = [] | ||||
|
r21983 | if self.include_vars: | ||
|
r26712 | try: | ||
# we likely want to fix stackdata at some point, but | ||||
# still need a workaround. | ||||
fibp = frame_info.variables_in_executing_piece | ||||
for var in fibp: | ||||
lvals_list.append(tpl_name_val % (var.name, repr(var.value))) | ||||
except Exception: | ||||
lvals_list.append( | ||||
"Exception trying to inspect frame. No more locals available." | ||||
) | ||||
|
r24334 | if lvals_list: | ||
lvals = '%s%s' % (indent, em_normal.join(lvals_list)) | ||||
|
r1234 | |||
|
r27686 | result = f'{link}{", " if call else ""}{call}\n' | ||
|
r28123 | if frame_info._sd is None: | ||
# fast fallback if file is too long | ||||
|
r28124 | tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal) | ||
|
r28123 | link = tpl_link % util_path.compress_user(frame_info.filename) | ||
|
r28124 | level = "%s %s\n" % (link, call) | ||
_line_format = PyColorize.Parser( | ||||
style=self.color_scheme_table.active_scheme_name, parent=self | ||||
).format2 | ||||
|
r28123 | first_line = frame_info.code.co_firstlineno | ||
current_line = frame_info.lineno[0] | ||||
|
r28208 | raw_lines = frame_info.raw_lines | ||
index = current_line - first_line | ||||
if index >= frame_info.context: | ||||
start = max(index - frame_info.context, 0) | ||||
stop = index + frame_info.context | ||||
index = frame_info.context | ||||
else: | ||||
start = 0 | ||||
stop = index + frame_info.context | ||||
raw_lines = raw_lines[start:stop] | ||||
|
r28124 | return "%s%s" % ( | ||
level, | ||||
"".join( | ||||
_simple_format_traceback_lines( | ||||
current_line, | ||||
|
r28208 | index, | ||
raw_lines, | ||||
|
r28124 | Colors, | ||
lvals, | ||||
_line_format, | ||||
) | ||||
), | ||||
) | ||||
# result += "\n".join(frame_info.raw_lines) | ||||
|
r28123 | else: | ||
result += "".join( | ||||
_format_traceback_lines( | ||||
frame_info.lines, Colors, self.has_colors, lvals | ||||
) | ||||
) | ||||
|
r25466 | return result | ||
|
r17157 | |||
|
r28167 | def prepare_header(self, etype: str, long_version: bool = False): | ||
|
r17157 | colors = self.Colors # just a shorthand + quicker name lookup | ||
colorsnormal = colors.Normal # used a lot | ||||
exc = '%s%s%s' % (colors.excName, etype, colorsnormal) | ||||
|
r22150 | width = min(75, get_terminal_size()[0]) | ||
|
r17157 | if long_version: | ||
# Header with the exception type, python version, and date | ||||
pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable | ||||
date = time.ctime(time.time()) | ||||
|
r28167 | head = "%s%s%s\n%s%s%s\n%s" % ( | ||
colors.topline, | ||||
"-" * width, | ||||
colorsnormal, | ||||
exc, | ||||
" " * (width - len(etype) - len(pyver)), | ||||
pyver, | ||||
date.rjust(width), | ||||
) | ||||
head += ( | ||||
"\nA problem occurred executing Python code. Here is the sequence of function" | ||||
"\ncalls leading up to the error, with the most recent (innermost) call last." | ||||
) | ||||
|
r17157 | else: | ||
# Simplified header | ||||
|
r28167 | head = "%s%s" % ( | ||
exc, | ||||
"Traceback (most recent call last)".rjust(width - len(etype)), | ||||
) | ||||
|
r17157 | |||
return head | ||||
def format_exception(self, etype, evalue): | ||||
colors = self.Colors # just a shorthand + quicker name lookup | ||||
colorsnormal = colors.Normal # used a lot | ||||
|
r1234 | # Get (safely) a string form of the exception info | ||
try: | ||||
|
r17156 | etype_str, evalue_str = map(str, (etype, evalue)) | ||
|
r1234 | except: | ||
# User exception is improperly defined. | ||||
|
r17156 | etype, evalue = str, sys.exc_info()[:2] | ||
etype_str, evalue_str = map(str, (etype, evalue)) | ||||
|
r28242 | |||
|
r28278 | # PEP-678 notes | ||
|
r28242 | notes = getattr(evalue, "__notes__", []) | ||
if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)): | ||||
notes = [_safe_string(notes, "__notes__", func=repr)] | ||||
|
r1234 | # ... and format it | ||
|
r28242 | return [ | ||
"{}{}{}: {}".format( | ||||
colors.excName, | ||||
etype_str, | ||||
colorsnormal, | ||||
py3compat.cast_unicode(evalue_str), | ||||
), | ||||
*( | ||||
"{}{}".format( | ||||
colorsnormal, _safe_string(py3compat.cast_unicode(n), "note") | ||||
) | ||||
for n in notes | ||||
), | ||||
] | ||||
|
r1532 | |||
|
r27361 | def format_exception_as_a_whole( | ||
self, | ||||
etype: type, | ||||
|
r28167 | evalue: Optional[BaseException], | ||
|
r27591 | etb: Optional[TracebackType], | ||
|
r27361 | number_of_lines_of_context, | ||
tb_offset: Optional[int], | ||||
): | ||||
|
r21983 | """Formats the header, traceback and exception message for a single exception. | ||
This may be called multiple times by Python 3 exception chaining | ||||
(PEP 3134). | ||||
""" | ||||
|
r17158 | # some locals | ||
|
r21983 | orig_etype = etype | ||
|
r17158 | try: | ||
|
r28208 | etype = etype.__name__ # type: ignore | ||
|
r17158 | except AttributeError: | ||
pass | ||||
tb_offset = self.tb_offset if tb_offset is None else tb_offset | ||||
|
r27361 | assert isinstance(tb_offset, int) | ||
|
r28208 | head = self.prepare_header(str(etype), self.long_header) | ||
|
r27591 | records = ( | ||
self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else [] | ||||
) | ||||
|
r17158 | |||
|
r25839 | frames = [] | ||
skipped = 0 | ||||
|
r26053 | lastrecord = len(records) - 1 | ||
|
r28123 | for i, record in enumerate(records): | ||
|
r28124 | if ( | ||
not isinstance(record._sd, stack_data.RepeatedFrames) | ||||
and self.skip_hidden | ||||
): | ||||
if ( | ||||
record.frame.f_locals.get("__tracebackhide__", 0) | ||||
and i != lastrecord | ||||
): | ||||
|
r25839 | skipped += 1 | ||
continue | ||||
if skipped: | ||||
Colors = self.Colors # just a shorthand + quicker name lookup | ||||
ColorsNormal = Colors.Normal # used a lot | ||||
frames.append( | ||||
" %s[... skipping hidden %s frame]%s\n" | ||||
% (Colors.excName, skipped, ColorsNormal) | ||||
) | ||||
skipped = 0 | ||||
|
r28123 | frames.append(self.format_record(record)) | ||
|
r25845 | if skipped: | ||
Colors = self.Colors # just a shorthand + quicker name lookup | ||||
ColorsNormal = Colors.Normal # used a lot | ||||
frames.append( | ||||
" %s[... skipping hidden %s frame]%s\n" | ||||
% (Colors.excName, skipped, ColorsNormal) | ||||
) | ||||
|
r21751 | |||
|
r17158 | formatted_exception = self.format_exception(etype, evalue) | ||
if records: | ||||
|
r25466 | frame_info = records[-1] | ||
|
r17158 | ipinst = get_ipython() | ||
if ipinst is not None: | ||||
|
r25466 | ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0) | ||
|
r17158 | |||
|
r28242 | return [[head] + frames + formatted_exception] | ||
|
r17158 | |||
|
r27361 | def get_records( | ||
self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int | ||||
): | ||||
|
r27591 | assert etb is not None | ||
|
r25466 | context = number_of_lines_of_context - 1 | ||
after = context // 2 | ||||
before = context - after | ||||
|
r25506 | if self.has_colors: | ||
|
r28361 | style = get_style_by_name(self._tb_highlight_style) | ||
|
r27793 | style = stack_data.style_with_executing_node(style, self._tb_highlight) | ||
|
r25548 | formatter = Terminal256Formatter(style=style) | ||
|
r25506 | else: | ||
formatter = None | ||||
options = stack_data.Options( | ||||
before=before, | ||||
after=after, | ||||
pygments_formatter=formatter, | ||||
) | ||||
|
r28123 | |||
|
r28157 | # Let's estimate the amount of code we will have to parse/highlight. | ||
|
r28167 | cf: Optional[TracebackType] = etb | ||
|
r28123 | max_len = 0 | ||
tbs = [] | ||||
while cf is not None: | ||||
|
r28158 | try: | ||
|
r28208 | mod = inspect.getmodule(cf.tb_frame) | ||
if mod is not None: | ||||
mod_name = mod.__name__ | ||||
root_name, *_ = mod_name.split(".") | ||||
if root_name == "IPython": | ||||
cf = cf.tb_next | ||||
continue | ||||
max_len = get_line_number_of_frame(cf.tb_frame) | ||||
|
r28158 | except OSError: | ||
|
r28208 | max_len = 0 | ||
max_len = max(max_len, max_len) | ||||
|
r28123 | tbs.append(cf) | ||
|
r28208 | cf = getattr(cf, "tb_next", None) | ||
|
r28123 | |||
if max_len > FAST_THRESHOLD: | ||||
FIs = [] | ||||
for tb in tbs: | ||||
|
r28208 | frame = tb.tb_frame # type: ignore | ||
|
r28124 | lineno = (frame.f_lineno,) | ||
|
r28123 | code = frame.f_code | ||
filename = code.co_filename | ||||
|
r28208 | # TODO: Here we need to use before/after/ | ||
FIs.append( | ||||
FrameInfo( | ||||
"Raw frame", filename, lineno, frame, code, context=context | ||||
) | ||||
) | ||||
|
r28123 | return FIs | ||
res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:] | ||||
res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res] | ||||
return res | ||||
|
r4872 | |||
|
r27361 | def structured_traceback( | ||
self, | ||||
etype: type, | ||||
evalue: Optional[BaseException], | ||||
|
r28208 | etb: Optional[TracebackType] = None, | ||
|
r27361 | tb_offset: Optional[int] = None, | ||
number_of_lines_of_context: int = 5, | ||||
): | ||||
|
r17157 | """Return a nice text document describing the traceback.""" | ||
|
r17162 | formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context, | ||
tb_offset) | ||||
|
r17157 | |||
|
r17163 | colors = self.Colors # just a shorthand + quicker name lookup | ||
colorsnormal = colors.Normal # used a lot | ||||
|
r22150 | head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal) | ||
|
r17163 | structured_traceback_parts = [head] | ||
|
r24010 | chained_exceptions_tb_offset = 0 | ||
lines_of_context = 3 | ||||
formatted_exceptions = formatted_exception | ||||
exception = self.get_parts_of_chained_exception(evalue) | ||||
if exception: | ||||
|
r27361 | assert evalue is not None | ||
|
r24010 | formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) | ||
etype, evalue, etb = exception | ||||
else: | ||||
evalue = None | ||||
chained_exc_ids = set() | ||||
while evalue: | ||||
formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context, | ||||
chained_exceptions_tb_offset) | ||||
|
r17164 | exception = self.get_parts_of_chained_exception(evalue) | ||
|
r24010 | |||
if exception and not id(exception[1]) in chained_exc_ids: | ||||
chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop | ||||
|
r17162 | formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) | ||
etype, evalue, etb = exception | ||||
else: | ||||
evalue = None | ||||
|
r17157 | |||
|
r24010 | # we want to see exceptions in a reversed order: | ||
# the first exception should be on top | ||||
for formatted_exception in reversed(formatted_exceptions): | ||||
structured_traceback_parts += formatted_exception | ||||
|
r17162 | |||
|
r17157 | return structured_traceback_parts | ||
|
r4872 | |||
|
r27361 | def debugger(self, force: bool = False): | ||
|
r1234 | """Call up the pdb debugger if desired, always clean up the tb | ||
reference. | ||||
Keywords: | ||||
- force(False): by default, this routine checks the instance call_pdb | ||||
|
r12553 | flag and does not actually invoke the debugger if the flag is false. | ||
The 'force' option forces the debugger to activate even if the flag | ||||
is false. | ||||
|
r1234 | |||
If the call_pdb flag is set, the pdb interactive debugger is | ||||
invoked. In all cases, the self.tb reference to the current traceback | ||||
is deleted to prevent lingering references which hamper memory | ||||
management. | ||||
Note that each call to pdb() does an 'import readline', so if your app | ||||
requires a special setup for the readline completers, you'll have to | ||||
fix that by hand after invoking the exception handler.""" | ||||
if force or self.call_pdb: | ||||
if self.pdb is None: | ||||
|
r22911 | self.pdb = self.debugger_cls() | ||
|
r1234 | # the system displayhook may have changed, restore the original | ||
# for pdb | ||||
|
r2816 | display_trap = DisplayTrap(hook=sys.__displayhook__) | ||
|
r2231 | with display_trap: | ||
self.pdb.reset() | ||||
# Find the right frame so we don't pop up inside ipython itself | ||||
|
r28208 | if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type] | ||
etb = self.tb # type: ignore[has-type] | ||||
|
r2231 | else: | ||
etb = self.tb = sys.last_traceback | ||||
|
r2438 | while self.tb is not None and self.tb.tb_next is not None: | ||
|
r27361 | assert self.tb.tb_next is not None | ||
|
r2231 | self.tb = self.tb.tb_next | ||
|
r1234 | if etb and etb.tb_next: | ||
etb = etb.tb_next | ||||
self.pdb.botframe = etb.tb_frame | ||||
|
r28377 | # last_value should be deprecated, but last-exc sometimme not set | ||
# please check why later and remove the getattr. | ||||
exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value) # type: ignore[attr-defined] | ||||
if exc: | ||||
self.pdb.interaction(None, exc) | ||||
else: | ||||
self.pdb.interaction(None, etb) | ||||
|
r2231 | |||
|
r17156 | if hasattr(self, 'tb'): | ||
|
r1234 | del self.tb | ||
def handler(self, info=None): | ||||
(etype, evalue, etb) = info or sys.exc_info() | ||||
self.tb = etb | ||||
|
r2852 | ostream = self.ostream | ||
ostream.flush() | ||||
ostream.write(self.text(etype, evalue, etb)) | ||||
ostream.write('\n') | ||||
ostream.flush() | ||||
|
r1234 | |||
# Changed so an instance can just be called as VerboseTB_inst() and print | ||||
# out the right info on its own. | ||||
def __call__(self, etype=None, evalue=None, etb=None): | ||||
"""This hook can replace sys.excepthook (for Python 2.1 or higher).""" | ||||
if etb is None: | ||||
self.handler() | ||||
else: | ||||
self.handler((etype, evalue, etb)) | ||||
try: | ||||
self.debugger() | ||||
except KeyboardInterrupt: | ||||
|
r13348 | print("\nKeyboardInterrupt") | ||
|
r1234 | |||
|
r17156 | |||
|
r1234 | #---------------------------------------------------------------------------- | ||
|
r2838 | class FormattedTB(VerboseTB, ListTB): | ||
|
r1234 | """Subclass ListTB but allow calling with a traceback. | ||
It can thus be used as a sys.excepthook for Python > 2.1. | ||||
Also adds 'Context' and 'Verbose' modes, not available in ListTB. | ||||
Allows a tb_offset to be specified. This is useful for situations where | ||||
one needs to remove a number of topmost frames from the traceback (such as | ||||
occurs with python programs that themselves execute other python code, | ||||
like Python shells). """ | ||||
|
r4872 | |||
|
r27361 | mode: str | ||
|
r2852 | def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False, | ||
|
r4872 | ostream=None, | ||
|
r3175 | tb_offset=0, long_header=False, include_vars=False, | ||
|
r22943 | check_cache=None, debugger_cls=None, | ||
parent=None, config=None): | ||||
|
r1234 | |||
# NEVER change the order of this list. Put new modes at the end: | ||||
|
r24849 | self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal'] | ||
|
r1234 | self.verbose_modes = self.valid_modes[1:3] | ||
|
r2852 | VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, | ||
ostream=ostream, tb_offset=tb_offset, | ||||
|
r3175 | long_header=long_header, include_vars=include_vars, | ||
|
r22943 | check_cache=check_cache, debugger_cls=debugger_cls, | ||
parent=parent, config=config) | ||||
|
r2839 | |||
# Different types of tracebacks are joined with different separators to | ||||
# form a single string. They are taken from this dict | ||||
|
r24849 | self._join_chars = dict(Plain='', Context='\n', Verbose='\n', | ||
Minimal='') | ||||
|
r2839 | # set_mode also sets the tb_join_char attribute | ||
|
r1234 | self.set_mode(mode) | ||
|
r4872 | |||
|
r17157 | def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5): | ||
|
r2838 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | ||
|
r2839 | mode = self.mode | ||
|
r1234 | if mode in self.verbose_modes: | ||
|
r2792 | # Verbose modes need a full traceback | ||
return VerboseTB.structured_traceback( | ||||
|
r17157 | self, etype, value, tb, tb_offset, number_of_lines_of_context | ||
|
r2792 | ) | ||
|
r24849 | elif mode == 'Minimal': | ||
return ListTB.get_exception_only(self, etype, value) | ||||
|
r1234 | else: | ||
# We must check the source cache because otherwise we can print | ||||
# out-of-date source code. | ||||
|
r3175 | self.check_cache() | ||
|
r1234 | # Now we can extract and format the exception | ||
|
r2792 | return ListTB.structured_traceback( | ||
|
r25301 | self, etype, value, tb, tb_offset, number_of_lines_of_context | ||
|
r2792 | ) | ||
|
r2839 | def stb2text(self, stb): | ||
"""Convert a structured traceback (a list) to a string.""" | ||||
return self.tb_join_char.join(stb) | ||||
|
r4872 | |||
|
r27361 | def set_mode(self, mode: Optional[str] = None): | ||
|
r1234 | """Switch to the desired mode. | ||
If mode is not specified, cycles through the available modes.""" | ||||
if not mode: | ||||
|
r17157 | new_idx = (self.valid_modes.index(self.mode) + 1 ) % \ | ||
|
r1234 | len(self.valid_modes) | ||
self.mode = self.valid_modes[new_idx] | ||||
elif mode not in self.valid_modes: | ||||
|
r27361 | raise ValueError( | ||
"Unrecognized mode in FormattedTB: <" + mode + ">\n" | ||||
"Valid modes: " + str(self.valid_modes) | ||||
) | ||||
|
r1234 | else: | ||
|
r27361 | assert isinstance(mode, str) | ||
|
r1234 | self.mode = mode | ||
# include variable details only in 'Verbose' mode | ||||
self.include_vars = (self.mode == self.valid_modes[2]) | ||||
|
r2839 | # Set the join character for generating text tracebacks | ||
|
r3146 | self.tb_join_char = self._join_chars[self.mode] | ||
|
r1234 | |||
|
r17156 | # some convenient shortcuts | ||
|
r1234 | def plain(self): | ||
self.set_mode(self.valid_modes[0]) | ||||
def context(self): | ||||
self.set_mode(self.valid_modes[1]) | ||||
def verbose(self): | ||||
self.set_mode(self.valid_modes[2]) | ||||
|
r24849 | def minimal(self): | ||
self.set_mode(self.valid_modes[3]) | ||||
|
r17156 | |||
|
r1234 | #---------------------------------------------------------------------------- | ||
class AutoFormattedTB(FormattedTB): | ||||
"""A traceback printer which can be called on the fly. | ||||
It will find out about exceptions by itself. | ||||
|
r9244 | A brief example:: | ||
|
r4872 | |||
|
r9244 | AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux') | ||
try: | ||||
... | ||||
except: | ||||
AutoTB() # or AutoTB(out=logfile) where logfile is an open file object | ||||
|
r1234 | """ | ||
|
r4872 | |||
|
r17156 | def __call__(self, etype=None, evalue=None, etb=None, | ||
out=None, tb_offset=None): | ||||
|
r1234 | """Print out a formatted exception traceback. | ||
Optional arguments: | ||||
- out: an open file-like object to direct output to. | ||||
- tb_offset: the number of frames to skip over in the stack, on a | ||||
per-call basis (this overrides temporarily the instance's tb_offset | ||||
|
r25951 | given at initialization time.""" | ||
|
r2440 | |||
|
r1234 | if out is None: | ||
|
r2852 | out = self.ostream | ||
|
r2775 | out.flush() | ||
|
r2838 | out.write(self.text(etype, evalue, etb, tb_offset)) | ||
out.write('\n') | ||||
|
r1234 | out.flush() | ||
|
r2838 | # FIXME: we should remove the auto pdb behavior from here and leave | ||
# that to the clients. | ||||
|
r1234 | try: | ||
self.debugger() | ||||
except KeyboardInterrupt: | ||||
|
r13348 | print("\nKeyboardInterrupt") | ||
|
r1234 | |||
|
r28124 | def structured_traceback( | ||
self, | ||||
|
r28208 | etype: type, | ||
evalue: Optional[BaseException], | ||||
etb: Optional[TracebackType] = None, | ||||
tb_offset: Optional[int] = None, | ||||
number_of_lines_of_context: int = 5, | ||||
|
r28124 | ): | ||
|
r27361 | # tb: TracebackType or tupleof tb types ? | ||
|
r1234 | if etype is None: | ||
|
r28208 | etype, evalue, etb = sys.exc_info() | ||
if isinstance(etb, tuple): | ||||
|
r25370 | # tb is a tuple if this is a chained exception. | ||
|
r28208 | self.tb = etb[0] | ||
|
r25371 | else: | ||
|
r28208 | self.tb = etb | ||
|
r2792 | return FormattedTB.structured_traceback( | ||
|
r28208 | self, etype, evalue, etb, tb_offset, number_of_lines_of_context | ||
) | ||||
|
r1234 | |||
|
r17156 | |||
|
r1234 | #--------------------------------------------------------------------------- | ||
|
r2792 | |||
|
r1234 | # A simple class to preserve Nathan's original functionality. | ||
class ColorTB(FormattedTB): | ||||
"""Shorthand to initialize a FormattedTB in Linux colors mode.""" | ||||
|
r17156 | |||
|
r21789 | def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs): | ||
|
r17156 | FormattedTB.__init__(self, color_scheme=color_scheme, | ||
|
r21789 | call_pdb=call_pdb, **kwargs) | ||
|
r1234 | |||
|
r2792 | |||
class SyntaxTB(ListTB): | ||||
"""Extension which holds some state: the last exception value""" | ||||
|
r22943 | def __init__(self, color_scheme='NoColor', parent=None, config=None): | ||
ListTB.__init__(self, color_scheme, parent=parent, config=config) | ||||
|
r2792 | self.last_syntax_error = None | ||
def __call__(self, etype, value, elist): | ||||
self.last_syntax_error = value | ||||
|
r17157 | |||
|
r17156 | ListTB.__call__(self, etype, value, elist) | ||
|
r2792 | |||
|
r12544 | def structured_traceback(self, etype, value, elist, tb_offset=None, | ||
context=5): | ||||
# If the source file has been edited, the line in the syntax error can | ||||
# be wrong (retrieved from an outdated cache). This replaces it with | ||||
# the current value. | ||||
|
r12951 | if isinstance(value, SyntaxError) \ | ||
|
r23037 | and isinstance(value.filename, str) \ | ||
|
r12545 | and isinstance(value.lineno, int): | ||
|
r12544 | linecache.checkcache(value.filename) | ||
|
r23117 | newtext = linecache.getline(value.filename, value.lineno) | ||
|
r12544 | if newtext: | ||
value.text = newtext | ||||
|
r22204 | self.last_syntax_error = value | ||
|
r12544 | return super(SyntaxTB, self).structured_traceback(etype, value, elist, | ||
|
r17156 | tb_offset=tb_offset, context=context) | ||
|
r12544 | |||
|
r2792 | def clear_err_state(self): | ||
"""Return the current error state and clear it""" | ||||
e = self.last_syntax_error | ||||
self.last_syntax_error = None | ||||
return e | ||||
|
r2839 | def stb2text(self, stb): | ||
"""Convert a structured traceback (a list) to a string.""" | ||||
return ''.join(stb) | ||||
|
r2838 | |||
|
r17157 | # some internal-use functions | ||
def text_repr(value): | ||||
"""Hopefully pretty robust repr equivalent.""" | ||||
# this is pretty horrible but should always return *something* | ||||
try: | ||||
|
r28208 | return pydoc.text.repr(value) # type: ignore[call-arg] | ||
|
r17157 | except KeyboardInterrupt: | ||
raise | ||||
except: | ||||
try: | ||||
return repr(value) | ||||
except KeyboardInterrupt: | ||||
raise | ||||
except: | ||||
try: | ||||
# all still in an except block so we catch | ||||
# getattr raising | ||||
name = getattr(value, '__name__', None) | ||||
if name: | ||||
# ick, recursion | ||||
return text_repr(name) | ||||
klass = getattr(value, '__class__', None) | ||||
if klass: | ||||
return '%s instance' % text_repr(klass) | ||||
except KeyboardInterrupt: | ||||
raise | ||||
except: | ||||
return 'UNRECOVERABLE REPR FAILURE' | ||||
def eqrepr(value, repr=text_repr): | ||||
return '=%s' % repr(value) | ||||
def nullrepr(value, repr=text_repr): | ||||
return '' | ||||