From ce64168ade2271e08463f222178d1229a4dbc27c Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Thu, 6 Feb 2025 22:58:28 +0100 Subject: [PATCH] Make Typer compatible with Click 8.2 Click 8.2 now expects the context to be passed to the make_metavar method. To enable Typer to be used both with new and old versions of Click, this passes the parameter conditionally. For the make_metavar override to stay compatible, pass the parameter through via catchall for positional arguments. --- tests/test_completion/test_completion_show.py | 2 +- tests/test_others.py | 2 +- .../test_optional/test_tutorial001.py | 2 +- .../test_optional/test_tutorial001_an.py | 2 +- .../test_optional/test_tutorial003.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial002_an.py | 2 +- .../test_terminating/test_tutorial003.py | 4 ++-- .../test_using_click/test_tutorial003.py | 2 +- .../test_using_click/test_tutorial004.py | 6 +++--- typer/core.py | 18 ++++++++++++++---- typer/rich_utils.py | 9 ++++++++- 12 files changed, 35 insertions(+), 18 deletions(-) diff --git a/tests/test_completion/test_completion_show.py b/tests/test_completion/test_completion_show.py index af4ed2a90e..10d8b6ff39 100644 --- a/tests/test_completion/test_completion_show.py +++ b/tests/test_completion/test_completion_show.py @@ -146,4 +146,4 @@ def test_completion_show_invalid_shell(): shellingham, "detect_shell", return_value=("xshell", "/usr/bin/xshell") ): result = runner.invoke(app, ["--show-completion"]) - assert "Shell xshell not supported" in result.stdout + assert "Shell xshell not supported" in result.output diff --git a/tests/test_others.py b/tests/test_others.py index 1078e63d1f..0238d816a3 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -247,7 +247,7 @@ def main(arg1, arg2: int, arg3: "int", arg4: bool = False, arg5: "bool" = False) result = runner.invoke(app, ["Hello", "2", "3", "--arg4", "--arg5"]) assert ( "arg1: Hello\narg2: 2\narg3: 3\narg4: True\narg5: True\n" - in result.stdout + in result.output ) diff --git a/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py b/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py index 46be46cbbd..ffd64460b6 100644 --- a/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py +++ b/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py @@ -31,7 +31,7 @@ def test_call_no_arg_no_rich(): typer.core.rich = None result = runner.invoke(app) assert result.exit_code != 0 - assert "Error: Missing argument 'NAME'" in result.stdout + assert "Error: Missing argument 'NAME'" in result.output typer.core.rich = rich diff --git a/tests/test_tutorial/test_arguments/test_optional/test_tutorial001_an.py b/tests/test_tutorial/test_arguments/test_optional/test_tutorial001_an.py index 45d680b6a3..ad4d646168 100644 --- a/tests/test_tutorial/test_arguments/test_optional/test_tutorial001_an.py +++ b/tests/test_tutorial/test_arguments/test_optional/test_tutorial001_an.py @@ -31,7 +31,7 @@ def test_call_no_arg_no_rich(): typer.core.rich = None result = runner.invoke(app) assert result.exit_code != 0 - assert "Error: Missing argument 'NAME'" in result.stdout + assert "Error: Missing argument 'NAME'" in result.output typer.core.rich = rich diff --git a/tests/test_tutorial/test_arguments/test_optional/test_tutorial003.py b/tests/test_tutorial/test_arguments/test_optional/test_tutorial003.py index 0d0ad50551..22aaa0d52b 100644 --- a/tests/test_tutorial/test_arguments/test_optional/test_tutorial003.py +++ b/tests/test_tutorial/test_arguments/test_optional/test_tutorial003.py @@ -31,7 +31,7 @@ def test_call_no_arg_no_rich(): typer.core.rich = None result = runner.invoke(app) assert result.exit_code != 0 - assert "Error: Missing argument 'NAME'" in result.stdout + assert "Error: Missing argument 'NAME'" in result.output typer.core.rich = rich diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py index 4a1c93f1cd..05ef53c23d 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py @@ -30,7 +30,7 @@ def test_defaults(): def test_invalid_args(): result = runner.invoke(app, ["Draco", "Hagrid"]) assert result.exit_code != 0 - assert "Argument 'names' takes 3 values" in result.stdout + assert "Argument 'names' takes 3 values" in result.output def test_valid_args(): diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002_an.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002_an.py index b634c08ef3..add4f8991a 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002_an.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002_an.py @@ -32,7 +32,7 @@ def test_defaults(): def test_invalid_args(): result = runner.invoke(app, ["Draco", "Hagrid"]) assert result.exit_code != 0 - assert "Argument 'names' takes 3 values" in result.stdout + assert "Argument 'names' takes 3 values" in result.output def test_valid_args(): diff --git a/tests/test_tutorial/test_terminating/test_tutorial003.py b/tests/test_tutorial/test_terminating/test_tutorial003.py index 8fc5f68de4..21104db0dd 100644 --- a/tests/test_tutorial/test_terminating/test_tutorial003.py +++ b/tests/test_tutorial/test_terminating/test_tutorial003.py @@ -38,8 +38,8 @@ def test_root_no_rich(): typer.core.rich = None result = runner.invoke(app, ["root"]) assert result.exit_code == 1 - assert "The root user is reserved" in result.stdout - assert "Aborted!" in result.stdout + assert "The root user is reserved" in result.output + assert "Aborted!" in result.output typer.core.rich = rich diff --git a/tests/test_tutorial/test_using_click/test_tutorial003.py b/tests/test_tutorial/test_using_click/test_tutorial003.py index 1dd26a7e04..ccb481552d 100644 --- a/tests/test_tutorial/test_using_click/test_tutorial003.py +++ b/tests/test_tutorial/test_using_click/test_tutorial003.py @@ -10,7 +10,7 @@ def test_cli(): result = runner.invoke(mod.typer_click_object, []) - assert "Missing command" in result.stdout + assert "Missing command" in result.output def test_help(): diff --git a/tests/test_tutorial/test_using_click/test_tutorial004.py b/tests/test_tutorial/test_using_click/test_tutorial004.py index 72800aec60..902c7deca7 100644 --- a/tests/test_tutorial/test_using_click/test_tutorial004.py +++ b/tests/test_tutorial/test_using_click/test_tutorial004.py @@ -10,9 +10,9 @@ def test_cli(): result = runner.invoke(mod.cli, []) - assert "Usage" in result.stdout - assert "dropdb" in result.stdout - assert "sub" in result.stdout + assert "Usage" in result.output + assert "dropdb" in result.output + assert "sub" in result.output def test_typer(): diff --git a/typer/core.py b/typer/core.py index 4dc24ada70..7781c37141 100644 --- a/typer/core.py +++ b/typer/core.py @@ -332,7 +332,12 @@ def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: # to support Arguments if self.hidden: return None - name = self.make_metavar() + # Starting with Click 8.2 we need to pass the context. + # We need to check on Clicks function, as our override uses varargs + if inspect.signature(click.Parameter.make_metavar).parameters.get("ctx"): + name = self.make_metavar(ctx) + else: + name = self.make_metavar() help = self.help or "" extra = [] if self.show_envvar: @@ -378,7 +383,7 @@ def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: help = f"{help} {extra_str}" if help else f"{extra_str}" return name, help - def make_metavar(self) -> str: + def make_metavar(self, *args) -> str: # Modified version of click.core.Argument.make_metavar() # to include Argument name if self.metavar is not None: @@ -386,7 +391,7 @@ def make_metavar(self) -> str: var = (self.name or "").upper() if not self.required: var = f"[{var}]" - type_var = self.type.get_metavar(self) + type_var = self.type.get_metavar(self, *args) if type_var: var += f":{type_var}" if self.nargs != 1: @@ -501,7 +506,12 @@ def _write_opts(opts: Sequence[str]) -> str: any_prefix_is_slash = True if not self.is_flag and not self.count: - rv += f" {self.make_metavar()}" + if inspect.signature(click.Parameter.make_metavar).parameters.get( + "ctx" + ): + rv += f" {self.make_metavar(ctx)}" + else: + rv += f" {self.make_metavar()}" return rv diff --git a/typer/rich_utils.py b/typer/rich_utils.py index 7d603da2d7..60f836e3b6 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -9,6 +9,7 @@ from typing import Any, DefaultDict, Dict, Iterable, List, Optional, Union import click +from click import Parameter from rich import box from rich.align import Align from rich.columns import Columns @@ -366,7 +367,13 @@ def _print_options_panel( # Column for a metavar, if we have one metavar = Text(style=STYLE_METAVAR, overflow="fold") - metavar_str = param.make_metavar() + + # Starting with Click 8.2 we need to pass the context. + # We need to check on Clicks function, as our override uses varargs + if inspect.signature(Parameter.make_metavar).parameters.get("ctx"): + metavar_str = param.make_metavar(ctx) + else: + metavar_str = param.make_metavar() # Do it ourselves if this is a positional argument if (