Skip to content

Conversation

@svlandeg
Copy link
Member

@svlandeg svlandeg commented Nov 17, 2025

Ok this is a bit of a tricky PR, so I'll dump train of thought here in detail. [disclaimer: 0% vibe-coded, comment nor code]

Fixes #1156 .

Current master behaviour

First let's look at test_tutorial/test_arguments/test_help/test_tutorial006.py, corresponding to the section in the docs about metavar, python source here.

Without rich markdown on master, this looks like:

$ python docs_src/arguments/help/tutorial006.py --help
Usage: tutorial006.py [OPTIONS] ✨username✨

Arguments:
  ✨username✨  [default: World]
...

With rich on master:

$ python docs_src/arguments/help/tutorial006.py --help

 Usage: tutorial006.py [OPTIONS] ✨username✨

┌─ Arguments ──────────────────────────────────────────────────────────────┐
│   name      ✨username✨  [default: World]                               │
└──────────────────────────────────────────────────────────────────────────┘
...

What we see here, is that the original "name" still shows up in the very first column of the output formatting, with the metavar name only in the second column, which is currently on master construed as the "metavar" column.

When we look at another non-metavar example, e.g. tutorial005 we see something similarly weird on master:

$ python docs_src/arguments/help/tutorial005.py --help

 Usage: tutorial005.py [OPTIONS] [NAME]

┌─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│   name      [NAME]  Who to greet [default: (Deadpoolio the amazing's name)]                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

...

i.e. the var's proper formatting [NAME] only shows up in the second metavar column.

Let's look at a much more extensive example, taken from @Noxitu in #438 and in adapted form added to this PR as test_rich_help_metavar in test_rich_utils.py. It gives as output:

$ python issue_438.py --help

 Usage: issue_438.py [OPTIONS] ARG1 ARG3 [ARG4] meta7 ARG8 arg9

┌─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ *    arg1      INTEGER  [required]                                                                                          │
│ *    arg3      INTEGER  [required]                                                                                          │
│      arg4      [ARG4]   [default: 42]                                                                                       │
│      arg7      meta7    [default: 42]                                                                                       │
│ *    arg8      INTEGER  [required]                                                                                          │
│ *    arg9      arg9     [required]                                                                                          │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│    --arg2                      INTEGER  [default: 42]                                                                       │
│ *  --arg5                      INTEGER  [required]                                                                          │
│    --arg6                      INTEGER  [default: 42]                                                                       │
│    --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.                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

What we see here is that types and metavar information is freely mixed in that "metavar" column when Rich is used to print the help.

Compare this to the non-Rich output on master:

$ python issue_438.py --help
Usage: issue_438.py [OPTIONS] ARG1 ARG3 [ARG4] meta7 ARG8 arg9

Arguments:
  ARG1    [required]
  ARG3    [required]
  [ARG4]  [default: 42]
  meta7   [default: 42]
  ARG8    [required]
  arg9    [required]

Options:
  --arg2 INTEGER        [default: 42]
  --arg5 INTEGER        [required]
  --arg6 INTEGER        [default: 42]
  --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.

This PR

So, what this PR proposes to do is:

  • Capitalize the argument names in the Rich help format in the same way as the non-Rich formatting does
    • Note that this can be discussed - cf very last section below 👇
  • Consistently use the first column of the Rich help format to show the name or the metavar name if set, as the non-Rich formatting does
  • Consistently use the second column of the Rich help format to display the type

This required some edits to the tests:

  • I've changed the example from the docs to set ✨user✨ instead of ✨username✨ so the tests can more easily double check that the original name of the parameter, being "name", is not present anymore in the output
  • I've updated tests that checked for the presence of the lower-cases argument names, to now check the uppercase variant
  • I've added two tests test_tutorial006_rich.py and test_tutorial006_an_rich.py that double check the same behaviour with or without Rich formatting - both tests fail on master.
  • I've added the more extensive test test_rich_help_metavar in test_rich_utils.py.

Results with this PR:

$ python docs_src/arguments/help/tutorial005.py --help

 Usage: tutorial005.py [OPTIONS] [NAME]

┌─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│   [NAME]      TEXT  Who to greet [default: (Deadpoolio the amazing's name)]                                                 │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
....

This now shows [NAME] properly, and TEXT as type in the second column.

$ python docs_src/arguments/help/tutorial006.py --help

 Usage: tutorial006.py [OPTIONS] ✨user✨

┌─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│   ✨user✨      TEXT  [default: World]                                                                                      │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ --help          Show this message and exit.                                                                                 │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

No more "name" visible in the output.

And finally:

$ python sofie_stuff/issue_438_b.py --help

 Usage: issue_438_b.py [OPTIONS] ARG1 ARG3 [ARG4] meta7 ARG8 arg9

┌─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ *    ARG1        INTEGER  [required]                                                                                        │
│ *    ARG3        INTEGER  [required]                                                                                        │
│      [ARG4]      INTEGER  [default: 42]                                                                                     │
│      meta7       INTEGER  [default: 42]                                                                                     │
│ *    ARG8        INTEGER  [required]                                                                                        │
│ *    arg9        INTEGER  [required]                                                                                        │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│    --arg2                      INTEGER  [default: 42]                                                                       │
│ *  --arg5                      INTEGER  [required]                                                                          │
│    --arg6                      INTEGER  [default: 42]                                                                       │
│    --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.                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Which feels a lot more consistent and nice.

Details of the actual fix

Which brings us to the actual fix in rich_utils.py.

The old L375 had code specifically designed for a "metavar column", but as we see in the examples, this column was filled with different types of data. I've found out that in fact, Argument's store an alternative name in their metavar data, while Option objects may store alternative represenations of their types. This function is not directly used in Typer, but becomes apparent for Enum cases where we want to display something like [simple|conv|lstm] instead of Choice. For this reason, the new code in rich_utils.py puts the metavar_str data in a different column, depending on the object instance check. As before, we don't explicitely display BOOLEAN values.

Breaking behaviour

While I consider this to be a bug fix, it's also breaking behaviour and may impact users. One potential way to minimize the impact is to keep the capitalization of argument names in Rich help formatting the same as before (i.e. lowercased) even though this is inconsistent with non-Rich formatting (where they are upper-cased, which is also what the docs state).


result = runner.invoke(app, ["--help"])
# assert "Usage: main [OPTIONS] ARG1 ARG3 [ARG4] [meta7] ARG8 arg9" in result.stdout
assert "Usage: main [OPTIONS] ARG1 ARG3 [ARG4] meta7 ARG8 arg9" in result.stdout
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented line on L123 and L133 would be the correct assert once #1409 is merged. Suggest to review #1409 first before this one.

@svlandeg svlandeg added the bug Something isn't working label Nov 17, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Nov 17, 2025

📝 Docs preview

Last commit 987eaaf at: https://49bcbb69.typertiangolo.pages.dev

Comment on lines +377 to +378
if isinstance(param, click.Option):
metavar_type = metavar_str
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this part is crucial to ensure the current Enum tests won't fail. This is the part that will replace Choice with something like [simple|conv|lstm]

Comment on lines +390 to +391
elif metavar_name: # pragma: no cover
secondary_opt_short_strs.append(metavar_name)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too sure yet about these secondary_opts, probably requires another test (instead of having pragma cover).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

1 participant