import inspect import linecache import os import sys from dataclasses import dataclass, field from itertools import islice from traceback import walk_tb from types import ModuleType, TracebackType from typing import (
Any,
Callable,
Dict,
Iterable,
List,
Optional,
Sequence,
Set,
Tuple,
Type,
Union,
)
from pygments.lexers import guess_lexer_for_filename from pygments.token import Comment, Keyword, Name, Number, Operator, String from pygments.token import Text as TextToken from pygments.token import Token from pygments.util import ClassNotFound
from . import pretty from ._loop import loop_first_last, loop_last from .columns import Columns from .console import (
Console,
ConsoleOptions,
ConsoleRenderable,
OverflowMethod,
Group,
RenderResult,
group,
) from .constrain import Constrain from .highlighter import RegexHighlighter, ReprHighlighter from .panel import Panel from .scope import render_scope from .style import Style from .syntax import Syntax, SyntaxPosition from .text import Text from .theme import Theme
WINDOWS = sys.platform == "win32"
LOCALS_MAX_LENGTH = 10
LOCALS_MAX_STRING = 80
def _iter_syntax_lines(
start: SyntaxPosition, end: SyntaxPosition
) -> Iterable[Tuple[int, int, int]]: """Yield start and end positions per line.
Args:
start: Start position.
end: End position.
Returns:
Iterable of (LINE, COLUMN1, COLUMN2). """
line1, column1 = start
line2, column2 = end
if line1 == line2: yield line1, column1, column2 else: for first, last, line_no in loop_first_last(range(line1, line2 + 1)): if first: yield line_no, column1, -1 elif last: yield line_no, 0, column2 else: yield line_no, 0, -1
Once installed, any tracebacks will be printed with syntax highlighting and rich formatting.
Args:
console (Optional[Console], optional): Console to write exception to. Default uses internal Console instance.
width (Optional[int], optional): Width (in characters) of traceback. Defaults to 100.
code_width (Optional[int], optional): Code width (in characters) of traceback. Defaults to 88.
extra_lines (int, optional): Extra lines of code. Defaults to 3.
theme (Optional[str], optional): Pygments theme to use in traceback. Defaults to ``None`` which will pick
a theme appropriate for the platform.
word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
show_locals (bool, optional): Enable display of local variables. Defaults to False.
locals_max_length (int, optional): Maximum length of containers before abbreviating, orNonefor no abbreviation.
Defaults to 10.
locals_max_string (int, optional): Maximum length of string before truncating, orNone to disable. Defaults to 80.
locals_max_depth (int, optional): Maximum depths of locals before truncating, orNone to disable. Defaults to None.
locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
locals_overflow (OverflowMethod, optional): How to handle overflowing locals, orNone to disable. Defaults to None.
indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
Returns:
Callable: The previous exception handler that was replaced.
"""
traceback_console = Console(stderr=True) if console isNoneelse console
locals_hide_sunder = ( True if (traceback_console.is_jupyter and locals_hide_sunder isNone) else locals_hide_sunder
)
def ipy_excepthook_closure(ip: Any) -> None: # pragma: no cover
tb_data = {} # store information about showtraceback call
default_showtraceback = ip.showtraceback # keep reference of default traceback
def ipy_show_traceback(*args: Any, **kwargs: Any) -> None: """wrap the default ip.showtraceback to store info for ip._showtraceback""" nonlocal tb_data
tb_data = kwargs
default_showtraceback(*args, **kwargs)
def ipy_display_traceback(
*args: Any, is_syntax: bool = False, **kwargs: Any
) -> None: """Internally called traceback from ip._showtraceback""" nonlocal tb_data
exc_tuple = ip._get_exc_info()
# do not display trace on syntax error
tb: Optional[TracebackType] = Noneif is_syntax else exc_tuple[2]
# determine correct tb_offset
compiled = tb_data.get("running_compiled_code", False)
tb_offset = tb_data.get("tb_offset") if tb_offset isNone:
tb_offset = 1if compiled else0 # remove ipython internal frames from trace with tb_offset for _ in range(tb_offset): if tb isNone: break
tb = tb.tb_next
excepthook(exc_tuple[0], exc_tuple[1], tb)
tb_data = {} # clear data upon usage
# replace _showtraceback instead of showtraceback to allow ipython features such as debugging to work # this is also what the ipython docs recommends to modify when subclassing InteractiveShell
ip._showtraceback = ipy_display_traceback # add wrapper to capture tb_data
ip.showtraceback = ipy_show_traceback
ip.showsyntaxerror = lambda *args, **kwargs: ipy_display_traceback(
*args, is_syntax=True, **kwargs
)
try: # pragma: no cover # if within ipython, use customized traceback
ip = get_ipython() # type: ignore[name-defined]
ipy_excepthook_closure(ip) return sys.excepthook except Exception: # otherwise use default system hook
old_excepthook = sys.excepthook
sys.excepthook = excepthook return old_excepthook
class PathHighlighter(RegexHighlighter):
highlights = [r"(?P<dim>.*/)(?P<bold>.+)"]
class Traceback: """A Console renderable that renders a traceback.
Args:
trace (Trace, optional): A `Trace` object produced from `extract`. Defaults to None, which uses
the last exception.
width (Optional[int], optional): Number of characters used to traceback. Defaults to 100.
code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88.
extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
theme (str, optional): Override pygments theme used in traceback.
word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
show_locals (bool, optional): Enable display of local variables. Defaults to False.
indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
locals_max_length (int, optional): Maximum length of containers before abbreviating, orNonefor no abbreviation.
Defaults to 10.
locals_max_string (int, optional): Maximum length of string before truncating, orNone to disable. Defaults to 80.
locals_max_depth (int, optional): Maximum depths of locals before truncating, orNone to disable. Defaults to None.
locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
locals_overflow (OverflowMethod, optional): How to handle overflowing locals, orNone to disable. Defaults to None.
suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
max_frames (int): Maximum number of frames to show in a traceback, 0for no maximum. Defaults to 100.
Args:
exc_type (Type[BaseException]): Exception type.
exc_value (BaseException): Exception value.
traceback (TracebackType): Python Traceback object.
width (Optional[int], optional): Number of characters used to traceback. Defaults to 100.
code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88.
extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
theme (str, optional): Override pygments theme used in traceback.
word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
show_locals (bool, optional): Enable display of local variables. Defaults to False.
indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
locals_max_length (int, optional): Maximum length of containers before abbreviating, orNonefor no abbreviation.
Defaults to 10.
locals_max_depth (int, optional): Maximum depths of locals before truncating, orNone to disable. Defaults to None.
locals_max_string (int, optional): Maximum length of string before truncating, orNone to disable. Defaults to 80.
locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
locals_overflow (OverflowMethod, optional): How to handle overflowing locals, orNone to disable. Defaults to None.
suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
max_frames (int): Maximum number of frames to show in a traceback, 0for no maximum. Defaults to 100.
Returns:
Traceback: A Traceback instance that may be printed. """
rich_traceback = cls.extract(
exc_type,
exc_value,
traceback,
show_locals=show_locals,
locals_max_length=locals_max_length,
locals_max_string=locals_max_string,
locals_max_depth=locals_max_depth,
locals_hide_dunder=locals_hide_dunder,
locals_hide_sunder=locals_hide_sunder,
)
Args:
exc_type (Type[BaseException]): Exception type.
exc_value (BaseException): Exception value.
traceback (TracebackType): Python Traceback object.
show_locals (bool, optional): Enable display of local variables. Defaults to False.
locals_max_length (int, optional): Maximum length of containers before abbreviating, orNonefor no abbreviation.
Defaults to 10.
locals_max_string (int, optional): Maximum length of string before truncating, orNone to disable. Defaults to 80.
locals_max_depth (int, optional): Maximum depths of locals before truncating, orNone to disable. Defaults to None.
locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
Returns:
Trace: A Trace instance which you can use to construct a `Traceback`. """
stacks: List[Stack] = []
is_cause = False
from rich import _IMPORT_CWD
notes: List[str] = getattr(exc_value, "__notes__", None) or []
grouped_exceptions: Set[BaseException] = (
set() if _visited_exceptions isNoneelse _visited_exceptions
)
def safe_str(_object: Any) -> str: """Don't allow exceptions from __str__ to propagate.""" try: return str(_object) except Exception: return"<exception str() failed>"
def get_locals(
iter_locals: Iterable[Tuple[str, object]],
) -> Iterable[Tuple[str, object]]: """Extract locals from an iterator of key pairs.""" ifnot (locals_hide_dunder or locals_hide_sunder): yieldfrom iter_locals return for key, value in iter_locals: if locals_hide_dunder and key.startswith("__"): continue if locals_hide_sunder and key.startswith("_"): continue yield key, value
for frame_summary, line_no in walk_tb(traceback):
filename = frame_summary.f_code.co_filename
if filename andnot filename.startswith("<"): ifnot os.path.isabs(filename):
filename = os.path.join(_IMPORT_CWD, filename) if frame_summary.f_locals.get("_rich_traceback_omit", False): continue
frame = Frame(
filename=filename or"?",
lineno=line_no,
name=frame_summary.f_code.co_name,
locals=(
{
key: pretty.traverse(
value,
max_length=locals_max_length,
max_string=locals_max_string,
max_depth=locals_max_depth,
) for key, value in get_locals(frame_summary.f_locals.items()) ifnot (inspect.isfunction(value) or inspect.isclass(value))
} if show_locals elseNone
),
last_instruction=last_instruction,
)
append(frame) if frame_summary.f_locals.get("_rich_traceback_guard", False): del stack.frames[:]
ifnot grouped_exceptions:
cause = getattr(exc_value, "__cause__", None) if cause isnotNoneand cause isnot exc_value:
exc_type = cause.__class__
exc_value = cause # __traceback__ can be None, e.g. for exceptions raised by the # 'multiprocessing' module
traceback = cause.__traceback__
is_cause = True continue
cause = exc_value.__context__ if cause isnotNoneandnot getattr(
exc_value, "__suppress_context__", False
):
exc_type = cause.__class__
exc_value = cause
traceback = cause.__traceback__
is_cause = False continue # No cover, code is reached but coverage doesn't recognize it. break# pragma: no cover
for note in stack.notes: yield Text.assemble(("[NOTE] ", "traceback.note"), highlighter(note))
if stack.is_group: for group_no, group_exception in enumerate(stack.exceptions, 1):
grouped_exceptions: List[Group] = [] for group_last, group_stack in loop_last(group_exception.stacks):
grouped_exceptions.append(render_stack(group_stack, group_last)) yield"" yield Constrain(
Panel(
Group(*grouped_exceptions),
title=f"Sub-exception #{group_no}",
border_style="traceback.group.border",
),
self.width,
)
ifnot last: if stack.is_cause: yield Text.from_markup( "\n[i]The above exception was the direct cause of the following exception:\n",
) else: yield Text.from_markup( "\n[i]During handling of the above exception, another exception occurred:\n",
)
for last, stack in loop_last(reversed(self.trace.stacks)): yield render_stack(stack, last)
@classmethod def _guess_lexer(cls, filename: str, code: str) -> str:
ext = os.path.splitext(filename)[-1] ifnot ext: # No extension, look at first line to see if it is a hashbang # Note, this is an educated guess and not a guarantee # If it fails, the only downside is that the code is highlighted strangely
new_line_index = code.index("\n")
first_line = code[:new_line_index] if new_line_index != -1else code if first_line.startswith("#!") and "python" in first_line.lower(): return"python" try: return cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name except ClassNotFound: return"text"
first = frame_index == 0
frame_filename = frame.filename
suppressed = any(frame_filename.startswith(path) for path in self.suppress)
if os.path.exists(frame.filename):
text = Text.assemble(
path_highlighter(Text(frame.filename, style="pygments.string")),
(":", "pygments.text"),
(str(frame.lineno), "pygments.number"), " in ",
(frame.name, "pygments.function"),
style="pygments.text",
) else:
text = Text.assemble( "in ",
(frame.name, "pygments.function"),
(":", "pygments.text"),
(str(frame.lineno), "pygments.number"),
style="pygments.text",
) ifnot frame.filename.startswith("<") andnot first: yield"" yield text if frame.filename.startswith("<"): yieldfrom render_locals(frame) continue ifnot suppressed: try:
code_lines = linecache.getlines(frame.filename)
code = "".join(code_lines) ifnot code: # code may be an empty string if the file doesn't exist, OR # if the traceback filename is generated dynamically continue
lexer_name = self._guess_lexer(frame.filename, code)
syntax = Syntax(
code,
lexer_name,
theme=theme,
line_numbers=True,
line_range=(
frame.lineno - self.extra_lines,
frame.lineno + self.extra_lines,
),
highlight_lines={frame.lineno},
word_wrap=self.word_wrap,
code_width=self.code_width,
indent_guides=self.indent_guides,
dedent=False,
) yield"" except Exception as error: yield Text.assemble(
(f"\n{error}", "traceback.error"),
) else: if frame.last_instruction isnotNone:
start, end = frame.last_instruction
# Stylize a line at a time # So that indentation isn't underlined (which looks bad) for line1, column1, column2 in _iter_syntax_lines(start, end): try: if column1 == 0:
line = code_lines[line1 - 1]
column1 = len(line) - len(line.lstrip()) if column2 == -1:
column2 = len(code_lines[line1 - 1]) except IndexError: # Being defensive here # If last_instruction reports a line out-of-bounds, we don't want to crash continue
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.