Skip to content

Commit b7ac6bb

Browse files
authored
Merge pull request #2754 from Textualize/jupyter-traceback-fix
Fix tracebacks in Jupyter
2 parents 80188f2 + 8baf90b commit b7ac6bb

File tree

3 files changed

+107
-27
lines changed

3 files changed

+107
-27
lines changed

CHANGELOG.md

+15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [13.1.0] - 2023-01-14
9+
10+
### Fixed
11+
12+
- Fixed wrong filenames in Jupyter tracebacks https://github.com/Textualize/rich/issues/2271
13+
14+
### Added
15+
16+
- 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
17+
18+
### Changed
19+
20+
- Tracebacks will now hide double underscore names from locals by default. Set `locals_hide_dunder=False` to restore previous behaviour.
21+
822
## [13.0.1] - 2023-01-06
923

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

18611875
- First official release, API still to be stabilized
18621876

1877+
[13.1.0]: https://github.com/textualize/rich/compare/v13.0.1...v13.1.0
18631878
[13.0.1]: https://github.com/textualize/rich/compare/v13.0.0...v13.0.1
18641879
[13.0.0]: https://github.com/textualize/rich/compare/v12.6.0...v13.0.0
18651880
[12.6.0]: https://github.com/textualize/rich/compare/v12.5.2...v12.6.0

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "rich"
33
homepage = "https://github.com/Textualize/rich"
44
documentation = "https://rich.readthedocs.io/en/latest/"
5-
version = "13.0.1"
5+
version = "13.1.0"
66
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
77
authors = ["Will McGugan <[email protected]>"]
88
license = "MIT"

rich/traceback.py

+91-26
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
from __future__ import absolute_import
22

3+
import linecache
34
import os
45
import platform
56
import sys
67
from dataclasses import dataclass, field
7-
from pathlib import Path
88
from traceback import walk_tb
99
from types import ModuleType, TracebackType
10-
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Type, Union
10+
from typing import (
11+
Any,
12+
Callable,
13+
Dict,
14+
Iterable,
15+
List,
16+
Optional,
17+
Sequence,
18+
Tuple,
19+
Type,
20+
Union,
21+
)
1122

