From 7a7674880e75b09a98ac0401206a2be5135753cc Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 1 Oct 2022 23:04:23 -0400 Subject: [PATCH 1/5] fix(types): `Choice.get_metavar()` uses metavars of choices if `show_choices=False` --- CHANGES.rst | 3 +++ src/click/types.py | 7 ++++++- tests/test_options.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index b5d970117..097a79f75 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 8d9750b63..0d62915a5 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -259,7 +259,12 @@ def to_info_dict(self) -> dict[str, t.Any]: 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: + choices_str = "|".join( + {convert_type(type(choice)).name.upper() for choice in self.choices} + ) + 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": diff --git a/tests/test_options.py b/tests/test_options.py index 7397f3667..49df83bbd 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 From 04c8c39706ea7d3d82a90ce6f08fa8f7ee89fe05 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 2 Oct 2022 21:15:40 -0400 Subject: [PATCH 2/5] fix: Keep metavar order when making choices_str --- src/click/types.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/click/types.py b/src/click/types.py index 0d62915a5..3d9c69d91 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -260,9 +260,10 @@ def to_info_dict(self) -> dict[str, t.Any]: def get_metavar(self, param: Parameter) -> str: if param.param_type_name == "option" and not param.show_choices: - choices_str = "|".join( - {convert_type(type(choice)).name.upper() for choice in self.choices} - ) + 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]) From 96bf71613ce1354593c4f415ddeb01cc7f38fff1 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 4 Oct 2022 23:08:44 -0400 Subject: [PATCH 3/5] fix: Ignore typecheck on param.show_choices --- src/click/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/click/types.py b/src/click/types.py index 3d9c69d91..595443b3f 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -259,7 +259,7 @@ def to_info_dict(self) -> dict[str, t.Any]: return info_dict def get_metavar(self, param: Parameter) -> str: - if param.param_type_name == "option" and not param.show_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 ] From b2642d40ec4af6c33d435919980d0006bb17e5c6 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 12 Oct 2022 20:43:07 -0400 Subject: [PATCH 4/5] fix: Do not wrap single choice metavar in braces --- src/click/types.py | 16 +++++++++++++--- tests/test_options.py | 8 ++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/click/types.py b/src/click/types.py index 595443b3f..5c1e14442 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -259,13 +259,23 @@ def to_info_dict(self) -> dict[str, t.Any]: return info_dict def get_metavar(self, param: Parameter) -> str: + # Use choice ParamTypes if choices are hidden. if param.param_type_name == "option" and not param.show_choices: # type: ignore - choice_metavars = [ + _choices = [ 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]) + _choices = [str(i) for i in self.choices] + + # Dedupe choices + _choices = [*dict.fromkeys(_choices)] + + # Create choices_str + choices_str = "|".join(_choices) + + # Use no braces if single choice + if len(_choices) < 2: + return choices_str # Use curly braces to indicate a required argument. if param.required and param.param_type_name == "argument": diff --git a/tests/test_options.py b/tests/test_options.py index 49df83bbd..a3f13a03a 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -931,10 +931,10 @@ def test_invalid_flag_combinations(runner, kwargs, message): @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", "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"), ], ) From f7d98feecb91701dceaa0ff2574c854b321403f8 Mon Sep 17 00:00:00 2001 From: Matt Bibby Date: Tue, 11 Jul 2023 00:38:08 -0400 Subject: [PATCH 5/5] Revert inconsistent choice metavar formatting --- src/click/types.py | 16 +++------------- tests/test_options.py | 8 ++++---- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/click/types.py b/src/click/types.py index 5c1e14442..595443b3f 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -259,23 +259,13 @@ def to_info_dict(self) -> dict[str, t.Any]: return info_dict def get_metavar(self, param: Parameter) -> str: - # Use choice ParamTypes if choices are hidden. if param.param_type_name == "option" and not param.show_choices: # type: ignore - _choices = [ + choice_metavars = [ convert_type(type(choice)).name.upper() for choice in self.choices ] + choices_str = "|".join([*dict.fromkeys(choice_metavars)]) else: - _choices = [str(i) for i in self.choices] - - # Dedupe choices - _choices = [*dict.fromkeys(_choices)] - - # Create choices_str - choices_str = "|".join(_choices) - - # Use no braces if single choice - if len(_choices) < 2: - return choices_str + 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": diff --git a/tests/test_options.py b/tests/test_options.py index a3f13a03a..49df83bbd 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -931,10 +931,10 @@ def test_invalid_flag_combinations(runner, kwargs, message): @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", "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"), ], )