Skip to content

Commit 89be528

Browse files
committed
Made traceback settings customizable.
1 parent fe308da commit 89be528

7 files changed

Lines changed: 50 additions & 76 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ prompt is displayed.
9797
`set_theme()` functions to support lazy initialization and safer in-place updates of the
9898
theme.
9999
- Renamed `Cmd._command_parsers` to `Cmd.command_parsers`.
100+
- Removed `RichPrintKwargs` `TypedDict` in favor of using `Mapping[str, Any]`, allowing for
101+
greater flexibility in passing keyword arguments to `console.print()` calls.
100102
- Enhancements
101103
- New `cmd2.Cmd` parameters
102104
- **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These
@@ -114,8 +116,9 @@ prompt is displayed.
114116
- **read_secret**: read secrets like passwords without displaying them to the terminal
115117
- **ppretty**: a cmd2-compatible replacement for `rich.pretty.pprint()`
116118
- New settables:
117-
- **max_column_completion_results**: (int) the maximum number of completion results to
118-
display in a single column
119+
- **max_column_completion_results**: (int) Maximum number of completion results to display
120+
in a single column
121+
- **traceback_show_locals**: (bool) Display local variables in tracebacks
119122
- `cmd2.Cmd.select` has been revamped to use the
120123
[choice](https://python-prompt-toolkit.readthedocs.io/en/3.0.52/pages/asking_for_a_choice.html)
121124
function from `prompt-toolkit` when both **stdin** and **stdout** are TTYs
@@ -131,6 +134,7 @@ prompt is displayed.
131134
specific `cmd2.Cmd` subclass (e.g.,`class MyCommandSet(CommandSet[MyApp]):`). This provides
132135
full type hints and IDE autocompletion for `self._cmd` without needing to override and cast
133136
the property.
137+
- Added `traceback_kwargs` attribute to allow customization of Rich-based tracebacks.
134138

135139
## 3.5.0 (April 13, 2026)
136140

cmd2/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
MetavarTypeCmd2HelpFormatter,
5151
RawDescriptionCmd2HelpFormatter,
5252
RawTextCmd2HelpFormatter,
53-
RichPrintKwargs,
5453
TextGroup,
5554
get_theme,
5655
set_theme,
@@ -105,7 +104,6 @@
105104
"MetavarTypeCmd2HelpFormatter",
106105
"RawDescriptionCmd2HelpFormatter",
107106
"RawTextCmd2HelpFormatter",
108-
"RichPrintKwargs",
109107
"set_theme",
110108
"TextGroup",
111109
# String Utils

cmd2/cmd2.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@
154154
Cmd2ExceptionConsole,
155155
Cmd2GeneralConsole,
156156
Cmd2SimpleTable,
157-
RichPrintKwargs,
158157
TextGroup,
159158
)
160159
from .styles import Cmd2Style
@@ -474,6 +473,19 @@ def __init__(
474473
self.scripts_add_to_history = True # Scripts and pyscripts add commands to history
475474
self.timing = False # Prints elapsed time for each command
476475

476+
# Default settings for Rich tracebacks created by format_exception().
477+
# This dictionary can contain any parameter accepted by the
478+
# rich.traceback.Traceback class. You can modify it to adjust
479+
# the detail and layout of tracebacks.
480+
self.traceback_kwargs: dict[str, Any] = {
481+
"width": 100,
482+
"code_width": None, # Show all code characters
483+
"show_locals": False,
484+
"max_frames": 100,
485+
"word_wrap": True, # Wrap long lines of code instead of truncate
486+
"indent_guides": True,
487+
}
488+
477489
# Cached Rich consoles used by core print methods.
478490
self._console_cache = _ConsoleCache()
479491

@@ -1339,19 +1351,28 @@ def allow_style_type(value: str) -> ru.AllowStyle:
13391351
self.add_settable(Settable("quiet", bool, "Don't print nonessential feedback", self))
13401352
self.add_settable(Settable("scripts_add_to_history", bool, "Scripts and pyscripts add commands to history", self))
13411353
self.add_settable(Settable("timing", bool, "Report execution times", self))
1342-
1343-
# ----- Methods related to presenting output to the user -----
1354+
self.add_settable(Settable("traceback_show_locals", bool, "Display local variables in tracebacks", self))
13441355

13451356
@property
13461357
def allow_style(self) -> ru.AllowStyle:
1347-
"""Read-only property needed to support do_set when it reads allow_style."""
1358+
"""Property needed to support do_set when it reads allow_style."""
13481359
return ru.ALLOW_STYLE
13491360

13501361
@allow_style.setter
13511362
def allow_style(self, new_val: ru.AllowStyle) -> None:
13521363
"""Setter property needed to support do_set when it updates allow_style."""
13531364
ru.ALLOW_STYLE = new_val
13541365

1366+
@property
1367+
def traceback_show_locals(self) -> bool:
1368+
"""Property needed to support do_set when it reads traceback_show_locals."""
1369+
return cast(bool, self.traceback_kwargs.get("show_locals", False))
1370+
1371+
@traceback_show_locals.setter
1372+
def traceback_show_locals(self, value: bool) -> None:
1373+
"""Setter property needed to support do_set when it updates traceback_show_locals."""
1374+
self.traceback_kwargs["show_locals"] = value
1375+
13551376
@property
13561377
def visible_prompt(self) -> str:
13571378
"""Read-only property to get the visible prompt with any ANSI style sequences stripped.
@@ -1426,7 +1447,7 @@ def print_to(
14261447
emoji: bool = False,
14271448
markup: bool = False,
14281449
highlight: bool = False,
1429-
rich_print_kwargs: RichPrintKwargs | None = None,
1450+
rich_print_kwargs: Mapping[str, Any] | None = None,
14301451
**kwargs: Any, # noqa: ARG002
14311452
) -> None:
14321453
"""Print objects to a given file stream.
@@ -1442,7 +1463,8 @@ def print_to(
14421463
:param style: optional style to apply to output
14431464
:param soft_wrap: Enable soft wrap mode. Defaults to True.
14441465
If True, text that doesn't fit will run on to the following line,
1445-
just like with print(). This is useful for raw text and logs.
1466+
just like the built-in print() function. This is useful for raw text
1467+
and logs.
14461468
If False, Rich wraps text to fit the terminal width.
14471469
Set this to False when printing structured Renderables like
14481470
Tables, Panels, or Columns to ensure they render as expected.
@@ -1457,10 +1479,10 @@ def print_to(
14571479
strings, such as common Python data types like numbers, booleans, or None.
14581480
This is particularly useful when pretty printing objects like lists and
14591481
dictionaries to display them in color. Defaults to False.
1460-
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
1482+
:param rich_print_kwargs: optional additional keyword arguments to pass to console.print().
14611483
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
14621484
method and still call `super()` without encountering unexpected keyword argument errors.
1463-
These arguments are not passed to Rich's Console.print().
1485+
These arguments are not passed to console.print().
14641486
14651487
See the Rich documentation for more details on emoji codes, markup tags, and highlighting.
14661488
"""
@@ -1499,7 +1521,7 @@ def poutput(
14991521
emoji: bool = False,
15001522
markup: bool = False,
15011523
highlight: bool = False,
1502-
rich_print_kwargs: RichPrintKwargs | None = None,
1524+
rich_print_kwargs: Mapping[str, Any] | None = None,
15031525
**kwargs: Any, # noqa: ARG002
15041526
) -> None:
15051527
"""Print objects to self.stdout.
@@ -1531,7 +1553,7 @@ def perror(
15311553
emoji: bool = False,
15321554
markup: bool = False,
15331555
highlight: bool = False,
1534-
rich_print_kwargs: RichPrintKwargs | None = None,
1556+
rich_print_kwargs: Mapping[str, Any] | None = None,
15351557
**kwargs: Any, # noqa: ARG002
15361558
) -> None:
15371559
"""Print objects to sys.stderr.
@@ -1564,7 +1586,7 @@ def psuccess(
15641586
emoji: bool = False,
15651587
markup: bool = False,
15661588
highlight: bool = False,
1567-
rich_print_kwargs: RichPrintKwargs | None = None,
1589+
rich_print_kwargs: Mapping[str, Any] | None = None,
15681590
**kwargs: Any, # noqa: ARG002
15691591
) -> None:
15701592
"""Wrap poutput, but apply Cmd2Style.SUCCESS.
@@ -1594,7 +1616,7 @@ def pwarning(
15941616
emoji: bool = False,
15951617
markup: bool = False,
15961618
highlight: bool = False,
1597-
rich_print_kwargs: RichPrintKwargs | None = None,
1619+
rich_print_kwargs: Mapping[str, Any] | None = None,
15981620
**kwargs: Any, # noqa: ARG002
15991621
) -> None:
16001622
"""Wrap perror, but apply Cmd2Style.WARNING.
@@ -1626,13 +1648,7 @@ def format_exception(self, exception: BaseException) -> str:
16261648
with console.capture() as capture:
16271649
# Only print a traceback if we're in debug mode and one exists.
16281650
if self.debug and sys.exc_info() != (None, None, None):
1629-
traceback = Traceback(
1630-
width=None, # Use all available width
1631-
code_width=None, # Use all available width
1632-
show_locals=True,
1633-
max_frames=0, # 0 means full traceback.
1634-
word_wrap=True, # Wrap long lines of code instead of truncate
1635-
)
1651+
traceback = Traceback(**self.traceback_kwargs)
16361652
console.print(traceback, end="")
16371653

16381654
else:
@@ -1690,7 +1706,7 @@ def pfeedback(
16901706
emoji: bool = False,
16911707
markup: bool = False,
16921708
highlight: bool = False,
1693-
rich_print_kwargs: RichPrintKwargs | None = None,
1709+
rich_print_kwargs: Mapping[str, Any] | None = None,
16941710
**kwargs: Any, # noqa: ARG002
16951711
) -> None:
16961712
"""Print nonessential feedback.
@@ -1740,7 +1756,7 @@ def ppaged(
17401756
emoji: bool = False,
17411757
markup: bool = False,
17421758
highlight: bool = False,
1743-
rich_print_kwargs: RichPrintKwargs | None = None,
1759+
rich_print_kwargs: Mapping[str, Any] | None = None,
17441760
**kwargs: Any, # noqa: ARG002
17451761
) -> None:
17461762
"""Print output using a pager.

cmd2/rich_utils.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
IO,
1414
Any,
1515
ClassVar,
16-
TypedDict,
1716
)
1817

1918
from rich.box import SIMPLE_HEAD
@@ -631,27 +630,6 @@ def __init__(self, *, file: IO[str] | None = None) -> None:
631630
)
632631

633632

634-
class RichPrintKwargs(TypedDict, total=False):
635-
"""Infrequently used Rich Console.print() keyword arguments.
636-
637-
These arguments are supported by cmd2's print methods (e.g., poutput())
638-
via their ``rich_print_kwargs`` parameter.
639-
640-
See Rich's Console.print() documentation for full details:
641-
https://rich.readthedocs.io/en/stable/reference/console.html#rich.console.Console.print
642-
643-
Note: All fields are optional (total=False). If a key is not present,
644-
Rich's default behavior for that argument will apply.
645-
"""
646-
647-
overflow: OverflowMethod | None
648-
no_wrap: bool | None
649-
width: int | None
650-
height: int | None
651-
crop: bool
652-
new_line_start: bool
653-
654-
655633
class Cmd2SimpleTable(Table):
656634
"""A clean, lightweight Rich Table tailored for cmd2's internal use."""
657635

docs/features/builtin_commands.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ application:
9090
quiet False Don't print nonessential feedback
9191
scripts_add_to_history True Scripts and pyscripts add commands to history
9292
timing False Report execution times
93+
traceback_show_locals False Display local variables in tracebacks
9394
```
9495

9596
Any of these user-settable parameters can be set while running your app with the `set` command like

docs/features/generating_output.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ complex output:
4040
- `end`: string to write at end of printed text. Defaults to a newline
4141
- `style`: optional style to apply to output
4242
- `soft_wrap`: Enable soft wrap mode. If True, lines of text will not be word-wrapped or cropped to fit the terminal width. Defaults to True
43+
- `justify`: justify method ("left", "center", "right", "full"). Defaults to None.
4344
- `emoji`: If True, Rich will replace emoji codes (e.g., 😃) with their corresponding Unicode characters. Defaults to False
4445
- `markup`: If True, Rich will interpret strings with tags (e.g., [bold]hello[/bold]) as styled output. Defaults to False
4546
- `highlight`: If True, Rich will automatically apply highlighting to elements within strings, such as common Python data types like numbers, booleans, or None.
46-
- `rich_print_kwargs`: optional additional keyword arguments to pass to Rich's `Console.print()`
47+
- `rich_print_kwargs`: optional additional keyword arguments to pass to `console.print()`
48+
- `kwargs`: arbitrary keyword arguments to support extending the print methods. These are not passed to `console.print()`.
4749

4850
## Ordinary Output
4951

tests/test_cmd2.py

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
Color,
2828
CommandSet,
2929
Completions,
30-
RichPrintKwargs,
3130
clipboard,
3231
constants,
3332
exceptions,
@@ -2465,7 +2464,7 @@ def test_poutput_emoji(outsim_app):
24652464

24662465
@with_ansi_style(ru.AllowStyle.ALWAYS)
24672466
def test_poutput_justify_and_width(outsim_app):
2468-
rich_print_kwargs = RichPrintKwargs(width=10)
2467+
rich_print_kwargs = {"width": 10}
24692468

24702469
# Use a styled-string when justifying to check if its display width is correct.
24712470
outsim_app.poutput(
@@ -2477,9 +2476,8 @@ def test_poutput_justify_and_width(outsim_app):
24772476
assert out == " \x1b[34mHello\x1b[0m\n"
24782477

24792478

2480-
@with_ansi_style(ru.AllowStyle.ALWAYS)
2481-
def test_poutput_no_wrap_and_overflow(outsim_app):
2482-
rich_print_kwargs = RichPrintKwargs(no_wrap=True, overflow="ellipsis", width=10)
2479+
def test_rich_print_kwargs(outsim_app):
2480+
rich_print_kwargs = {"no_wrap": True, "overflow": "ellipsis", "width": 10}
24832481

24842482
outsim_app.poutput(
24852483
"This is longer than width.",
@@ -2500,29 +2498,6 @@ def test_poutput_pretty_print(outsim_app):
25002498
assert out.startswith("\x1b[1m{\x1b[0m\x1b[1;36m1\x1b[0m: \x1b[32m'hello'\x1b[0m")
25012499

25022500

2503-
@with_ansi_style(ru.AllowStyle.ALWAYS)
2504-
def test_poutput_all_keyword_args(outsim_app):
2505-
"""Test that all fields in RichPrintKwargs are recognized by Rich's Console.print()."""
2506-
rich_print_kwargs = RichPrintKwargs(
2507-
overflow="ellipsis",
2508-
no_wrap=True,
2509-
width=40,
2510-
height=50,
2511-
crop=False,
2512-
new_line_start=True,
2513-
)
2514-
2515-
outsim_app.poutput(
2516-
"My string",
2517-
rich_print_kwargs=rich_print_kwargs,
2518-
)
2519-
2520-
# Verify that something printed which means Console.print() didn't
2521-
# raise a TypeError for an unexpected keyword argument.
2522-
out = outsim_app.stdout.getvalue()
2523-
assert "My string" in out
2524-
2525-
25262501
@pytest.mark.parametrize(
25272502
"stream",
25282503
["stdout", "stderr"],

0 commit comments

Comments
 (0)