1223
from pygments.lexers import guess_lexer_for_filename
1324
from pygments.token import Comment, Keyword, Name, Number, Operator, String
@@ -42,6 +53,10 @@ def install(
4253
theme: Optional[str] = None,
4354
word_wrap: bool = False,
4455
show_locals: bool = False,
56+
locals_max_length: int = LOCALS_MAX_LENGTH,
57+
locals_max_string: int = LOCALS_MAX_STRING,
58+
locals_hide_dunder: bool = True,
59+
locals_hide_sunder: Optional[bool] = None,
4560
indent_guides: bool = True,
4661
suppress: Iterable[Union[str, ModuleType]] = (),
4762
max_frames: int = 100,
@@ -59,6 +74,11 @@ def install(
5974
a theme appropriate for the platform.
6075
word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
6176
show_locals (bool, optional): Enable display of local variables. Defaults to False.
77+
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
78+
Defaults to 10.
79+
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
80+
locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
81+
locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
6282
indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
6383
suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
6484
@@ -68,6 +88,12 @@ def install(
6888
"""
6989
traceback_console = Console(file=sys.stderr) if console is None else console
7090

91+
locals_hide_sunder = (
92+
True
93+
if (traceback_console.is_jupyter and locals_hide_sunder is None)
94+
else locals_hide_sunder
95+
)
96+
7197
def excepthook(
7298
type_: Type[BaseException],
7399
value: BaseException,
@@ -83,6 +109,10 @@ def excepthook(
83109
theme=theme,
84110
word_wrap=word_wrap,
85111
show_locals=show_locals,
112+
locals_max_length=locals_max_length,
113+
locals_max_string=locals_max_string,
114+
locals_hide_dunder=locals_hide_dunder,
115+
locals_hide_sunder=bool(locals_hide_sunder),
86116
indent_guides=indent_guides,
87117
suppress=suppress,
88118
max_frames=max_frames,
@@ -193,6 +223,8 @@ class Traceback:
193223
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
194224
Defaults to 10.
195225
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
226+
locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
227+
locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
196228
suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
197229
max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
198230
@@ -209,14 +241,17 @@ class Traceback:
209241
def __init__(
210242
self,
211243
trace: Optional[Trace] = None,
244+
*,
212245
width: Optional[int] = 100,
213246
extra_lines: int = 3,
214247
theme: Optional[str] = None,
215248
word_wrap: bool = False,
216249
show_locals: bool = False,
217-
indent_guides: bool = True,
218250
locals_max_length: int = LOCALS_MAX_LENGTH,
219251
locals_max_string: int = LOCALS_MAX_STRING,
252+
locals_hide_dunder: bool = True,
253+
locals_hide_sunder: bool = False,
254+
indent_guides: bool = True,
220255
suppress: Iterable[Union[str, ModuleType]] = (),
221256
max_frames: int = 100,
222257
):
@@ -238,6 +273,8 @@ def __init__(
238273
self.indent_guides = indent_guides
239274
self.locals_max_length = locals_max_length
240275
self.locals_max_string = locals_max_string
276+
self.locals_hide_dunder = locals_hide_dunder
277+
self.locals_hide_sunder = locals_hide_sunder
241278

242279
self.suppress: Sequence[str] = []
243280
for suppress_entity in suppress:
@@ -258,14 +295,17 @@ def from_exception(
258295
exc_type: Type[Any],
259296
exc_value: BaseException,
260297
traceback: Optional[TracebackType],
298+
*,
261299
width: Optional[int] = 100,
262300
extra_lines: int = 3,
263301
theme: Optional[str] = None,
264302
word_wrap: bool = False,
265303
show_locals: bool = False,
266-
indent_guides: bool = True,
267304
locals_max_length: int = LOCALS_MAX_LENGTH,
268305
locals_max_string: int = LOCALS_MAX_STRING,
306+
locals_hide_dunder: bool = True,
307+
locals_hide_sunder: bool = False,
308+
indent_guides: bool = True,
269309
suppress: Iterable[Union[str, ModuleType]] = (),
270310
max_frames: int = 100,
271311
) -> "Traceback":
@@ -284,6 +324,8 @@ def from_exception(
284324
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
285325
Defaults to 10.
286326
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
327+
locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
328+
locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
287329
suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
288330
max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
289331
@@ -297,6 +339,8 @@ def from_exception(
297339
show_locals=show_locals,
298340
locals_max_length=locals_max_length,
299341
locals_max_string=locals_max_string,
342+
locals_hide_dunder=locals_hide_dunder,
343+
locals_hide_sunder=locals_hide_sunder,
300344
)
301345
return cls(
302346
rich_traceback,
@@ -308,6 +352,8 @@ def from_exception(
308352
indent_guides=indent_guides,
309353
locals_max_length=locals_max_length,
310354
locals_max_string=locals_max_string,
355+
locals_hide_dunder=locals_hide_dunder,
356+
locals_hide_sunder=locals_hide_sunder,
311357
suppress=suppress,
312358
max_frames=max_frames,
313359
)
@@ -318,9 +364,12 @@ def extract(
318364
exc_type: Type[BaseException],
319365
exc_value: BaseException,
320366
traceback: Optional[TracebackType],
367+
*,
321368
show_locals: bool = False,
322369
locals_max_length: int = LOCALS_MAX_LENGTH,
323370
locals_max_string: int = LOCALS_MAX_STRING,
371+
locals_hide_dunder: bool = True,
372+
locals_hide_sunder: bool = False,
324373
) -> Trace:
325374
"""Extract traceback information.
326375
@@ -332,6 +381,8 @@ def extract(
332381
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
333382
Defaults to 10.
334383
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
384+
locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
385+
locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
335386
336387
Returns:
337388
Trace: A Trace instance which you can use to construct a `Traceback`.
@@ -368,13 +419,28 @@ def safe_str(_object: Any) -> str:
368419
stacks.append(stack)
369420
append = stack.frames.append
370421

422+
def get_locals(
423+
iter_locals: Iterable[Tuple[str, object]]
424+
) -> Iterable[Tuple[str, object]]:
425+
"""Extract locals from an iterator of key pairs."""
426+
if not (locals_hide_dunder or locals_hide_sunder):
427+
yield from iter_locals
428+
return
429+
for key, value in iter_locals:
430+
if locals_hide_dunder and key.startswith("__"):
431+
continue
432+
if locals_hide_sunder and key.startswith("_"):
433+
continue
434+
yield key, value
435+
371436
for frame_summary, line_no in walk_tb(traceback):
372437
filename = frame_summary.f_code.co_filename
373438
if filename and not filename.startswith("<"):
374439
if not os.path.isabs(filename):
375440
filename = os.path.join(_IMPORT_CWD, filename)
376441
if frame_summary.f_locals.get("_rich_traceback_omit", False):
377442
continue
443+
378444
frame = Frame(
379445
filename=filename or "?",
380446
lineno=line_no,
@@ -385,7 +451,7 @@ def safe_str(_object: Any) -> str:
385451
max_length=locals_max_length,
386452
max_string=locals_max_string,
387453
)
388-
for key, value in frame_summary.f_locals.items()
454+
for key, value in get_locals(frame_summary.f_locals.items())
389455
}
390456
if show_locals
391457
else None,
@@ -500,13 +566,14 @@ def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult:
500566
highlighter = ReprHighlighter()
501567
path_highlighter = PathHighlighter()
502568
if syntax_error.filename != "<stdin>":
503-
text = Text.assemble(
504-
(f" {syntax_error.filename}", "pygments.string"),
505-
(":", "pygments.text"),
506-
(str(syntax_error.lineno), "pygments.number"),
507-
style="pygments.text",
508-
)
509-
yield path_highlighter(text)
569+
if os.path.exists(syntax_error.filename):
570+
text = Text.assemble(
571+
(f" {syntax_error.filename}", "pygments.string"),
572+
(":", "pygments.text"),
573+
(str(syntax_error.lineno), "pygments.number"),
574+
style="pygments.text",
575+
)
576+
yield path_highlighter(text)
510577
syntax_error_text = highlighter(syntax_error.line.rstrip())
511578
syntax_error_text.no_wrap = True
512579
offset = min(syntax_error.offset - 1, len(syntax_error_text))
@@ -537,7 +604,6 @@ def _guess_lexer(cls, filename: str, code: str) -> str:
537604
def _render_stack(self, stack: Stack) -> RenderResult:
538605
path_highlighter = PathHighlighter()
539606
theme = self.theme
540-
code_cache: Dict[str, str] = {}
541607

542608
def read_code(filename: str) -> str:
543609
"""Read files, and cache results on filename.
@@ -548,11 +614,7 @@ def read_code(filename: str) -> str:
548614
Returns:
549615
str: Contents of file
550616
"""
551-
code = code_cache.get(filename)
552-
if code is None:
553-
code = Path(filename).read_text(encoding="utf-8", errors="replace")
554-
code_cache[filename] = code
555-
return code
617+
return "".join(linecache.getlines(filename))
556618

557619
def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
558620
if frame.locals:
@@ -591,14 +653,17 @@ def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
591653
frame_filename = frame.filename
592654
suppressed = any(frame_filename.startswith(path) for path in self.suppress)
593655

594-
text = Text.assemble(
595-
path_highlighter(Text(frame.filename, style="pygments.string")),
596-
(":", "pygments.text"),
597-
(str(frame.lineno), "pygments.number"),
598-
" in ",
599-
(frame.name, "pygments.function"),
600-
style="pygments.text",
601-
)
656+
if os.path.exists(frame.filename):
657+
text = Text.assemble(
658+
path_highlighter(Text(frame.filename, style="pygments.string")),
659+
(":", "pygments.text"),
660+
(str(frame.lineno), "pygments.number"),
661+
" in ",
662+
(frame.name, "pygments.function"),
663+
style="pygments.text",
664+
)
665+
else:
666+
text = Text.assemble("in ", (frame.name, "pygments.function"))
602667
if not frame.filename.startswith("<") and not first:
603668
yield ""
604669
yield text

0 commit comments

Comments
 (0)