Repository: pallets/click (python)

Issue:
`show_choices=False` seems to have no effect
I noticed when using Typer that even if you pass `show_choices=False`, the choices still get shown in the help. It seems that this is related to Click.

Example:
```python
import click

@click.command()
@click.option('-s', '--string-to-echo', type=click.Choice(['hello', 'world']), show_choices=False)
def echo(string):
    click.echo(string)

if __name__ == "__main__":
    echo()
```
```
Usage: ... [OPTIONS]

Options:
  -s, --string-to-echo [hello|world]
  --help                          Show this message and exit.
```

I guess it isn't documented what `show_choices` does for `Option` (if anything) in the non-prompt case, but I expected `show_choices=False` to disable showing the choices in the help.

Environment:

- Python version: 3.9.13
- Click version: 8.1.3


Pull request:
Fix metavar for Choice options when show_choices=False
The `show_choices` parameter is intended to suppress choices from being displayed inline when `click.prompt()` is used. It's available to `click.option()` because an option can act like a prompt if the `prompt=True` parameter  is used. However, this parameter does not prevent the choices from being used to create the `Choice` metavar displayed within the `--help` message.

This change addresses this issue by creating the metavar string using the ParamTypes of the choices instead of the values themselves.

Example script:
```python
import click

@click.command()
@click.option(
    '-s', 
    '--string', 
    type=click.Choice(['hello', 'world']), 
    show_choices=False, 
    prompt=True, 
    help="This value echoed back to stdout."
)
def demoecho(string):
    click.echo(string)

if __name__ == "__main__":
    demoecho()
```
Output when `show_choices=True`:
```bash
$ demoecho --help         
Usage: demoecho [OPTIONS]

Options:
  -s, --string [hello|world]   This value echoed back to stdout.
  --help                       Show this message and exit.
```

Output when `show_choices=False`:
```bash
$ demoecho --help         
Usage: demoecho [OPTIONS]

Options:
  -s, --string [TEXT]    This value echoed back to stdout.
  --help                 Show this message and exit.
```

- fixes #2356

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [x] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index b5d9701..097a79f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -31,6 +31,9 @@ Unreleased
 -   When generating a command's name from a decorated function's name, the
     suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed.
     :issue:`2322`
+-   Show the ``types.ParamType.name`` for ``types.Choice`` options within
+    ``--help`` message if ``show_choices=False`` is specified.
+    :issue:`2356`
 
 
 Version 8.1.8
diff --git a/src/click/types.py b/src/click/types.py
index 8d9750b..595443b 100644
--- a/src/click/types.py
+++ b/src/click/types.py
@@ -259,7 +259,13 @@ class Choice(ParamType):
         return info_dict
 
     def get_metavar(self, param: Parameter) -> str:
-        choices_str = "|".join(self.choices)
+        if param.param_type_name == "option" and not param.show_choices:  # type: ignore
+            choice_metavars = [
+                convert_type(type(choice)).name.upper() for choice in self.choices
+            ]
+            choices_str = "|".join([*dict.fromkeys(choice_metavars)])
+        else:
+            choices_str = "|".join([str(i) for i in self.choices])
 
         # Use curly braces to indicate a required argument.
         if param.required and param.param_type_name == "argument":


Test patch:
diff --git a/tests/test_options.py b/tests/test_options.py
index 7397f36..49df83b 100644
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -926,3 +926,39 @@ def test_invalid_flag_combinations(runner, kwargs, message):
         click.Option(["-a"], **kwargs)
 
     assert message in str(e.value)
+
+
+@pytest.mark.parametrize(
+    ("choices", "metavars"),
+    [
+        pytest.param(["foo", "bar"], "[TEXT]", id="text choices"),
+        pytest.param([1, 2], "[INTEGER]", id="int choices"),
+        pytest.param([1.0, 2.0], "[FLOAT]", id="float choices"),
+        pytest.param([True, False], "[BOOLEAN]", id="bool choices"),
+        pytest.param(["foo", 1], "[TEXT|INTEGER]", id="text/int choices"),
+    ],
+)
+def test_usage_show_choices(runner, choices, metavars):
+    """When show_choices=False is set, the --help output
+    should print choice metavars instead of values.
+    """
+
+    @click.command()
+    @click.option("-g", type=click.Choice(choices))
+    def cli_with_choices(g):
+        pass
+
+    @click.command()
+    @click.option(
+        "-g",
+        type=click.Choice(choices),
+        show_choices=False,
+    )
+    def cli_without_choices(g):
+        pass
+
+    result = runner.invoke(cli_with_choices, ["--help"])
+    assert f"[{'|'.join([str(i) for i in choices])}]" in result.output
+
+    result = runner.invoke(cli_without_choices, ["--help"])
+    assert metavars in result.output