Skip to content

Commit 9b37e89

Browse files
committed
feat(rich): expose code_width and use the full terminal width by default
1 parent 244fde4 commit 9b37e89

File tree

3 files changed

+65
-25
lines changed

3 files changed

+65
-25
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/
3232
- `structlog.processors.TimeStamper` now produces internally timezone-aware `datetime` objects.
3333
Default output hasn't changed, but you can now use `%z` in your *fmt* string.
3434
[#709](https://github.com/hynek/structlog/pull/709)
35+
- `structlog.dev.RichTracebackFormatter` now expose the upstream `code_width` parameter.
36+
Default `width` is now `None` for full terminal width.
37+
Full terminal width is now handled by `brich` itself, bringing support for reflow and `COLUMN` environment variable.
38+
Using `-1` as width is now depreccated and automatically replaced by `None`.
39+
[#717](https://github.com/hynek/structlog/pull/717)
3540

3641

3742
### Fixed

src/structlog/dev.py

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from __future__ import annotations
1313

14-
import shutil
1514
import sys
1615
import warnings
1716

@@ -348,10 +347,15 @@ class RichTracebackFormatter:
348347
349348
See :class:`rich.traceback.Traceback` for details on the arguments.
350349
351-
If a *width* of -1 is passed, the terminal width is used. If the width
350+
If ``width`` is ``None``, the terminal width is used. If the width
352351
can't be determined, fall back to 80.
353352
354353
.. versionadded:: 23.2.0
354+
355+
.. versionchanged:: 25.3.0
356+
Default ``width`` is ``None`` to have full width and reflow support.
357+
``-1`` has width is deprecated, use ``None`` instead.
358+
``code_width`` is now exposed. ``word_wrap`` is enabled by default.
355359
"""
356360

357361
color_system: Literal[
@@ -360,9 +364,10 @@ class RichTracebackFormatter:
360364
show_locals: bool = True
361365
max_frames: int = 100
362366
theme: str | None = None
363-
word_wrap: bool = False
367+
word_wrap: bool = True
364368
extra_lines: int = 3
365-
width: int = 100
369+
width: int | None = None
370+
code_width: int | None = 88
366371
indent_guides: bool = True
367372
locals_max_length: int = 10
368373
locals_max_string: int = 80
@@ -372,29 +377,37 @@ class RichTracebackFormatter:
372377

373378
def __call__(self, sio: TextIO, exc_info: ExcInfo) -> None:
374379
if self.width == -1:
375-
self.width, _ = shutil.get_terminal_size((80, 0))
380+
warnings.warn(
381+
"Use None to use the terminal width instead of -1.",
382+
DeprecationWarning,
383+
stacklevel=2,
384+
)
385+
self.width = None
376386

377387
sio.write("\n")
378388

379-
Console(
389+
console = Console(
380390
file=sio, color_system=self.color_system, width=self.width
381-
).print(
382-
Traceback.from_exception(
383-
*exc_info,
384-
show_locals=self.show_locals,
385-
max_frames=self.max_frames,
386-
theme=self.theme,
387-
word_wrap=self.word_wrap,
388-
extra_lines=self.extra_lines,
389-
width=self.width,
390-
indent_guides=self.indent_guides,
391-
locals_max_length=self.locals_max_length,
392-
locals_max_string=self.locals_max_string,
393-
locals_hide_dunder=self.locals_hide_dunder,
394-
locals_hide_sunder=self.locals_hide_sunder,
395-
suppress=self.suppress,
396-
)
397391
)
392+
tb = Traceback.from_exception(
393+
*exc_info,
394+
show_locals=self.show_locals,
395+
max_frames=self.max_frames,
396+
theme=self.theme,
397+
word_wrap=self.word_wrap,
398+
extra_lines=self.extra_lines,
399+
width=self.width,
400+
indent_guides=self.indent_guides,
401+
locals_max_length=self.locals_max_length,
402+
locals_max_string=self.locals_max_string,
403+
locals_hide_dunder=self.locals_hide_dunder,
404+
locals_hide_sunder=self.locals_hide_sunder,
405+
suppress=self.suppress,
406+
)
407+
if hasattr(tb, "code_width"):
408+
# `code_width` requires `rich>=13.8.0`
409+
tb.code_width = self.code_width
410+
console.print(tb)
398411

399412

400413
rich_traceback = RichTracebackFormatter()

tests/test_dev.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -671,17 +671,39 @@ def test_does_not_blow_up(self, sio):
671671

672672
def test_width_minus_one(self, sio):
673673
"""
674-
If width is -1, it's replaced by the terminal width on first use.
674+
If width is -1, it raise a DeprecationWarning and replaced by None to let `rich` handle it.
675675
"""
676676
rtf = dev.RichTracebackFormatter(width=-1)
677677

678-
with mock.patch("shutil.get_terminal_size", return_value=(42, 0)):
678+
with pytest.deprecated_call():
679679
try:
680680
0 / 0
681681
except ZeroDivisionError:
682682
rtf(sio, sys.exc_info())
683683

684-
assert 42 == rtf.width
684+
assert rtf.width is None
685+
686+
@pytest.mark.parametrize("code_width_support", [True, False])
687+
def test_code_width_support(self, sio, code_width_support):
688+
"""
689+
If rich does not support code_width, it should not fail
690+
"""
691+
tb = mock.Mock()
692+
if not code_width_support:
693+
tb.code_width.side_effect = AttributeError("code_width")
694+
695+
with mock.patch.object(
696+
dev.Traceback, "from_exception", return_value=tb
697+
) as factory:
698+
try:
699+
0 / 0
700+
except ZeroDivisionError:
701+
dev.rich_traceback(sio, sys.exc_info())
702+
703+
assert "code_width" not in factory.call_args.kwargs
704+
705+
if code_width_support:
706+
assert tb.code_width == 88
685707

686708

687709
@pytest.mark.skipif(

0 commit comments

Comments
 (0)