123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- from __future__ import absolute_import
- import os
- import platform
- import sys
- from dataclasses import dataclass, field
- from traceback import walk_tb
- from types import ModuleType, TracebackType
- from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Type, Union
- from pip._vendor.pygments.lexers import guess_lexer_for_filename
- from pip._vendor.pygments.token import Comment, Keyword, Name, Number, Operator, String
- from pip._vendor.pygments.token import Text as TextToken
- from pip._vendor.pygments.token import Token
- from . import pretty
- from ._loop import loop_first, loop_last
- from .columns import Columns
- from .console import Console, ConsoleOptions, ConsoleRenderable, 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
- from .text import Text
- from .theme import Theme
- WINDOWS = platform.system() == "Windows"
- LOCALS_MAX_LENGTH = 10
- LOCALS_MAX_STRING = 80
- def install(
- *,
- console: Optional[Console] = None,
- width: Optional[int] = 100,
- extra_lines: int = 3,
- theme: Optional[str] = None,
- word_wrap: bool = False,
- show_locals: bool = False,
- indent_guides: bool = True,
- suppress: Iterable[Union[str, ModuleType]] = (),
- max_frames: int = 100,
- ) -> Callable[[Type[BaseException], BaseException, Optional[TracebackType]], Any]:
- """Install a rich traceback handler.
- 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.
- 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.
- 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(file=sys.stderr) if console is None else console
- def excepthook(
- type_: Type[BaseException],
- value: BaseException,
- traceback: Optional[TracebackType],
- ) -> None:
- traceback_console.print(
- Traceback.from_exception(
- type_,
- value,
- traceback,
- width=width,
- extra_lines=extra_lines,
- theme=theme,
- word_wrap=word_wrap,
- show_locals=show_locals,
- indent_guides=indent_guides,
- suppress=suppress,
- max_frames=max_frames,
- )
- )
- 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] = None if 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", 1 if compiled else 0)
- # remove ipython internal frames from trace with tb_offset
- for _ in range(tb_offset):
- if tb is None:
- 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
- ipy_excepthook_closure(ip)
- return sys.excepthook
- except Exception:
- # otherwise use default system hook
- old_excepthook = sys.excepthook
- sys.excepthook = excepthook
- return old_excepthook
- @dataclass
- class Frame:
- filename: str
- lineno: int
- name: str
- line: str = ""
- locals: Optional[Dict[str, pretty.Node]] = None
- @dataclass
- class _SyntaxError:
- offset: int
- filename: str
- line: str
- lineno: int
- msg: str
- @dataclass
- class Stack:
- exc_type: str
- exc_value: str
- syntax_error: Optional[_SyntaxError] = None
- is_cause: bool = False
- frames: List[Frame] = field(default_factory=list)
- @dataclass
- class Trace:
- stacks: List[Stack]
- 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.
- 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, or None for no abbreviation.
- Defaults to 10.
- locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
- 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, 0 for no maximum. Defaults to 100.
- """
- LEXERS = {
- "": "text",
- ".py": "python",
- ".pxd": "cython",
- ".pyx": "cython",
- ".pxi": "pyrex",
- }
- def __init__(
- self,
- trace: Optional[Trace] = None,
- width: Optional[int] = 100,
- extra_lines: int = 3,
- theme: Optional[str] = None,
- word_wrap: bool = False,
- show_locals: bool = False,
- indent_guides: bool = True,
- locals_max_length: int = LOCALS_MAX_LENGTH,
- locals_max_string: int = LOCALS_MAX_STRING,
- suppress: Iterable[Union[str, ModuleType]] = (),
- max_frames: int = 100,
- ):
- if trace is None:
- exc_type, exc_value, traceback = sys.exc_info()
- if exc_type is None or exc_value is None or traceback is None:
- raise ValueError(
- "Value for 'trace' required if not called in except: block"
- )
- trace = self.extract(
- exc_type, exc_value, traceback, show_locals=show_locals
- )
- self.trace = trace
- self.width = width
- self.extra_lines = extra_lines
- self.theme = Syntax.get_theme(theme or "ansi_dark")
- self.word_wrap = word_wrap
- self.show_locals = show_locals
- self.indent_guides = indent_guides
- self.locals_max_length = locals_max_length
- self.locals_max_string = locals_max_string
- self.suppress: Sequence[str] = []
- for suppress_entity in suppress:
- if not isinstance(suppress_entity, str):
- assert (
- suppress_entity.__file__ is not None
- ), f"{suppress_entity!r} must be a module with '__file__' attribute"
- path = os.path.dirname(suppress_entity.__file__)
- else:
- path = suppress_entity
- path = os.path.normpath(os.path.abspath(path))
- self.suppress.append(path)
- self.max_frames = max(4, max_frames) if max_frames > 0 else 0
- @classmethod
- def from_exception(
- cls,
- exc_type: Type[Any],
- exc_value: BaseException,
- traceback: Optional[TracebackType],
- width: Optional[int] = 100,
- extra_lines: int = 3,
- theme: Optional[str] = None,
- word_wrap: bool = False,
- show_locals: bool = False,
- indent_guides: bool = True,
- locals_max_length: int = LOCALS_MAX_LENGTH,
- locals_max_string: int = LOCALS_MAX_STRING,
- suppress: Iterable[Union[str, ModuleType]] = (),
- max_frames: int = 100,
- ) -> "Traceback":
- """Create a traceback from exception info
- 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.
- 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, or None for no abbreviation.
- Defaults to 10.
- locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
- 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, 0 for 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
- )
- return cls(
- rich_traceback,
- width=width,
- extra_lines=extra_lines,
- theme=theme,
- word_wrap=word_wrap,
- show_locals=show_locals,
- indent_guides=indent_guides,
- locals_max_length=locals_max_length,
- locals_max_string=locals_max_string,
- suppress=suppress,
- max_frames=max_frames,
- )
- @classmethod
- def extract(
- cls,
- exc_type: Type[BaseException],
- exc_value: BaseException,
- traceback: Optional[TracebackType],
- show_locals: bool = False,
- locals_max_length: int = LOCALS_MAX_LENGTH,
- locals_max_string: int = LOCALS_MAX_STRING,
- ) -> Trace:
- """Extract traceback information.
- 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, or None for no abbreviation.
- Defaults to 10.
- locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
- Returns:
- Trace: A Trace instance which you can use to construct a `Traceback`.
- """
- stacks: List[Stack] = []
- is_cause = False
- from pip._vendor.rich import _IMPORT_CWD
- def safe_str(_object: Any) -> str:
- """Don't allow exceptions from __str__ to propegate."""
- try:
- return str(_object)
- except Exception:
- return "<exception str() failed>"
- while True:
- stack = Stack(
- exc_type=safe_str(exc_type.__name__),
- exc_value=safe_str(exc_value),
- is_cause=is_cause,
- )
- if isinstance(exc_value, SyntaxError):
- stack.syntax_error = _SyntaxError(
- offset=exc_value.offset or 0,
- filename=exc_value.filename or "?",
- lineno=exc_value.lineno or 0,
- line=exc_value.text or "",
- msg=exc_value.msg,
- )
- stacks.append(stack)
- append = stack.frames.append
- for frame_summary, line_no in walk_tb(traceback):
- filename = frame_summary.f_code.co_filename
- if filename and not filename.startswith("<"):
- if not os.path.isabs(filename):
- filename = os.path.join(_IMPORT_CWD, filename)
- 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,
- )
- for key, value in frame_summary.f_locals.items()
- }
- if show_locals
- else None,
- )
- append(frame)
- if "_rich_traceback_guard" in frame_summary.f_locals:
- del stack.frames[:]
- cause = getattr(exc_value, "__cause__", None)
- if cause and cause.__traceback__:
- exc_type = cause.__class__
- exc_value = cause
- traceback = cause.__traceback__
- if traceback:
- is_cause = True
- continue
- cause = exc_value.__context__
- if (
- cause
- and cause.__traceback__
- and not getattr(exc_value, "__suppress_context__", False)
- ):
- exc_type = cause.__class__
- exc_value = cause
- traceback = cause.__traceback__
- if traceback:
- is_cause = False
- continue
- # No cover, code is reached but coverage doesn't recognize it.
- break # pragma: no cover
- trace = Trace(stacks=stacks)
- return trace
- def __rich_console__(
- self, console: Console, options: ConsoleOptions
- ) -> RenderResult:
- theme = self.theme
- background_style = theme.get_background_style()
- token_style = theme.get_style_for_token
- traceback_theme = Theme(
- {
- "pretty": token_style(TextToken),
- "pygments.text": token_style(Token),
- "pygments.string": token_style(String),
- "pygments.function": token_style(Name.Function),
- "pygments.number": token_style(Number),
- "repr.indent": token_style(Comment) + Style(dim=True),
- "repr.str": token_style(String),
- "repr.brace": token_style(TextToken) + Style(bold=True),
- "repr.number": token_style(Number),
- "repr.bool_true": token_style(Keyword.Constant),
- "repr.bool_false": token_style(Keyword.Constant),
- "repr.none": token_style(Keyword.Constant),
- "scope.border": token_style(String.Delimiter),
- "scope.equals": token_style(Operator),
- "scope.key": token_style(Name),
- "scope.key.special": token_style(Name.Constant) + Style(dim=True),
- },
- inherit=False,
- )
- highlighter = ReprHighlighter()
- for last, stack in loop_last(reversed(self.trace.stacks)):
- if stack.frames:
- stack_renderable: ConsoleRenderable = Panel(
- self._render_stack(stack),
- title="[traceback.title]Traceback [dim](most recent call last)",
- style=background_style,
- border_style="traceback.border",
- expand=True,
- padding=(0, 1),
- )
- stack_renderable = Constrain(stack_renderable, self.width)
- with console.use_theme(traceback_theme):
- yield stack_renderable
- if stack.syntax_error is not None:
- with console.use_theme(traceback_theme):
- yield Constrain(
- Panel(
- self._render_syntax_error(stack.syntax_error),
- style=background_style,
- border_style="traceback.border.syntax_error",
- expand=True,
- padding=(0, 1),
- width=self.width,
- ),
- self.width,
- )
- yield Text.assemble(
- (f"{stack.exc_type}: ", "traceback.exc_type"),
- highlighter(stack.syntax_error.msg),
- )
- elif stack.exc_value:
- yield Text.assemble(
- (f"{stack.exc_type}: ", "traceback.exc_type"),
- highlighter(stack.exc_value),
- )
- else:
- yield Text.assemble((f"{stack.exc_type}", "traceback.exc_type"))
- if not 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",
- )
- @group()
- def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult:
- highlighter = ReprHighlighter()
- path_highlighter = PathHighlighter()
- if syntax_error.filename != "<stdin>":
- text = Text.assemble(
- (f" {syntax_error.filename}", "pygments.string"),
- (":", "pygments.text"),
- (str(syntax_error.lineno), "pygments.number"),
- style="pygments.text",
- )
- yield path_highlighter(text)
- syntax_error_text = highlighter(syntax_error.line.rstrip())
- syntax_error_text.no_wrap = True
- offset = min(syntax_error.offset - 1, len(syntax_error_text))
- syntax_error_text.stylize("bold underline", offset, offset)
- syntax_error_text += Text.from_markup(
- "\n" + " " * offset + "[traceback.offset]▲[/]",
- style="pygments.text",
- )
- yield syntax_error_text
- @classmethod
- def _guess_lexer(cls, filename: str, code: str) -> str:
- ext = os.path.splitext(filename)[-1]
- if not 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 != -1 else code
- if first_line.startswith("#!") and "python" in first_line.lower():
- return "python"
- lexer_name = (
- cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name
- )
- return lexer_name
- @group()
- def _render_stack(self, stack: Stack) -> RenderResult:
- path_highlighter = PathHighlighter()
- theme = self.theme
- code_cache: Dict[str, str] = {}
- def read_code(filename: str) -> str:
- """Read files, and cache results on filename.
- Args:
- filename (str): Filename to read
- Returns:
- str: Contents of file
- """
- code = code_cache.get(filename)
- if code is None:
- with open(
- filename, "rt", encoding="utf-8", errors="replace"
- ) as code_file:
- code = code_file.read()
- code_cache[filename] = code
- return code
- def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
- if frame.locals:
- yield render_scope(
- frame.locals,
- title="locals",
- indent_guides=self.indent_guides,
- max_length=self.locals_max_length,
- max_string=self.locals_max_string,
- )
- exclude_frames: Optional[range] = None
- if self.max_frames != 0:
- exclude_frames = range(
- self.max_frames // 2,
- len(stack.frames) - self.max_frames // 2,
- )
- excluded = False
- for frame_index, frame in enumerate(stack.frames):
- if exclude_frames and frame_index in exclude_frames:
- excluded = True
- continue
- if excluded:
- assert exclude_frames is not None
- yield Text(
- f"\n... {len(exclude_frames)} frames hidden ...",
- justify="center",
- style="traceback.error",
- )
- excluded = False
- first = frame_index == 1
- frame_filename = frame.filename
- suppressed = any(frame_filename.startswith(path) for path in self.suppress)
- 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",
- )
- if not frame.filename.startswith("<") and not first:
- yield ""
- yield text
- if frame.filename.startswith("<"):
- yield from render_locals(frame)
- continue
- if not suppressed:
- try:
- code = read_code(frame.filename)
- 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=88,
- indent_guides=self.indent_guides,
- dedent=False,
- )
- yield ""
- except Exception as error:
- yield Text.assemble(
- (f"\n{error}", "traceback.error"),
- )
- else:
- yield (
- Columns(
- [
- syntax,
- *render_locals(frame),
- ],
- padding=1,
- )
- if frame.locals
- else syntax
- )
- if __name__ == "__main__": # pragma: no cover
- from .console import Console
- console = Console()
- import sys
- def bar(a: Any) -> None: # 这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑
- one = 1
- print(one / a)
- def foo(a: Any) -> None:
- _rich_traceback_guard = True
- zed = {
- "characters": {
- "Paul Atreides",
- "Vladimir Harkonnen",
- "Thufir Hawat",
- "Duncan Idaho",
- },
- "atomic_types": (None, False, True),
- }
- bar(a)
- def error() -> None:
- try:
- try:
- foo(0)
- except:
- slfkjsldkfj # type: ignore
- except:
- console.print_exception(show_locals=True)
- error()
|