Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix tracebacks in Jupyter #2754

Merged
merged 5 commits into from
Jan 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [13.1.0] - 2023-01-14

### Fixed

- Fixed wrong filenames in Jupyter tracebacks https://github.com/Textualize/rich/issues/2271

### Added

- Added locals_hide_dunder and locals_hide_sunder to Tracebacks, to hide double underscore and single underscore locals. https://github.com/Textualize/rich/pull/2754

### Changed

- Tracebacks will now hide double underscore names from locals by default. Set `locals_hide_dunder=False` to restore previous behaviour.

## [13.0.1] - 2023-01-06

### Fixed
Expand Down Expand Up @@ -1860,6 +1874,7 @@ Major version bump for a breaking change to `Text.stylize signature`, which corr

- First official release, API still to be stabilized

[13.1.0]: https://github.com/textualize/rich/compare/v13.0.1...v13.1.0
[13.0.1]: https://github.com/textualize/rich/compare/v13.0.0...v13.0.1
[13.0.0]: https://github.com/textualize/rich/compare/v12.6.0...v13.0.0
[12.6.0]: https://github.com/textualize/rich/compare/v12.5.2...v12.6.0
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "rich"
homepage = "https://github.com/Textualize/rich"
documentation = "https://rich.readthedocs.io/en/latest/"
version = "13.0.1"
version = "13.1.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
authors = ["Will McGugan <willmcgugan@gmail.com>"]
license = "MIT"
Expand Down
117 changes: 91 additions & 26 deletions rich/traceback.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
from __future__ import absolute_import

import linecache
import os
import platform
import sys
from dataclasses import dataclass, field
from pathlib import Path
from traceback import walk_tb
from types import ModuleType, TracebackType
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Type, Union
from typing import (
Any,
Callable,
Dict,
Iterable,
List,
Optional,
Sequence,
Tuple,
Type,
Union,
)

from pygments.lexers import guess_lexer_for_filename
from pygments.token import Comment, Keyword, Name, Number, Operator, String
Expand Down Expand Up @@ -42,6 +53,10 @@ def install(
theme: Optional[str] = None,
word_wrap: bool = False,
show_locals: bool = False,
locals_max_length: int = LOCALS_MAX_LENGTH,
locals_max_string: int = LOCALS_MAX_STRING,
locals_hide_dunder: bool = True,
locals_hide_sunder: Optional[bool] = None,
indent_guides: bool = True,
suppress: Iterable[Union[str, ModuleType]] = (),
max_frames: int = 100,
Expand All @@ -59,6 +74,11 @@ def install(
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, 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.
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.
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.

Expand All @@ -68,6 +88,12 @@ def install(
"""
traceback_console = Console(file=sys.stderr) if console is None else console

locals_hide_sunder = (
True
if (traceback_console.is_jupyter and locals_hide_sunder is None)
else locals_hide_sunder
)

def excepthook(
type_: Type[BaseException],
value: BaseException,
Expand All @@ -83,6 +109,10 @@ def excepthook(
theme=theme,
word_wrap=word_wrap,
show_locals=show_locals,
locals_max_length=locals_max_length,
locals_max_string=locals_max_string,
locals_hide_dunder=locals_hide_dunder,
locals_hide_sunder=bool(locals_hide_sunder),
indent_guides=indent_guides,
suppress=suppress,
max_frames=max_frames,
Expand Down Expand Up @@ -193,6 +223,8 @@ class Traceback:
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.
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.
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.

Expand All @@ -209,14 +241,17 @@ class Traceback:
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,
locals_hide_dunder: bool = True,
locals_hide_sunder: bool = False,
indent_guides: bool = True,
suppress: Iterable[Union[str, ModuleType]] = (),
max_frames: int = 100,
):
Expand All @@ -238,6 +273,8 @@ def __init__(
self.indent_guides = indent_guides
self.locals_max_length = locals_max_length
self.locals_max_string = locals_max_string
self.locals_hide_dunder = locals_hide_dunder
self.locals_hide_sunder = locals_hide_sunder

self.suppress: Sequence[str] = []
for suppress_entity in suppress:
Expand All @@ -258,14 +295,17 @@ def from_exception(
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,
locals_hide_dunder: bool = True,
locals_hide_sunder: bool = False,
indent_guides: bool = True,
suppress: Iterable[Union[str, ModuleType]] = (),
max_frames: int = 100,
) -> "Traceback":
Expand All @@ -284,6 +324,8 @@ def from_exception(
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.
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.
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.

Expand All @@ -297,6 +339,8 @@ def from_exception(
show_locals=show_locals,
locals_max_length=locals_max_length,
locals_max_string=locals_max_string,
locals_hide_dunder=locals_hide_dunder,
locals_hide_sunder=locals_hide_sunder,
)
return cls(
rich_traceback,
Expand All @@ -308,6 +352,8 @@ def from_exception(
indent_guides=indent_guides,
locals_max_length=locals_max_length,
locals_max_string=locals_max_string,
locals_hide_dunder=locals_hide_dunder,
locals_hide_sunder=locals_hide_sunder,
suppress=suppress,
max_frames=max_frames,
)
Expand All @@ -318,9 +364,12 @@ def extract(
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,
locals_hide_dunder: bool = True,
locals_hide_sunder: bool = False,
) -> Trace:
"""Extract traceback information.

Expand All @@ -332,6 +381,8 @@ def extract(
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.
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`.
Expand Down Expand Up @@ -368,13 +419,28 @@ def safe_str(_object: Any) -> str:
stacks.append(stack)
append = stack.frames.append

def get_locals(
iter_locals: Iterable[Tuple[str, object]]
) -> Iterable[Tuple[str, object]]:
"""Extract locals from an iterator of key pairs."""
if not (locals_hide_dunder or locals_hide_sunder):
yield from 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 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,
Expand All @@ -385,7 +451,7 @@ def safe_str(_object: Any) -> str:
max_length=locals_max_length,
max_string=locals_max_string,
)
for key, value in frame_summary.f_locals.items()
for key, value in get_locals(frame_summary.f_locals.items())
}
if show_locals
else None,
Expand Down Expand Up @@ -500,13 +566,14 @@ 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)
if os.path.exists(syntax_error.filename):
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))
Expand Down Expand Up @@ -537,7 +604,6 @@ def _guess_lexer(cls, filename: str, code: str) -> str:
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.
Expand All @@ -548,11 +614,7 @@ def read_code(filename: str) -> str:
Returns:
str: Contents of file
"""
code = code_cache.get(filename)
if code is None:
code = Path(filename).read_text(encoding="utf-8", errors="replace")
code_cache[filename] = code
return code
return "".join(linecache.getlines(filename))

def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
if frame.locals:
Expand Down Expand Up @@ -591,14 +653,17 @@ def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
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 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"))
if not frame.filename.startswith("<") and not first:
yield ""
yield text
Expand Down