summaryrefslogtreecommitdiff
path: root/src/pip/_vendor/rich/traceback.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pip/_vendor/rich/traceback.py')
-rw-r--r--src/pip/_vendor/rich/traceback.py679
1 files changed, 679 insertions, 0 deletions
diff --git a/src/pip/_vendor/rich/traceback.py b/src/pip/_vendor/rich/traceback.py
new file mode 100644
index 000000000..e5023c77a
--- /dev/null
+++ b/src/pip/_vendor/rich/traceback.py
@@ -0,0 +1,679 @@
+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 pip._vendor.pygments.util import ClassNotFound
+
+from . import pretty
+from ._loop import 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[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
+
+
+@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)
+ 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,
+ )
+ for key, value in frame_summary.f_locals.items()
+ }
+ if show_locals
+ else None,
+ )
+ append(frame)
+ if frame_summary.f_locals.get("_rich_traceback_guard", False):
+ del stack.frames[:]
+
+ cause = getattr(exc_value, "__cause__", None)
+ if cause and cause.__traceback__:
+ exc_type = cause.__class__
+ exc_value = cause
+ traceback = cause.__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__
+ 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"
+ try:
+ return cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name
+ except ClassNotFound:
+ return "text"
+
+ @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 == 0
+ 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[name-defined]
+ except:
+ console.print_exception(show_locals=True)
+
+ error()