From a49094ac5b05513166d968c8e129ed0cc9407dcc Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 27 Mar 2026 14:31:55 -0400 Subject: [PATCH 1/7] Added method to pretty print. --- CHANGELOG.md | 1 + cmd2/cmd2.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94dc16788..a9674f646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ prompt is displayed. - Add support for Python 3.15 by fixing various bugs related to internal `argparse` changes - Added `common_prefix` method to `cmd2.string_utils` module as a replacement for `os.path.commonprefix` since that is now deprecated in Python 3.15 + - Added `Cmd.ppretty()` as a cmd2-compatible replacement for `rich.pretty.pprint()`. ## 3.4.0 (March 3, 2026) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 1282d3cb1..70a505a8b 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -88,6 +88,7 @@ RenderableType, ) from rich.highlighter import ReprHighlighter +from rich.pretty import Pretty from rich.rule import Rule from rich.style import ( Style, @@ -1822,6 +1823,50 @@ def ppaged( rich_print_kwargs=rich_print_kwargs, ) + def ppretty( + self, + obj: Any, + *, + file: IO[str] | None = None, + indent_size: int = 4, + indent_guides: bool = True, + max_length: int | None = None, + max_string: int | None = None, + max_depth: int | None = None, + expand_all: bool = False, + ) -> None: + """Pretty print an object. + + This is a cmd2-compatible replacement for rich.pretty.pprint(). + + :param obj: object to pretty print + :param file: file stream being written to or None for self.stdout. + Defaults to None. + :param indent_size: number of spaces in indent. Defaults to 4. + :param indent_guides: enable indentation guides. Defaults to True. + :param max_length: maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + :param max_string: maximum length of strings before truncating, or None to disable. Defaults to None. + :param max_depth: maximum depth for nested data structures, or None for unlimited depth. Defaults to None. + :param expand_all: Expand all containers. Defaults to False. + """ + pretty_obj = Pretty( + obj, + indent_size=indent_size, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + max_depth=max_depth, + expand_all=expand_all, + overflow="ignore", + ) + + self.print_to( + file or self.stdout, + pretty_obj, + soft_wrap=True, + ) + def get_bottom_toolbar(self) -> list[str | tuple[str, str]] | None: """Get the bottom toolbar content. From 0325b5e72d40a97a991f29c6bfab556cfb7f2b37 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 27 Mar 2026 14:45:13 -0400 Subject: [PATCH 2/7] Updated pretty_print.py example to use ppretty(). --- examples/pretty_print.py | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/examples/pretty_print.py b/examples/pretty_print.py index bf3ce9c9c..48c6cfd37 100755 --- a/examples/pretty_print.py +++ b/examples/pretty_print.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -"""A simple example demonstrating how to pretty print JSON data in a cmd2 app using rich.""" - -from rich.json import JSON +"""A simple example demonstrating how to pretty print data.""" import cmd2 @@ -9,34 +7,17 @@ "name": "John Doe", "age": 30, "address": {"street": "123 Main St", "city": "Anytown", "state": "CA"}, - "hobbies": ["reading", "hiking", "coding"], + "hobbies": ["reading", "hiking", "coding", "cooking", "running", "painting", "music", "photography", "cycling"], } class Cmd2App(cmd2.Cmd): def __init__(self) -> None: super().__init__() - self.data = EXAMPLE_DATA - - def do_normal(self, _) -> None: - """Display the data using the normal poutput method.""" - self.poutput(self.data) - - def do_pretty(self, _) -> None: - """Display the JSON data in a pretty way using rich.""" - json_renderable = JSON.from_data( - self.data, - indent=2, - highlight=True, - skip_keys=False, - ensure_ascii=False, - check_circular=True, - allow_nan=True, - default=None, - sort_keys=False, - ) - self.poutput(json_renderable) + def do_pretty(self, _: cmd2.Statement) -> None: + """Print an object using ppretty().""" + self.ppretty(EXAMPLE_DATA) if __name__ == '__main__': From 3ee5666c3656665ba0b68ef5e741b1cc323d5067 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 27 Mar 2026 15:00:37 -0400 Subject: [PATCH 3/7] Added unit test for ppretty(). --- tests/test_cmd2.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index e971ae736..228f20238 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -3490,6 +3490,40 @@ def test_ppaged_terminal_restoration_oserror(outsim_app, monkeypatch) -> None: assert not termios_mock.tcsetattr.called +def test_ppretty(base_app: cmd2.Cmd) -> None: + # Mock the Pretty class and the print_to() method + with mock.patch('cmd2.cmd2.Pretty') as mock_pretty, mock.patch.object(cmd2.Cmd, 'print_to') as mock_print_to: + # Set up the mock return value for Pretty + mock_pretty_obj = mock.Mock() + mock_pretty.return_value = mock_pretty_obj + + test_obj = {"key": "value"} + + # Call ppretty() with some custom arguments + base_app.ppretty( + test_obj, + indent_size=2, + max_depth=5, + expand_all=True, + ) + + # Verify Pretty was instantiated with the correct arguments + mock_pretty.assert_called_once_with( + test_obj, + indent_size=2, + indent_guides=True, + max_length=None, + max_string=None, + max_depth=5, + expand_all=True, + overflow="ignore", + ) + + # Verify print_to() was called with the mock pretty object and soft_wrap=True + # It should default to self.stdout when no file is provided + mock_print_to.assert_called_once_with(base_app.stdout, mock_pretty_obj, soft_wrap=True) + + # we override cmd.parseline() so we always get consistent # command parsing by parent methods we don't override # don't need to test all the parsing logic here, because From 7b73a1d245096acd156d35d4d0f5e60056290cd1 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 27 Mar 2026 15:33:04 -0400 Subject: [PATCH 4/7] Addressed PR comments. --- CHANGELOG.md | 2 +- examples/pretty_print.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9674f646..50ab4d2cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ prompt is displayed. - **pre_prompt**: hook method that is called before the prompt is displayed, but after `prompt-toolkit` event loop has started - **read_secret**: read secrets like passwords without displaying them to the terminal + - **ppretty**: a cmd2-compatible replacement for `rich.pretty.pprint()` - New settables: - **max_column_completion_results**: (int) the maximum number of completion results to display in a single column @@ -88,7 +89,6 @@ prompt is displayed. - Add support for Python 3.15 by fixing various bugs related to internal `argparse` changes - Added `common_prefix` method to `cmd2.string_utils` module as a replacement for `os.path.commonprefix` since that is now deprecated in Python 3.15 - - Added `Cmd.ppretty()` as a cmd2-compatible replacement for `rich.pretty.pprint()`. ## 3.4.0 (March 3, 2026) diff --git a/examples/pretty_print.py b/examples/pretty_print.py index 48c6cfd37..110f9aa86 100755 --- a/examples/pretty_print.py +++ b/examples/pretty_print.py @@ -8,6 +8,9 @@ "age": 30, "address": {"street": "123 Main St", "city": "Anytown", "state": "CA"}, "hobbies": ["reading", "hiking", "coding", "cooking", "running", "painting", "music", "photography", "cycling"], + "member": True, + "vip": False, + "phone": None, } From d5cc054854fe361fcbb5485143418613935ac02b Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 27 Mar 2026 16:48:06 -0400 Subject: [PATCH 5/7] Added end parameter and comment to ppretty(). --- cmd2/cmd2.py | 4 ++++ tests/test_cmd2.py | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 70a505a8b..be752b8fe 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1834,6 +1834,7 @@ def ppretty( max_string: int | None = None, max_depth: int | None = None, expand_all: bool = False, + end: str = "\n", ) -> None: """Pretty print an object. @@ -1849,7 +1850,9 @@ def ppretty( :param max_string: maximum length of strings before truncating, or None to disable. Defaults to None. :param max_depth: maximum depth for nested data structures, or None for unlimited depth. Defaults to None. :param expand_all: Expand all containers. Defaults to False. + :param end: string to write at end of printed text. Defaults to a newline. """ + # The overflow and soft_wrap settings match the behavior of rich.pretty.pprint(). pretty_obj = Pretty( obj, indent_size=indent_size, @@ -1865,6 +1868,7 @@ def ppretty( file or self.stdout, pretty_obj, soft_wrap=True, + end=end, ) def get_bottom_toolbar(self) -> list[str | tuple[str, str]] | None: diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 228f20238..0f1e79566 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -3505,6 +3505,7 @@ def test_ppretty(base_app: cmd2.Cmd) -> None: indent_size=2, max_depth=5, expand_all=True, + end="\n\n", ) # Verify Pretty was instantiated with the correct arguments @@ -3521,7 +3522,12 @@ def test_ppretty(base_app: cmd2.Cmd) -> None: # Verify print_to() was called with the mock pretty object and soft_wrap=True # It should default to self.stdout when no file is provided - mock_print_to.assert_called_once_with(base_app.stdout, mock_pretty_obj, soft_wrap=True) + mock_print_to.assert_called_once_with( + base_app.stdout, + mock_pretty_obj, + soft_wrap=True, + end="\n\n", + ) # we override cmd.parseline() so we always get consistent From 754fc45428f718c347c89f49d6a66c68cea6b92e Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 27 Mar 2026 16:51:06 -0400 Subject: [PATCH 6/7] Updated comment. --- cmd2/cmd2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index be752b8fe..b426bca16 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1852,7 +1852,7 @@ def ppretty( :param expand_all: Expand all containers. Defaults to False. :param end: string to write at end of printed text. Defaults to a newline. """ - # The overflow and soft_wrap settings match the behavior of rich.pretty.pprint(). + # The overflow and soft_wrap values match those in rich.pretty.pprint(). pretty_obj = Pretty( obj, indent_size=indent_size, From 9f50378ccc70babae18b41b6c620f548ae3f1796 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Sat, 28 Mar 2026 10:08:35 -0400 Subject: [PATCH 7/7] Updated comment. --- cmd2/cmd2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index b426bca16..43cc0f3ed 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1853,6 +1853,8 @@ def ppretty( :param end: string to write at end of printed text. Defaults to a newline. """ # The overflow and soft_wrap values match those in rich.pretty.pprint(). + # This ensures long strings are neither truncated with ellipses nor broken + # up by injected newlines. pretty_obj = Pretty( obj, indent_size=indent_size,