From 79ccdf1e71b3f9bc397a685395a525d9d55460ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Mellstr=C3=B6m?= <11281108+harkabeeparolus@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:23:12 +0100 Subject: [PATCH 01/19] Add rich_expand parameter --- typer/core.py | 11 ++++++++++- typer/main.py | 10 ++++++++++ typer/rich_utils.py | 25 +++++++++++++++++++------ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/typer/core.py b/typer/core.py index 4dc24ada70..f6cd125dd4 100644 --- a/typer/core.py +++ b/typer/core.py @@ -172,6 +172,7 @@ def _main( standalone_mode: bool = True, windows_expand_args: bool = True, rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, + rich_expand: bool = True, **extra: Any, ) -> Any: # Typer override, duplicated from click.main() to handle custom rich exceptions @@ -216,7 +217,7 @@ def _main( raise # Typer override if rich and rich_markup_mode is not None: - rich_utils.rich_format_error(e) + rich_utils.rich_format_error(e, expand=rich_expand) else: e.show() # Typer override end @@ -635,6 +636,7 @@ def __init__( # Rich settings rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, rich_help_panel: Union[str, None] = None, + rich_expand: bool = True, ) -> None: super().__init__( name=name, @@ -652,6 +654,7 @@ def __init__( ) self.rich_markup_mode: MarkupMode = rich_markup_mode self.rich_help_panel = rich_help_panel + self.rich_expand = rich_expand def format_options( self, ctx: click.Context, formatter: click.HelpFormatter @@ -685,6 +688,7 @@ def main( standalone_mode=standalone_mode, windows_expand_args=windows_expand_args, rich_markup_mode=self.rich_markup_mode, + rich_expand=self.rich_expand, **extra, ) @@ -695,6 +699,7 @@ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> Non obj=self, ctx=ctx, markup_mode=self.rich_markup_mode, + expand=self.rich_expand, ) @@ -709,11 +714,13 @@ def __init__( # Rich settings rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, rich_help_panel: Union[str, None] = None, + rich_expand: bool = True, **attrs: Any, ) -> None: super().__init__(name=name, commands=commands, **attrs) self.rich_markup_mode: MarkupMode = rich_markup_mode self.rich_help_panel = rich_help_panel + self.rich_expand = rich_expand def format_options( self, ctx: click.Context, formatter: click.HelpFormatter @@ -748,6 +755,7 @@ def main( standalone_mode=standalone_mode, windows_expand_args=windows_expand_args, rich_markup_mode=self.rich_markup_mode, + rich_expand=self.rich_expand, **extra, ) @@ -758,6 +766,7 @@ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> Non obj=self, ctx=ctx, markup_mode=self.rich_markup_mode, + expand=self.rich_expand, ) def list_commands(self, ctx: click.Context) -> List[str]: diff --git a/typer/main.py b/typer/main.py index 36737e49ef..bfa1496826 100644 --- a/typer/main.py +++ b/typer/main.py @@ -149,6 +149,7 @@ def __init__( add_completion: bool = True, # Rich settings rich_markup_mode: MarkupMode = Default(DEFAULT_MARKUP_MODE), + rich_expand: bool = True, rich_help_panel: Union[str, None] = Default(None), pretty_exceptions_enable: bool = True, pretty_exceptions_show_locals: bool = True, @@ -156,6 +157,7 @@ def __init__( ): self._add_completion = add_completion self.rich_markup_mode: MarkupMode = rich_markup_mode + self.rich_expand = rich_expand self.rich_help_panel = rich_help_panel self.pretty_exceptions_enable = pretty_exceptions_enable self.pretty_exceptions_show_locals = pretty_exceptions_show_locals @@ -345,6 +347,7 @@ def get_group(typer_instance: Typer) -> TyperGroup: TyperInfo(typer_instance), pretty_exceptions_short=typer_instance.pretty_exceptions_short, rich_markup_mode=typer_instance.rich_markup_mode, + rich_expand=typer_instance.rich_expand, ) return group @@ -377,6 +380,7 @@ def get_command(typer_instance: Typer) -> click.Command: single_command, pretty_exceptions_short=typer_instance.pretty_exceptions_short, rich_markup_mode=typer_instance.rich_markup_mode, + rich_expand=typer_instance.rich_expand, ) if typer_instance._add_completion: click_command.params.append(click_install_param) @@ -472,6 +476,7 @@ def get_group_from_info( *, pretty_exceptions_short: bool, rich_markup_mode: MarkupMode, + rich_expand: bool, ) -> TyperGroup: assert ( group_info.typer_instance @@ -482,6 +487,7 @@ def get_group_from_info( command_info=command_info, pretty_exceptions_short=pretty_exceptions_short, rich_markup_mode=rich_markup_mode, + rich_expand=rich_expand, ) if command.name: commands[command.name] = command @@ -490,6 +496,7 @@ def get_group_from_info( sub_group_info, pretty_exceptions_short=pretty_exceptions_short, rich_markup_mode=rich_markup_mode, + rich_expand=rich_expand, ) if sub_group.name: commands[sub_group.name] = sub_group @@ -536,6 +543,7 @@ def get_group_from_info( hidden=solved_info.hidden, deprecated=solved_info.deprecated, rich_markup_mode=rich_markup_mode, + rich_expand=rich_expand, # Rich settings rich_help_panel=solved_info.rich_help_panel, ) @@ -570,6 +578,7 @@ def get_command_from_info( *, pretty_exceptions_short: bool, rich_markup_mode: MarkupMode, + rich_expand: bool, ) -> click.Command: assert command_info.callback, "A command must have a callback function" name = command_info.name or get_command_name(command_info.callback.__name__) @@ -604,6 +613,7 @@ def get_command_from_info( hidden=command_info.hidden, deprecated=command_info.deprecated, rich_markup_mode=rich_markup_mode, + rich_expand=rich_expand, # Rich settings rich_help_panel=command_info.rich_help_panel, ) diff --git a/typer/rich_utils.py b/typer/rich_utils.py index 7d603da2d7..6293c72aa8 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -11,7 +11,6 @@ import click from rich import box from rich.align import Align -from rich.columns import Columns from rich.console import Console, RenderableType, group from rich.emoji import Emoji from rich.highlighter import RegexHighlighter @@ -228,7 +227,7 @@ def _get_parameter_help( param: Union[click.Option, click.Argument, click.Parameter], ctx: click.Context, markup_mode: MarkupMode, -) -> Columns: +) -> Table: """Build primary help text for a click option or argument. Returns the prose help text for an option or argument, rendered either @@ -305,9 +304,11 @@ def _get_parameter_help( if param.required: items.append(Text(REQUIRED_LONG_STRING, style=STYLE_REQUIRED_LONG)) - # Use Columns - this allows us to group different renderable types + # Use Table - this allows us to group different renderable types # (Text, Markdown) onto a single line. - return Columns(items) + help_table = Table.grid(padding=(0, 1), expand=True) + help_table.add_row(*items) + return help_table def _make_command_help( @@ -343,6 +344,7 @@ def _print_options_panel( params: Union[List[click.Option], List[click.Argument]], ctx: click.Context, markup_mode: MarkupMode, + expand: bool, console: Console, ) -> None: options_rows: List[List[RenderableType]] = [] @@ -443,7 +445,7 @@ class MetavarHighlighter(RegexHighlighter): options_table = Table( highlight=True, show_header=False, - expand=True, + expand=False, box=box_style, **t_styles, ) @@ -454,6 +456,7 @@ class MetavarHighlighter(RegexHighlighter): options_table, border_style=STYLE_OPTIONS_PANEL_BORDER, title=name, + expand=expand, title_align=ALIGN_OPTIONS_PANEL, ) ) @@ -464,6 +467,7 @@ def _print_commands_panel( name: str, commands: List[click.Command], markup_mode: MarkupMode, + expand: bool, console: Console, cmd_len: int, ) -> None: @@ -530,6 +534,7 @@ def _print_commands_panel( commands_table, border_style=STYLE_COMMANDS_PANEL_BORDER, title=name, + expand=expand, title_align=ALIGN_COMMANDS_PANEL, ) ) @@ -540,6 +545,7 @@ def rich_format_help( obj: Union[click.Command, click.Group], ctx: click.Context, markup_mode: MarkupMode, + expand: bool, ) -> None: """Print nicely formatted help text using rich. @@ -593,6 +599,7 @@ def rich_format_help( params=default_arguments, ctx=ctx, markup_mode=markup_mode, + expand=expand, console=console, ) for panel_name, arguments in panel_to_arguments.items(): @@ -604,6 +611,7 @@ def rich_format_help( params=arguments, ctx=ctx, markup_mode=markup_mode, + expand=expand, console=console, ) default_options = panel_to_options.get(OPTIONS_PANEL_TITLE, []) @@ -612,6 +620,7 @@ def rich_format_help( params=default_options, ctx=ctx, markup_mode=markup_mode, + expand=expand, console=console, ) for panel_name, options in panel_to_options.items(): @@ -623,6 +632,7 @@ def rich_format_help( params=options, ctx=ctx, markup_mode=markup_mode, + expand=expand, console=console, ) @@ -653,6 +663,7 @@ def rich_format_help( name=COMMANDS_PANEL_TITLE, commands=default_commands, markup_mode=markup_mode, + expand=expand, console=console, cmd_len=max_cmd_len, ) @@ -664,6 +675,7 @@ def rich_format_help( name=panel_name, commands=commands, markup_mode=markup_mode, + expand=expand, console=console, cmd_len=max_cmd_len, ) @@ -677,7 +689,7 @@ def rich_format_help( console.print(Padding(Align(epilogue_text, pad=False), 1)) -def rich_format_error(self: click.ClickException) -> None: +def rich_format_error(self: click.ClickException, expand: bool = True) -> None: """Print richly formatted click errors. Called by custom exception handler to print richly formatted click errors. @@ -700,6 +712,7 @@ def rich_format_error(self: click.ClickException) -> None: Panel( highlighter(self.format_message()), border_style=STYLE_ERRORS_PANEL_BORDER, + expand=expand, title=ERRORS_PANEL_TITLE, title_align=ALIGN_ERRORS_PANEL, ) From 8cc6f45d1a25cc4ace9a2b15ec1fd8a468a6cecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Mellstr=C3=B6m?= <11281108+harkabeeparolus@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:46:15 +0100 Subject: [PATCH 02/19] Add documenation --- docs/tutorial/commands/help.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index d5ec229d68..855a5f1458 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -446,6 +446,20 @@ $ python main.py --help You can see the custom panel for the commands for "`Utils and Configs`". +## Expand or Fit + +By default, the help panels all expand to match the width of your terminal window. + +For a CLI with few parameters, especially on wide terminal windows, you might prefer a more narrow layout. +You can do this by initializing your Typer with `rich_expand=False`, like this: + +```python +app = typer.Typer(rich_expand=False) +``` + +Your help panels will all fit to their contents, which also means they will probably have different widths. +It's a different look, and sometimes you might prefer it as an option. + ## Epilog If you need, you can also add an epilog section to the help of your commands: From 2855bbcc83d1af1142a392c454b0fbd1eef73aee Mon Sep 17 00:00:00 2001 From: Fredrik Mellstrom <11281108+harkabeeparolus@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:51:22 +0100 Subject: [PATCH 03/19] Fix AssertionError: Sum of ratios must be > 0 --- typer/rich_utils.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/typer/rich_utils.py b/typer/rich_utils.py index 4fcabaa429..22c4dd1090 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -11,6 +11,7 @@ import click from rich import box from rich.align import Align +from rich.columns import Columns from rich.console import Console, RenderableType, group from rich.emoji import Emoji from rich.highlighter import RegexHighlighter @@ -233,7 +234,8 @@ def _get_parameter_help( param: Union[click.Option, click.Argument, click.Parameter], ctx: click.Context, markup_mode: MarkupMode, -) -> Table: + rich_expand: bool = True, +) -> Table | Columns: """Build primary help text for a click option or argument. Returns the prose help text for an option or argument, rendered either @@ -312,9 +314,14 @@ def _get_parameter_help( if param.required: items.append(Text(REQUIRED_LONG_STRING, style=STYLE_REQUIRED_LONG)) + if rich_expand: + # Use Columns - this allows us to group different renderable types + # (Text, Markdown) onto a single line. + return Columns(items) + # Use Table - this allows us to group different renderable types - # (Text, Markdown) onto a single line. - help_table = Table.grid(padding=(0, 1), expand=True) + # (Text, Markdown) onto a single line without using the full screen width. + help_table = Table.grid(padding=(0, 1), expand=False) help_table.add_row(*items) return help_table @@ -435,6 +442,7 @@ class MetavarHighlighter(RegexHighlighter): param=param, ctx=ctx, markup_mode=markup_mode, + rich_expand=expand, ), ] ) From 7f9e5a407071204bd852b981ae547ce0cde1b47a Mon Sep 17 00:00:00 2001 From: Fredrik Mellstrom <11281108+harkabeeparolus@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:51:49 +0100 Subject: [PATCH 04/19] Add rich_expand to typer.run() --- docs/tutorial/commands/help.md | 6 ++++++ typer/main.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index 75b1dee16b..f9d1d85f72 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -481,6 +481,12 @@ You can do this by initializing your Typer with `rich_expand=False`, like this: app = typer.Typer(rich_expand=False) ``` +Or if you use `typer.run()`, for simple apps: + +```python +typer.run(main, rich_expand=False) +``` + Your help panels will all fit to their contents, which also means they will probably have different widths. It's a different look, and sometimes you might prefer it as an option. diff --git a/typer/main.py b/typer/main.py index bd1b470717..4432cbc597 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1081,8 +1081,8 @@ def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> A return wrapper -def run(function: Callable[..., Any]) -> None: - app = Typer(add_completion=False) +def run(function: Callable[..., Any], rich_expand: bool = True) -> None: + app = Typer(add_completion=False, rich_expand=rich_expand) app.command()(function) app() From eeb9669c894a7f3f77dbf5f81d36c7f2b3d85fd0 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Wed, 19 Nov 2025 11:56:05 +0100 Subject: [PATCH 05/19] Use Union notation to avoid failing tests --- typer/rich_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/rich_utils.py b/typer/rich_utils.py index 22c4dd1090..2bf63bf777 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -235,7 +235,7 @@ def _get_parameter_help( ctx: click.Context, markup_mode: MarkupMode, rich_expand: bool = True, -) -> Table | Columns: +) -> Union[Table, Column]: """Build primary help text for a click option or argument. Returns the prose help text for an option or argument, rendered either From 6cb94dd7a6cdd67d4aed34a20da08491cfc78264 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Wed, 19 Nov 2025 12:01:32 +0100 Subject: [PATCH 06/19] Fix typo --- typer/rich_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/rich_utils.py b/typer/rich_utils.py index 2bf63bf777..b33dd60cae 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -235,7 +235,7 @@ def _get_parameter_help( ctx: click.Context, markup_mode: MarkupMode, rich_expand: bool = True, -) -> Union[Table, Column]: +) -> Union[Table, Columns]: """Build primary help text for a click option or argument. Returns the prose help text for an option or argument, rendered either From f6373a26e90bbc7e68347ec829355c802fd4ece6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Mellstr=C3=B6m?= <11281108+harkabeeparolus@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:25:49 +0100 Subject: [PATCH 07/19] Revert "Add rich_expand to typer.run()" This reverts commit 7f9e5a407071204bd852b981ae547ce0cde1b47a. --- docs/tutorial/commands/help.md | 6 ------ typer/main.py | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index f9d1d85f72..75b1dee16b 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -481,12 +481,6 @@ You can do this by initializing your Typer with `rich_expand=False`, like this: app = typer.Typer(rich_expand=False) ``` -Or if you use `typer.run()`, for simple apps: - -```python -typer.run(main, rich_expand=False) -``` - Your help panels will all fit to their contents, which also means they will probably have different widths. It's a different look, and sometimes you might prefer it as an option. diff --git a/typer/main.py b/typer/main.py index 4432cbc597..bd1b470717 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1081,8 +1081,8 @@ def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> A return wrapper -def run(function: Callable[..., Any], rich_expand: bool = True) -> None: - app = Typer(add_completion=False, rich_expand=rich_expand) +def run(function: Callable[..., Any]) -> None: + app = Typer(add_completion=False) app.command()(function) app() From b6d4511f19eccdc1cbbc95974ff7426f5470b8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Mellstr=C3=B6m?= <11281108+harkabeeparolus@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:33:49 +0100 Subject: [PATCH 08/19] Remove unneccessary defalt parameter values --- typer/core.py | 2 +- typer/rich_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/typer/core.py b/typer/core.py index 7255e6b0a4..a7ee068889 100644 --- a/typer/core.py +++ b/typer/core.py @@ -166,7 +166,7 @@ def _main( standalone_mode: bool = True, windows_expand_args: bool = True, rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, - rich_expand: bool = True, + rich_expand: bool, **extra: Any, ) -> Any: # Typer override, duplicated from click.main() to handle custom rich exceptions diff --git a/typer/rich_utils.py b/typer/rich_utils.py index b33dd60cae..2efdb9d3b5 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -234,7 +234,7 @@ def _get_parameter_help( param: Union[click.Option, click.Argument, click.Parameter], ctx: click.Context, markup_mode: MarkupMode, - rich_expand: bool = True, + rich_expand: bool, ) -> Union[Table, Columns]: """Build primary help text for a click option or argument. From 8e3d3182f57a18d7690fc99ec40212a4bc263b79 Mon Sep 17 00:00:00 2001 From: traal <11281108+harkabeeparolus@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:27:14 +0100 Subject: [PATCH 09/19] Remove unnecessary default value Co-authored-by: Sofie Van Landeghem --- typer/rich_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/rich_utils.py b/typer/rich_utils.py index 2efdb9d3b5..6fe7512e46 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -711,7 +711,7 @@ def rich_format_help( console.print(Padding(Align(epilogue_text, pad=False), 1)) -def rich_format_error(self: click.ClickException, expand: bool = True) -> None: +def rich_format_error(self: click.ClickException, expand: bool) -> None: """Print richly formatted click errors. Called by custom exception handler to print richly formatted click errors. From 82db16f7d4a177c9e6fc9ee5fa10cf8ae4a5f35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Mellstr=C3=B6m?= <11281108+harkabeeparolus@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:13:23 +0100 Subject: [PATCH 10/19] Restore unneeded expand=False in options_table --- typer/rich_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/rich_utils.py b/typer/rich_utils.py index 6fe7512e46..d1408bd8da 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -467,7 +467,7 @@ class MetavarHighlighter(RegexHighlighter): options_table = Table( highlight=True, show_header=False, - expand=False, + expand=True, box=box_style, **t_styles, ) From 633128ef01509fd67401adf4c7b1d1383dc0c128 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 26 Nov 2025 15:14:44 +0100 Subject: [PATCH 11/19] move new section to the end of the page --- docs/tutorial/commands/help.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index 75b1dee16b..59c4d580c5 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -470,20 +470,6 @@ $ python main.py --help You can see the custom panel for the commands for "`Utils and Configs`". -## Expand or Fit - -By default, the help panels all expand to match the width of your terminal window. - -For a CLI with few parameters, especially on wide terminal windows, you might prefer a more narrow layout. -You can do this by initializing your Typer with `rich_expand=False`, like this: - -```python -app = typer.Typer(rich_expand=False) -``` - -Your help panels will all fit to their contents, which also means they will probably have different widths. -It's a different look, and sometimes you might prefer it as an option. - ## Epilog If you need, you can also add an epilog section to the help of your commands: @@ -517,3 +503,17 @@ $ python main.py --help ``` + +## Expand or Fit + +By default, the help panels all expand to match the width of your terminal window. + +For a CLI with few parameters, especially on wide terminal windows, you might prefer a more narrow layout. +You can do this by initializing your Typer with `rich_expand=False`, like this: + +```python +app = typer.Typer(rich_expand=False) +``` + +Your help panels will all fit to their contents, which also means they will probably have different widths. +It's a different look, and sometimes you might prefer it as an option. From 18e81cb545b6aa456ce56eb7988bee153ee254d0 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 26 Nov 2025 15:21:41 +0100 Subject: [PATCH 12/19] add tutorial009 example --- docs/tutorial/commands/help.md | 33 +++++++++-- docs_src/commands/help/tutorial009.py | 39 +++++++++++++ .../test_help/test_tutorial009.py | 57 +++++++++++++++++++ 3 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 docs_src/commands/help/tutorial009.py create mode 100644 tests/test_tutorial/test_commands/test_help/test_tutorial009.py diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index 59c4d580c5..f257fbbaad 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -508,12 +508,35 @@ $ python main.py --help By default, the help panels all expand to match the width of your terminal window. -For a CLI with few parameters, especially on wide terminal windows, you might prefer a more narrow layout. +Sometimes, you might instead prefer that all panels fit their contents. This means that they will probably have different widths. + You can do this by initializing your Typer with `rich_expand=False`, like this: -```python -app = typer.Typer(rich_expand=False) +{* docs_src/commands/help/tutorial009.py hl[5] *} + +When you now check the `--help` option, it will look like: + +
+ +```console +$ python main.py --help + + Usage: main.py [OPTIONS] COMMAND [ARGS]... + +╭─ Options ─────────────────────────────────────────────────────────╮ +--install-completion Install completion for the current │ +│ shell. │ +--show-completion Show completion for the current │ +│ shell, to copy it or customize the │ +│ installation. │ +--help Show this message and exit. │ +╰───────────────────────────────────────────────────────────────────╯ +╭─ Commands ────────────────────────────────────────────────────────╮ +create Create a new user. ✨ │ +╰───────────────────────────────────────────────────────────────────╯ +╭─ Utils and Configs ───────────────────────────────────────────────╮ +config Configure the system. 🔧 │ +╰───────────────────────────────────────────────────────────────────╯ ``` -Your help panels will all fit to their contents, which also means they will probably have different widths. -It's a different look, and sometimes you might prefer it as an option. +
diff --git a/docs_src/commands/help/tutorial009.py b/docs_src/commands/help/tutorial009.py new file mode 100644 index 0000000000..0755ff2f1b --- /dev/null +++ b/docs_src/commands/help/tutorial009.py @@ -0,0 +1,39 @@ +from typing import Union + +import typer + +app = typer.Typer(rich_markup_mode="rich", rich_expand=False) + + +@app.command() +def create( + username: str = typer.Argument(..., help="The username to create"), + lastname: str = typer.Argument( + "", help="The last name of the new user", rich_help_panel="Secondary Arguments" + ), + force: bool = typer.Option(False, help="Force the creation of the user"), + age: Union[int, None] = typer.Option( + None, help="The age of the new user", rich_help_panel="Additional Data" + ), + favorite_color: Union[str, None] = typer.Option( + None, + help="The favorite color of the new user", + rich_help_panel="Additional Data", + ), +): + """ + [green]Create[/green] a new user. :sparkles: + """ + print(f"Creating user: {username}") + + +@app.command(rich_help_panel="Utils and Configs") +def config(configuration: str): + """ + [blue]Configure[/blue] the system. :wrench: + """ + print(f"Configuring the system with: {configuration}") + + +if __name__ == "__main__": + app() diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial009.py b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py new file mode 100644 index 0000000000..dbc8cddd72 --- /dev/null +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py @@ -0,0 +1,57 @@ +import os +import subprocess +import sys + +from typer.testing import CliRunner + +from docs_src.commands.help import tutorial009 as mod + +app = mod.app + +runner = CliRunner() + + +def test_main_help(): + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + assert "create" in result.output + assert "Create a new user. ✨" in result.output + assert "Utils and Configs" in result.output + assert "config" in result.output + assert "Configure the system. 🔧" in result.output + + +def test_create_help(): + result = runner.invoke(app, ["create", "--help"]) + assert result.exit_code == 0 + assert "username" in result.output + assert "The username to create" in result.output + assert "Secondary Arguments" in result.output + assert "lastname" in result.output + assert "The last name of the new user" in result.output + assert "--force" in result.output + assert "--no-force" in result.output + assert "Force the creation of the user" in result.output + assert "Additional Data" in result.output + assert "--age" in result.output + assert "The age of the new user" in result.output + assert "--favorite-color" in result.output + assert "The favorite color of the new user" in result.output + + +def test_call(): + # Mainly for coverage + result = runner.invoke(app, ["create", "Morty"]) + assert result.exit_code == 0 + result = runner.invoke(app, ["config", "Morty"]) + assert result.exit_code == 0 + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, + ) + assert "Usage" in result.stdout From 784907c2ed6fe87511884dadc65cb1c868c0111d Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 26 Nov 2025 16:19:50 +0100 Subject: [PATCH 13/19] update emoji --- docs/tutorial/commands/help.md | 2 +- docs_src/commands/help/tutorial009.py | 2 +- .../test_help/test_tutorial009.py | 20 +------------------ 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index f257fbbaad..616eadd1b7 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -462,7 +462,7 @@ $ python main.py --help create Create a new user. ✨ │ ╰───────────────────────────────────────────────────────────────────╯ ╭─ Utils and Configs ───────────────────────────────────────────────╮ -config Configure the system. 🔧 │ +config Configure the system. ⚙ │ ╰───────────────────────────────────────────────────────────────────╯ ``` diff --git a/docs_src/commands/help/tutorial009.py b/docs_src/commands/help/tutorial009.py index 0755ff2f1b..41f4162c4b 100644 --- a/docs_src/commands/help/tutorial009.py +++ b/docs_src/commands/help/tutorial009.py @@ -30,7 +30,7 @@ def create( @app.command(rich_help_panel="Utils and Configs") def config(configuration: str): """ - [blue]Configure[/blue] the system. :wrench: + [blue]Configure[/blue] the system. :gear: """ print(f"Configuring the system with: {configuration}") diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial009.py b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py index dbc8cddd72..857166f99f 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial009.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py @@ -18,25 +18,7 @@ def test_main_help(): assert "Create a new user. ✨" in result.output assert "Utils and Configs" in result.output assert "config" in result.output - assert "Configure the system. 🔧" in result.output - - -def test_create_help(): - result = runner.invoke(app, ["create", "--help"]) - assert result.exit_code == 0 - assert "username" in result.output - assert "The username to create" in result.output - assert "Secondary Arguments" in result.output - assert "lastname" in result.output - assert "The last name of the new user" in result.output - assert "--force" in result.output - assert "--no-force" in result.output - assert "Force the creation of the user" in result.output - assert "Additional Data" in result.output - assert "--age" in result.output - assert "The age of the new user" in result.output - assert "--favorite-color" in result.output - assert "The favorite color of the new user" in result.output + assert "Configure the system. ⚙" in result.output def test_call(): From 28aab8b83a8a27fef170d00ef89019dc758117c5 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 26 Nov 2025 16:34:49 +0100 Subject: [PATCH 14/19] show rich_expand=False results --- docs/tutorial/commands/help.md | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index 616eadd1b7..5d7f51ad5e 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -508,7 +508,7 @@ $ python main.py --help By default, the help panels all expand to match the width of your terminal window. -Sometimes, you might instead prefer that all panels fit their contents. This means that they will probably have different widths. +Sometimes, you might prefer that all panels fit their contents instead. This means that they will probably have different widths. You can do this by initializing your Typer with `rich_expand=False`, like this: @@ -523,20 +523,17 @@ $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... -╭─ Options ─────────────────────────────────────────────────────────╮ ---install-completion Install completion for the current │ -│ shell. │ ---show-completion Show completion for the current │ -│ shell, to copy it or customize the │ -│ installation. │ ---help Show this message and exit. │ -╰───────────────────────────────────────────────────────────────────╯ -╭─ Commands ────────────────────────────────────────────────────────╮ -create Create a new user. ✨ │ -╰───────────────────────────────────────────────────────────────────╯ -╭─ Utils and Configs ───────────────────────────────────────────────╮ -config Configure the system. 🔧 │ -╰───────────────────────────────────────────────────────────────────╯ +╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────╮ +--install-completion Install completion for the current shell. │ +--show-completion Show completion for the current shell, to copy it or customize the installation. │ +--help Show this message and exit. │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭─ Commands ─────────────────────────────╮ +create Create a new user. ✨ │ +╰────────────────────────────────────────╯ +╭─ Utils and Configs ──────────────────────╮ +config Configure the system. 🔧 │ +╰──────────────────────────────────────────╯ ``` From acb1b596fbfbef92313856eba174cbc401a1dfee Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 26 Nov 2025 17:03:04 +0100 Subject: [PATCH 15/19] update example (simplify) --- docs/tutorial/commands/help.md | 31 ++++++++++++++++----------- docs_src/commands/help/tutorial009.py | 10 ++++----- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index 5d7f51ad5e..3eb5fe8c2d 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -519,21 +519,26 @@ When you now check the `--help` option, it will look like:
```console -$ python main.py --help +$ python main.py create --help - Usage: main.py [OPTIONS] COMMAND [ARGS]... + Usage: main.py create [OPTIONS] USERNAME [LASTNAME] -╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────╮ ---install-completion Install completion for the current shell. │ ---show-completion Show completion for the current shell, to copy it or customize the installation. │ ---help Show this message and exit. │ -╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ -╭─ Commands ─────────────────────────────╮ -create Create a new user. ✨ │ -╰────────────────────────────────────────╯ -╭─ Utils and Configs ──────────────────────╮ -config Configure the system. 🔧 │ -╰──────────────────────────────────────────╯ + Create a new user. ✨ + +╭─ Arguments ──────────────────────────────────────╮ +* username TEXT The username [required] │ +╰──────────────────────────────────────────────────╯ +╭─ Secondary Arguments ─────────────────────╮ +│ lastname [LASTNAME] The last name │ +╰───────────────────────────────────────────╯ +╭─ Options ─────────────────────────────────────────────────────────╮ +--force --no-force Force the creation [required] │ +--help Show this message and exit. │ +╰───────────────────────────────────────────────────────────────────╯ +╭─ Additional Data ───────────────────────────────────╮ +--age INTEGER The age │ +--favorite-color TEXT The favorite color │ +╰─────────────────────────────────────────────────────╯ ```
diff --git a/docs_src/commands/help/tutorial009.py b/docs_src/commands/help/tutorial009.py index 41f4162c4b..373e14c2e3 100644 --- a/docs_src/commands/help/tutorial009.py +++ b/docs_src/commands/help/tutorial009.py @@ -7,17 +7,17 @@ @app.command() def create( - username: str = typer.Argument(..., help="The username to create"), + username: str = typer.Argument(..., help="The username"), lastname: str = typer.Argument( - "", help="The last name of the new user", rich_help_panel="Secondary Arguments" + "", help="The last name", rich_help_panel="Secondary Arguments" ), - force: bool = typer.Option(False, help="Force the creation of the user"), + force: bool = typer.Option(..., help="Force the creation"), age: Union[int, None] = typer.Option( - None, help="The age of the new user", rich_help_panel="Additional Data" + None, help="The age", rich_help_panel="Additional Data" ), favorite_color: Union[str, None] = typer.Option( None, - help="The favorite color of the new user", + help="The favorite color", rich_help_panel="Additional Data", ), ): From b0d031ffbffb7a66c41f2d9ba71af3e9a2e47696 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Wed, 26 Nov 2025 17:14:14 +0100 Subject: [PATCH 16/19] revert --- docs/tutorial/commands/help.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index 3eb5fe8c2d..2b70d4fe8c 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -462,7 +462,7 @@ $ python main.py --help create Create a new user. ✨ │ ╰───────────────────────────────────────────────────────────────────╯ ╭─ Utils and Configs ───────────────────────────────────────────────╮ -config Configure the system. ⚙ │ +config Configure the system. 🔧 │ ╰───────────────────────────────────────────────────────────────────╯ ``` From b3b41ec465df1c775db498405bc0df5054a52be2 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 26 Nov 2025 17:17:21 +0100 Subject: [PATCH 17/19] fix test --- tests/test_tutorial/test_commands/test_help/test_tutorial009.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial009.py b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py index 857166f99f..ab75d40d66 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial009.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py @@ -23,7 +23,7 @@ def test_main_help(): def test_call(): # Mainly for coverage - result = runner.invoke(app, ["create", "Morty"]) + result = runner.invoke(app, ["create", "Morty", "--force"]) assert result.exit_code == 0 result = runner.invoke(app, ["config", "Morty"]) assert result.exit_code == 0 From df3209c6d376d2223d46edee7a7d389dd787f965 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 26 Nov 2025 17:27:08 +0100 Subject: [PATCH 18/19] fix outline --- docs/tutorial/commands/help.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index 2b70d4fe8c..b9f9d45070 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -531,13 +531,13 @@ $ python main.py create --help ╭─ Secondary Arguments ─────────────────────╮ │ lastname [LASTNAME] The last name │ ╰───────────────────────────────────────────╯ -╭─ Options ─────────────────────────────────────────────────────────╮ ---force --no-force Force the creation [required] │ ---help Show this message and exit. │ -╰───────────────────────────────────────────────────────────────────╯ +╭─ Options ────────────────────────────────────────────────╮ +--force --no-force Force the creation [required] │ +--help Show this message and exit. │ +╰──────────────────────────────────────────────────────────╯ ╭─ Additional Data ───────────────────────────────────╮ ---age INTEGER The age │ ---favorite-color TEXT The favorite color │ +--age INTEGER The age │ +--favorite-color TEXT The favorite color │ ╰─────────────────────────────────────────────────────╯ ``` From beecfd950ff8b0b02faee2ca5fb9de67d056bded Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 7 Jan 2026 17:20:37 +0100 Subject: [PATCH 19/19] use new py39 format for tutorial file --- docs/tutorial/commands/help.md | 2 +- docs_src/commands/help/{tutorial009.py => tutorial009_py39.py} | 0 tests/test_tutorial/test_commands/test_help/test_tutorial009.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename docs_src/commands/help/{tutorial009.py => tutorial009_py39.py} (100%) diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index 20c81319c3..05c3340e9d 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -512,7 +512,7 @@ Sometimes, you might prefer that all panels fit their contents instead. This mea You can do this by initializing your Typer with `rich_expand=False`, like this: -{* docs_src/commands/help/tutorial009.py hl[5] *} +{* docs_src/commands/help/tutorial009_py39.py hl[5] *} When you now check the `--help` option, it will look like: diff --git a/docs_src/commands/help/tutorial009.py b/docs_src/commands/help/tutorial009_py39.py similarity index 100% rename from docs_src/commands/help/tutorial009.py rename to docs_src/commands/help/tutorial009_py39.py diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial009.py b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py index ab75d40d66..98ffbeb064 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial009.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py @@ -4,7 +4,7 @@ from typer.testing import CliRunner -from docs_src.commands.help import tutorial009 as mod +from docs_src.commands.help import tutorial009_py39 as mod app = mod.app