From e361777f4ce5fc815b6933dc4992d92b0110b050 Mon Sep 17 00:00:00 2001 From: Rodion Steshenko Date: Wed, 18 Feb 2026 10:07:51 -0500 Subject: [PATCH 1/2] Fix #1156: Argument metavar displays as name in rich help panel When a custom metavar is set on an Argument via typer.Argument(metavar=...), the rich help panel now correctly shows the metavar as the argument name and the actual type (e.g. TEXT, INTEGER) in the type column. Previously, the parameter name was shown in the name column and the metavar was incorrectly placed in the type column. --- tests/test_argument_metavar_rich.py | 103 ++++++++++++++++++++++++++++ typer/rich_utils.py | 15 ++-- 2 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 tests/test_argument_metavar_rich.py diff --git a/tests/test_argument_metavar_rich.py b/tests/test_argument_metavar_rich.py new file mode 100644 index 0000000000..ffda79ab42 --- /dev/null +++ b/tests/test_argument_metavar_rich.py @@ -0,0 +1,103 @@ +"""Test that Argument(metavar=...) displays correctly in rich help output. + +Regression test for https://github.com/fastapi/typer/issues/1156 + +When a custom metavar is set on an Argument, the rich help panel should: +- Show the metavar as the argument name (replacing the parameter name) +- Show the type (e.g. TEXT) in the type column +- NOT show the parameter name in the name column with the metavar in the type column +""" + +import typer +from typing_extensions import Annotated +from typer.testing import CliRunner + + +runner = CliRunner() + + +def test_argument_custom_metavar_shows_as_name_in_rich_help(): + """Custom metavar should replace the argument name, not the type.""" + app = typer.Typer() + + @app.command() + def show(user: Annotated[str, typer.Argument(metavar="MY_ARG")]): + """Show user.""" + typer.echo(f"show user: {user}") + + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + output = result.output + + # The usage line should show the metavar + assert "MY_ARG" in output + + # In the Arguments panel, MY_ARG should appear as the name + # and TEXT should appear as the type + # Before the fix: "user MY_ARG" (name=user, type=MY_ARG) + # After the fix: "MY_ARG TEXT" (name=MY_ARG, type=TEXT) + assert "MY_ARG" in output + assert "TEXT" in output + + # The parameter name 'user' should NOT appear in the help output + # (it should be replaced by the metavar) + lines = output.split("\n") + argument_section = False + for line in lines: + if "Arguments" in line: + argument_section = True + elif argument_section and "─" not in line and line.strip(): + # This is an argument line in the panel + # It should NOT contain the raw parameter name 'user' + assert "user" not in line.lower().split(), ( + f"Parameter name 'user' should not appear in argument panel " + f"when metavar is set. Got: {line!r}" + ) + break + + +def test_argument_without_metavar_shows_default_name(): + """Without a custom metavar, argument should show name and type normally.""" + app = typer.Typer() + + @app.command() + def show(user: Annotated[str, typer.Argument()]): + """Show user.""" + typer.echo(f"show user: {user}") + + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + output = result.output + + # Should show 'user' as name and 'TEXT' as type + assert "user" in output + assert "TEXT" in output + + +def test_argument_metavar_with_int_type(): + """Custom metavar with non-string type should show correct type.""" + app = typer.Typer() + + @app.command() + def process(count: Annotated[int, typer.Argument(metavar="NUM")]): + """Process items.""" + pass + + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + output = result.output + + assert "NUM" in output + assert "INTEGER" in output + + # 'count' should not appear in the arguments panel + lines = output.split("\n") + argument_section = False + for line in lines: + if "Arguments" in line: + argument_section = True + elif argument_section and "─" not in line and line.strip(): + assert "count" not in line.lower().split(), ( + f"Parameter name 'count' should not appear when metavar is set. Got: {line!r}" + ) + break diff --git a/typer/rich_utils.py b/typer/rich_utils.py index d85043238c..6f3e3cfe94 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -376,12 +376,15 @@ def _print_options_panel( metavar = Text(style=STYLE_METAVAR, overflow="fold") metavar_str = param.make_metavar(ctx=ctx) # Do it ourselves if this is a positional argument - if ( - isinstance(param, click.Argument) - and param.name - and metavar_str == param.name.upper() - ): - metavar_str = param.type.name.upper() + if isinstance(param, click.Argument) and param.name: + if param.metavar and metavar_str != param.name.upper(): + # Custom metavar set: use it as the display name and show + # the type in the metavar column instead + opt_long_strs = [metavar_str] + opt_short_strs = [] + metavar_str = param.type.name.upper() + elif metavar_str == param.name.upper(): + metavar_str = param.type.name.upper() # Skip booleans and choices (handled above) if metavar_str != "BOOLEAN": From 742edb16a8f2d66a59ca4247837be2497c978712 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:08:55 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_argument_metavar_rich.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_argument_metavar_rich.py b/tests/test_argument_metavar_rich.py index ffda79ab42..5b4d396ac7 100644 --- a/tests/test_argument_metavar_rich.py +++ b/tests/test_argument_metavar_rich.py @@ -8,11 +8,11 @@ - NOT show the parameter name in the name column with the metavar in the type column """ +from typing import Annotated + import typer -from typing_extensions import Annotated from typer.testing import CliRunner - runner = CliRunner()