From 860c3928b590fecccdce32f3615f04c3604707ec Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Tue, 17 Sep 2024 17:23:16 -0700 Subject: [PATCH 01/11] allow Parameter to be acception by autocompletion functions, also pass through CompletionItem return values, fix args to match what it was in click 7 --- docs_src/options_autocompletion/tutorial009.py | 6 +++--- .../options_autocompletion/tutorial009_an.py | 6 +++--- tests/test_others.py | 4 ++-- .../test_tutorial008.py | 2 +- .../test_tutorial008_an.py | 2 +- .../test_tutorial009.py | 1 - .../test_tutorial009_an.py | 1 - typer/_completion_classes.py | 18 ++++++++++++++++++ typer/core.py | 9 +++++++-- typer/main.py | 16 ++++++++++++++-- 10 files changed, 49 insertions(+), 16 deletions(-) diff --git a/docs_src/options_autocompletion/tutorial009.py b/docs_src/options_autocompletion/tutorial009.py index 7e82c7ff07..8b109e9dfb 100644 --- a/docs_src/options_autocompletion/tutorial009.py +++ b/docs_src/options_autocompletion/tutorial009.py @@ -1,6 +1,7 @@ from typing import List import typer +from click.core import Parameter from rich.console import Console valid_completion_items = [ @@ -12,9 +13,8 @@ err_console = Console(stderr=True) -def complete_name(ctx: typer.Context, args: List[str], incomplete: str): - err_console.print(f"{args}") - names = ctx.params.get("name") or [] +def complete_name(ctx: typer.Context, param: Parameter, incomplete: str): + names = ctx.params.get(param.name) or [] for name, help_text in valid_completion_items: if name.startswith(incomplete) and name not in names: yield (name, help_text) diff --git a/docs_src/options_autocompletion/tutorial009_an.py b/docs_src/options_autocompletion/tutorial009_an.py index c5b825eaf0..f7182403e3 100644 --- a/docs_src/options_autocompletion/tutorial009_an.py +++ b/docs_src/options_autocompletion/tutorial009_an.py @@ -1,6 +1,7 @@ from typing import List import typer +from click.core import Parameter from rich.console import Console from typing_extensions import Annotated @@ -13,9 +14,8 @@ err_console = Console(stderr=True) -def complete_name(ctx: typer.Context, args: List[str], incomplete: str): - err_console.print(f"{args}") - names = ctx.params.get("name") or [] +def complete_name(ctx: typer.Context, param: Parameter, incomplete: str): + names = ctx.params.get(param.name) or [] for name, help_text in valid_completion_items: if name.startswith(incomplete) and name not in names: yield (name, help_text) diff --git a/tests/test_others.py b/tests/test_others.py index 1078e63d1f..0457c262d8 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -176,7 +176,7 @@ def test_completion_untyped_parameters(): }, ) assert "info name is: completion_no_types.py" in result.stderr - assert "args is: []" in result.stderr + assert "args is: ['--name', 'Sebastian', '--name']" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout @@ -202,7 +202,7 @@ def test_completion_untyped_parameters_different_order_correct_names(): }, ) assert "info name is: completion_no_types_order.py" in result.stderr - assert "args is: []" in result.stderr + assert "args is: ['--name', 'Sebastian', '--name']" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py index 0874f23c5d..4ead1a01da 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py @@ -23,7 +23,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' in result.stdout - assert "[]" in result.stderr + assert "--name" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py index cb2481a67c..75ebf42965 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py @@ -23,7 +23,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' in result.stdout - assert "[]" in result.stderr + assert "--name" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py index 3c7eb0cc64..f168780a5c 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py @@ -23,7 +23,6 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' not in result.stdout - assert "[]" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py index 56182ac3b9..b710c934e8 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py @@ -23,7 +23,6 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' not in result.stdout - assert "[]" in result.stderr def test_1(): diff --git a/typer/_completion_classes.py b/typer/_completion_classes.py index f0bb89c3cc..26e321c032 100644 --- a/typer/_completion_classes.py +++ b/typer/_completion_classes.py @@ -42,6 +42,9 @@ def get_completion_args(self) -> Tuple[List[str], str]: except IndexError: incomplete = "" + obj = self.ctx_args.setdefault("obj", {}) + if isinstance(obj, dict): + obj.setdefault("args", args) return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: @@ -77,6 +80,11 @@ def get_completion_args(self) -> Tuple[List[str], str]: args = args[:-1] else: incomplete = "" + + obj = self.ctx_args.setdefault("obj", {}) + if isinstance(obj, dict): + obj.setdefault("args", args) + return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: @@ -128,6 +136,11 @@ def get_completion_args(self) -> Tuple[List[str], str]: args = args[:-1] else: incomplete = "" + + obj = self.ctx_args.setdefault("obj", {}) + if isinstance(obj, dict): + obj.setdefault("args", args) + return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: @@ -177,6 +190,11 @@ def get_completion_args(self) -> Tuple[List[str], str]: incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") cwords = click.parser.split_arg_string(completion_args) args = cwords[1:-1] if incomplete else cwords[1:] + + obj = self.ctx_args.setdefault("obj", {}) + if isinstance(obj, dict): + obj.setdefault("args", args) + return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: diff --git a/typer/core.py b/typer/core.py index 4dc24ada70..2caceed1cd 100644 --- a/typer/core.py +++ b/typer/core.py @@ -59,7 +59,10 @@ def _typer_param_setup_autocompletion_compat( self: click.Parameter, *, autocompletion: Optional[ - Callable[[click.Context, List[str], str], List[Union[Tuple[str, str], str]]] + Callable[ + [click.Context, click.core.Parameter, str], + List[Union[Tuple[str, str], str, "click.shell_completion.CompletionItem"]], + ] ] = None, ) -> None: if self._custom_shell_complete is not None: @@ -81,9 +84,11 @@ def compat_autocompletion( out = [] - for c in autocompletion(ctx, [], incomplete): + for c in autocompletion(ctx, param, incomplete): if isinstance(c, tuple): use_completion = CompletionItem(c[0], help=c[1]) + elif isinstance(c, CompletionItem): + use_completion = c else: assert isinstance(c, str) use_completion = CompletionItem(c) diff --git a/typer/main.py b/typer/main.py index 36737e49ef..b18f0f14d3 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1023,6 +1023,7 @@ def get_param_completion( parameters = get_params_from_function(callback) ctx_name = None args_name = None + param_name = None incomplete_name = None unassigned_params = list(parameters.values()) for param_sig in unassigned_params[:]: @@ -1033,6 +1034,9 @@ def get_param_completion( elif lenient_issubclass(origin, List): args_name = param_sig.name unassigned_params.remove(param_sig) + elif lenient_issubclass(param_sig.annotation, click.core.Parameter): + param_name = param_sig.name + unassigned_params.remove(param_sig) elif lenient_issubclass(param_sig.annotation, str): incomplete_name = param_sig.name unassigned_params.remove(param_sig) @@ -1044,6 +1048,9 @@ def get_param_completion( elif args_name is None and param_sig.name == "args": args_name = param_sig.name unassigned_params.remove(param_sig) + elif param_name is None and param_sig.name == "param": + param_name = param_sig.name + unassigned_params.remove(param_sig) elif incomplete_name is None and param_sig.name == "incomplete": incomplete_name = param_sig.name unassigned_params.remove(param_sig) @@ -1054,12 +1061,17 @@ def get_param_completion( f"Invalid autocompletion callback parameters: {show_params}" ) - def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> Any: + def wrapper( + ctx: click.Context, param: click.core.Parameter, incomplete: Optional[str] + ) -> Any: use_params: Dict[str, Any] = {} if ctx_name: use_params[ctx_name] = ctx if args_name: - use_params[args_name] = args + obj = ctx.obj or {} + use_params[args_name] = obj.get("args", []) if isinstance(obj, dict) else [] + if param_name: + use_params[param_name] = param if incomplete_name: use_params[incomplete_name] = incomplete return callback(**use_params) From 40fa0d55524391fed90758cb7355f71b68ac1786 Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Fri, 8 Nov 2024 15:00:12 -0800 Subject: [PATCH 02/11] add reusable completer function tutorial, add tutorial test for coverage --- docs/tutorial/options-autocompletion.md | 53 ++++++++++- .../options_autocompletion/tutorial010.py | 38 ++++++++ .../options_autocompletion/tutorial010_an.py | 41 +++++++++ pyproject.toml | 1 + .../test_tutorial010.py | 90 +++++++++++++++++++ .../test_tutorial010_an.py | 90 +++++++++++++++++++ typer/main.py | 2 +- 7 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 docs_src/options_autocompletion/tutorial010.py create mode 100644 docs_src/options_autocompletion/tutorial010_an.py create mode 100644 tests/test_tutorial/test_options_autocompletion/test_tutorial010.py create mode 100644 tests/test_tutorial/test_options_autocompletion/test_tutorial010_an.py diff --git a/docs/tutorial/options-autocompletion.md b/docs/tutorial/options-autocompletion.md index b6dd0698b6..5d9c482820 100644 --- a/docs/tutorial/options-autocompletion.md +++ b/docs/tutorial/options-autocompletion.md @@ -214,7 +214,7 @@ Hello Sebastian And the same way as before, we want to provide **completion** for those names. But we don't want to provide the **same names** for completion if they were already given in previous parameters. -For that, we will access and use the "Context". When you create a **Typer** application it uses Click underneath. And every Click application has a special object called a "Context" that is normally hidden. +For that, we will access and use the "Context". When you create a **Typer** application it uses Click underneath. And every Click application has a special object called a "Context" that is normally hidden. But you can access the context by declaring a function parameter of type `typer.Context`. @@ -264,6 +264,56 @@ It's quite possible that if there's only one option left, your shell will comple /// +## Reusing generic completer functions + +You may want to reuse completer functions across CLI applications or within the same CLI application. If you need to filter out previously supplied parameters the completer function will first have to determine which parameter it is being asked to complete. + +We can declare a parameter of type click.Parameter along with the `click.Context` in our completer function to determine this. For example, lets revisit our above context example where we filter out duplicates but add a second greeter argument that reuses the same completer function: + +//// tab | Python 3.7+ + +```Python hl_lines="15-16" +{!> ../docs_src/options_autocompletion/tutorial010_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="14-15" +{!> ../docs_src/options_autocompletion/tutorial010.py!} +``` + +//// + +/// tip + +You may also return click.shell_completion.CompletionItem objects from completer functions instead of 2-tuples. + +/// + + +Check it: + +
+ +```console +$ typer ./main.py run --name Sebastian --greeter Camila --greeter [TAB][TAB] + +// Our function returns Sebastian too because it is completing greeter +Carlos -- The writer of scripts. +Sebastian -- The type hints guy. +``` + +
+ + ## Getting the raw *CLI parameters* You can also get the raw *CLI parameters*, just a `list` of `str` with everything passed in the command line before the incomplete value. @@ -381,6 +431,7 @@ You can declare function parameters of these types: * `str`: for the incomplete value. * `typer.Context`: for the current context. +* `click.Parameter`: for the CLI parameter being completed. * `List[str]`: for the raw *CLI parameters*. It doesn't matter how you name them, in which order, or which ones of the 3 options you declare. It will all "**just work**" ✨ diff --git a/docs_src/options_autocompletion/tutorial010.py b/docs_src/options_autocompletion/tutorial010.py new file mode 100644 index 0000000000..450679a549 --- /dev/null +++ b/docs_src/options_autocompletion/tutorial010.py @@ -0,0 +1,38 @@ +from typing import List + +import click +import typer +from click.shell_completion import CompletionItem + +valid_completion_items = [ + ("Camila", "The reader of books."), + ("Carlos", "The writer of scripts."), + ("Sebastian", "The type hints guy."), +] + + +def complete_name(ctx: typer.Context, param: click.Parameter, incomplete: str): + names = (ctx.params.get(param.name) if param.name else []) or [] + for name, help_text in valid_completion_items: + if name.startswith(incomplete) and name not in names: + yield CompletionItem(name, help=help_text) + + +app = typer.Typer() + + +@app.command() +def main( + name: List[str] = typer.Option( + ["World"], help="The name to say hi to.", autocompletion=complete_name + ), + greeter: List[str] = typer.Option( + None, help="Who are the greeters?.", autocompletion=complete_name + ), +): + for n in name: + print(f"Hello {n}, from {' and '.join(greeter or [])}") + + +if __name__ == "__main__": + app() diff --git a/docs_src/options_autocompletion/tutorial010_an.py b/docs_src/options_autocompletion/tutorial010_an.py new file mode 100644 index 0000000000..74b69abf07 --- /dev/null +++ b/docs_src/options_autocompletion/tutorial010_an.py @@ -0,0 +1,41 @@ +from typing import List + +import click +import typer +from click.shell_completion import CompletionItem +from typing_extensions import Annotated + +valid_completion_items = [ + ("Camila", "The reader of books."), + ("Carlos", "The writer of scripts."), + ("Sebastian", "The type hints guy."), +] + + +def complete_name(ctx: typer.Context, param: click.Parameter, incomplete: str): + names = (ctx.params.get(param.name) if param.name else []) or [] + for name, help_text in valid_completion_items: + if name.startswith(incomplete) and name not in names: + yield CompletionItem(name, help=help_text) + + +app = typer.Typer() + + +@app.command() +def main( + name: Annotated[ + List[str], + typer.Option(help="The name to say hi to.", autocompletion=complete_name), + ] = ["World"], + greeter: Annotated[ + List[str], + typer.Option(help="Who are the greeters?.", autocompletion=complete_name), + ] = [], +): + for n in name: + print(f"Hello {n}, from {' and '.join(greeter)}") + + +if __name__ == "__main__": + app() diff --git a/pyproject.toml b/pyproject.toml index e17f3628c9..08a5db27a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -181,6 +181,7 @@ ignore = [ "docs_src/options_autocompletion/tutorial007_an.py" = ["B006"] "docs_src/options_autocompletion/tutorial008_an.py" = ["B006"] "docs_src/options_autocompletion/tutorial009_an.py" = ["B006"] +"docs_src/options_autocompletion/tutorial010_an.py" = ["B006"] "docs_src/parameter_types/enum/tutorial003_an.py" = ["B006"] # Loop control variable `value` not used within loop body "docs_src/progressbar/tutorial001.py" = ["B007"] diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial010.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial010.py new file mode 100644 index 0000000000..658dac4de8 --- /dev/null +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial010.py @@ -0,0 +1,90 @@ +import os +import subprocess +import sys + +from typer.testing import CliRunner + +from docs_src.options_autocompletion import tutorial010 as mod + +runner = CliRunner() + + +def test_completion(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_TUTORIAL010.PY_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": "tutorial010.py --name Sebastian --name ", + }, + ) + assert '"Camila":"The reader of books."' in result.stdout + assert '"Carlos":"The writer of scripts."' in result.stdout + assert '"Sebastian":"The type hints guy."' not in result.stdout + + +def test_completion_greeter1(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_TUTORIAL010.PY_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": "tutorial010.py --name Sebastian --greeter Ca", + }, + ) + assert '"Camila":"The reader of books."' in result.stdout + assert '"Carlos":"The writer of scripts."' in result.stdout + assert '"Sebastian":"The type hints guy."' not in result.stdout + + +def test_completion_greeter2(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_TUTORIAL010.PY_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": "tutorial010.py --name Sebastian --greeter Carlos --greeter ", + }, + ) + assert '"Camila":"The reader of books."' in result.stdout + assert '"Carlos":"The writer of scripts."' not in result.stdout + assert '"Sebastian":"The type hints guy."' in result.stdout + + +def test_1(): + result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + assert result.exit_code == 0 + assert "Hello Camila" in result.output + assert "Hello Sebastian" in result.output + + +def test_2(): + result = runner.invoke( + mod.app, ["--name", "Camila", "--name", "Sebastian", "--greeter", "Carlos"] + ) + assert result.exit_code == 0 + assert "Hello Camila, from Carlos" in result.output + assert "Hello Sebastian, from Carlos" in result.output + + +def test_3(): + result = runner.invoke( + mod.app, ["--name", "Camila", "--greeter", "Carlos", "--greeter", "Sebastian"] + ) + assert result.exit_code == 0 + assert "Hello Camila, from Carlos and Sebastian" in result.output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial010_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial010_an.py new file mode 100644 index 0000000000..64e0cc81ee --- /dev/null +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial010_an.py @@ -0,0 +1,90 @@ +import os +import subprocess +import sys + +from typer.testing import CliRunner + +from docs_src.options_autocompletion import tutorial010_an as mod + +runner = CliRunner() + + +def test_completion(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_TUTORIAL010_AN.PY_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": "tutorial010_an.py --name Sebastian --name ", + }, + ) + assert '"Camila":"The reader of books."' in result.stdout + assert '"Carlos":"The writer of scripts."' in result.stdout + assert '"Sebastian":"The type hints guy."' not in result.stdout + + +def test_completion_greeter1(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_TUTORIAL010_AN.PY_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": "tutorial010_an.py --name Sebastian --greeter Ca", + }, + ) + assert '"Camila":"The reader of books."' in result.stdout + assert '"Carlos":"The writer of scripts."' in result.stdout + assert '"Sebastian":"The type hints guy."' not in result.stdout + + +def test_completion_greeter2(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_TUTORIAL010_AN.PY_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": "tutorial010_an.py --name Sebastian --greeter Carlos --greeter ", + }, + ) + assert '"Camila":"The reader of books."' in result.stdout + assert '"Carlos":"The writer of scripts."' not in result.stdout + assert '"Sebastian":"The type hints guy."' in result.stdout + + +def test_1(): + result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + assert result.exit_code == 0 + assert "Hello Camila" in result.output + assert "Hello Sebastian" in result.output + + +def test_2(): + result = runner.invoke( + mod.app, ["--name", "Camila", "--name", "Sebastian", "--greeter", "Carlos"] + ) + assert result.exit_code == 0 + assert "Hello Camila, from Carlos" in result.output + assert "Hello Sebastian, from Carlos" in result.output + + +def test_3(): + result = runner.invoke( + mod.app, ["--name", "Camila", "--greeter", "Carlos", "--greeter", "Sebastian"] + ) + assert result.exit_code == 0 + assert "Hello Camila, from Carlos and Sebastian" in result.output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout diff --git a/typer/main.py b/typer/main.py index b18f0f14d3..bedbd626d8 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1034,7 +1034,7 @@ def get_param_completion( elif lenient_issubclass(origin, List): args_name = param_sig.name unassigned_params.remove(param_sig) - elif lenient_issubclass(param_sig.annotation, click.core.Parameter): + elif lenient_issubclass(param_sig.annotation, click.Parameter): param_name = param_sig.name unassigned_params.remove(param_sig) elif lenient_issubclass(param_sig.annotation, str): From c2ffc6b11f818b993daaae98a87dd4242358dc5f Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Fri, 8 Nov 2024 15:17:09 -0800 Subject: [PATCH 03/11] update completion_no_types tests to check for param as well --- tests/assets/completion_no_types.py | 3 ++- tests/assets/completion_no_types_order.py | 3 ++- tests/test_others.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/assets/completion_no_types.py b/tests/assets/completion_no_types.py index 8dc610a1b2..3ee3f3820b 100644 --- a/tests/assets/completion_no_types.py +++ b/tests/assets/completion_no_types.py @@ -3,9 +3,10 @@ app = typer.Typer() -def complete(ctx, args, incomplete): +def complete(ctx, args, param, incomplete): typer.echo(f"info name is: {ctx.info_name}", err=True) typer.echo(f"args is: {args}", err=True) + typer.echo(f"param is: {param.name}", err=True) typer.echo(f"incomplete is: {incomplete}", err=True) return [ ("Camila", "The reader of books."), diff --git a/tests/assets/completion_no_types_order.py b/tests/assets/completion_no_types_order.py index dbbbc77f19..11e8f5a599 100644 --- a/tests/assets/completion_no_types_order.py +++ b/tests/assets/completion_no_types_order.py @@ -3,9 +3,10 @@ app = typer.Typer() -def complete(args, incomplete, ctx): +def complete(args, incomplete, ctx, param): typer.echo(f"info name is: {ctx.info_name}", err=True) typer.echo(f"args is: {args}", err=True) + typer.echo(f"param is: {param.name}", err=True) typer.echo(f"incomplete is: {incomplete}", err=True) return [ ("Camila", "The reader of books."), diff --git a/tests/test_others.py b/tests/test_others.py index 0457c262d8..7d37d6df46 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -177,6 +177,7 @@ def test_completion_untyped_parameters(): ) assert "info name is: completion_no_types.py" in result.stderr assert "args is: ['--name', 'Sebastian', '--name']" in result.stderr + assert "param is: name" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout @@ -203,6 +204,7 @@ def test_completion_untyped_parameters_different_order_correct_names(): ) assert "info name is: completion_no_types_order.py" in result.stderr assert "args is: ['--name', 'Sebastian', '--name']" in result.stderr + assert "param is: name" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout From 46c406b6c854820b1fa0b02ff2d62e5aac340df6 Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Wed, 4 Dec 2024 16:10:29 -0800 Subject: [PATCH 04/11] remove python 3.7 tabs for reusable completer example --- docs/tutorial/options-autocompletion.md | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/docs/tutorial/options-autocompletion.md b/docs/tutorial/options-autocompletion.md index 5d9c482820..737a2c5e31 100644 --- a/docs/tutorial/options-autocompletion.md +++ b/docs/tutorial/options-autocompletion.md @@ -270,27 +270,7 @@ You may want to reuse completer functions across CLI applications or within the We can declare a parameter of type click.Parameter along with the `click.Context` in our completer function to determine this. For example, lets revisit our above context example where we filter out duplicates but add a second greeter argument that reuses the same completer function: -//// tab | Python 3.7+ - -```Python hl_lines="15-16" -{!> ../docs_src/options_autocompletion/tutorial010_an.py!} -``` - -//// - -//// tab | Python 3.7+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="14-15" -{!> ../docs_src/options_autocompletion/tutorial010.py!} -``` - -//// +{* docs_src/options_autocompletion/tutorial010_an.py hl[15:16] *} /// tip From c4ef5abab649621c792f0f8c7834a63b08860957 Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Fri, 24 Jan 2025 12:13:53 -0800 Subject: [PATCH 05/11] revert bug fix for args parameter in completer functions, this will be included in a separate PR. Also update tutorial 9 to show all supported parameters may be passed --- docs/tutorial/options-autocompletion.md | 2 +- docs_src/options_autocompletion/tutorial009.py | 5 ++++- .../options_autocompletion/tutorial009_an.py | 5 ++++- tests/test_others.py | 4 ++-- .../test_tutorial008.py | 2 +- .../test_tutorial008_an.py | 2 +- .../test_tutorial009.py | 1 + .../test_tutorial009_an.py | 1 + typer/_completion_classes.py | 18 ------------------ typer/core.py | 2 +- typer/main.py | 8 +++++--- 11 files changed, 21 insertions(+), 29 deletions(-) diff --git a/docs/tutorial/options-autocompletion.md b/docs/tutorial/options-autocompletion.md index 737a2c5e31..70d93901bc 100644 --- a/docs/tutorial/options-autocompletion.md +++ b/docs/tutorial/options-autocompletion.md @@ -372,7 +372,7 @@ But it's probably useful only in very advanced use cases. ## Getting the Context and the raw *CLI parameters* -Of course, you can declare everything if you need it, the context, the raw *CLI parameters*, and the incomplete `str`: +Of course, you can declare everything if you need it, the context, the raw *CLI parameters*, the Parameter and the incomplete `str`: {* docs_src/options_autocompletion/tutorial009_an.py hl[16] *} diff --git a/docs_src/options_autocompletion/tutorial009.py b/docs_src/options_autocompletion/tutorial009.py index 8b109e9dfb..9a74cf65d3 100644 --- a/docs_src/options_autocompletion/tutorial009.py +++ b/docs_src/options_autocompletion/tutorial009.py @@ -13,7 +13,10 @@ err_console = Console(stderr=True) -def complete_name(ctx: typer.Context, param: Parameter, incomplete: str): +def complete_name( + ctx: typer.Context, args: List[str], param: Parameter, incomplete: str +): + err_console.print(f"{args}") names = ctx.params.get(param.name) or [] for name, help_text in valid_completion_items: if name.startswith(incomplete) and name not in names: diff --git a/docs_src/options_autocompletion/tutorial009_an.py b/docs_src/options_autocompletion/tutorial009_an.py index f7182403e3..a7b4601ff4 100644 --- a/docs_src/options_autocompletion/tutorial009_an.py +++ b/docs_src/options_autocompletion/tutorial009_an.py @@ -14,7 +14,10 @@ err_console = Console(stderr=True) -def complete_name(ctx: typer.Context, param: Parameter, incomplete: str): +def complete_name( + ctx: typer.Context, args: List[str], param: Parameter, incomplete: str +): + err_console.print(f"{args}") names = ctx.params.get(param.name) or [] for name, help_text in valid_completion_items: if name.startswith(incomplete) and name not in names: diff --git a/tests/test_others.py b/tests/test_others.py index 7d37d6df46..50848999d6 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -176,7 +176,7 @@ def test_completion_untyped_parameters(): }, ) assert "info name is: completion_no_types.py" in result.stderr - assert "args is: ['--name', 'Sebastian', '--name']" in result.stderr + assert "args is: []" in result.stderr assert "param is: name" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout @@ -203,7 +203,7 @@ def test_completion_untyped_parameters_different_order_correct_names(): }, ) assert "info name is: completion_no_types_order.py" in result.stderr - assert "args is: ['--name', 'Sebastian', '--name']" in result.stderr + assert "args is: []" in result.stderr assert "param is: name" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py index 4ead1a01da..0874f23c5d 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py @@ -23,7 +23,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' in result.stdout - assert "--name" in result.stderr + assert "[]" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py index 75ebf42965..cb2481a67c 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py @@ -23,7 +23,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' in result.stdout - assert "--name" in result.stderr + assert "[]" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py index f168780a5c..3c7eb0cc64 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py @@ -23,6 +23,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' not in result.stdout + assert "[]" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py index b710c934e8..56182ac3b9 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py @@ -23,6 +23,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' not in result.stdout + assert "[]" in result.stderr def test_1(): diff --git a/typer/_completion_classes.py b/typer/_completion_classes.py index 26e321c032..f0bb89c3cc 100644 --- a/typer/_completion_classes.py +++ b/typer/_completion_classes.py @@ -42,9 +42,6 @@ def get_completion_args(self) -> Tuple[List[str], str]: except IndexError: incomplete = "" - obj = self.ctx_args.setdefault("obj", {}) - if isinstance(obj, dict): - obj.setdefault("args", args) return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: @@ -80,11 +77,6 @@ def get_completion_args(self) -> Tuple[List[str], str]: args = args[:-1] else: incomplete = "" - - obj = self.ctx_args.setdefault("obj", {}) - if isinstance(obj, dict): - obj.setdefault("args", args) - return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: @@ -136,11 +128,6 @@ def get_completion_args(self) -> Tuple[List[str], str]: args = args[:-1] else: incomplete = "" - - obj = self.ctx_args.setdefault("obj", {}) - if isinstance(obj, dict): - obj.setdefault("args", args) - return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: @@ -190,11 +177,6 @@ def get_completion_args(self) -> Tuple[List[str], str]: incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") cwords = click.parser.split_arg_string(completion_args) args = cwords[1:-1] if incomplete else cwords[1:] - - obj = self.ctx_args.setdefault("obj", {}) - if isinstance(obj, dict): - obj.setdefault("args", args) - return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: diff --git a/typer/core.py b/typer/core.py index 2caceed1cd..1ebaa83a58 100644 --- a/typer/core.py +++ b/typer/core.py @@ -84,7 +84,7 @@ def compat_autocompletion( out = [] - for c in autocompletion(ctx, param, incomplete): + for c in autocompletion(ctx, [], param, incomplete): if isinstance(c, tuple): use_completion = CompletionItem(c[0], help=c[1]) elif isinstance(c, CompletionItem): diff --git a/typer/main.py b/typer/main.py index bedbd626d8..a9e098a28d 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1062,14 +1062,16 @@ def get_param_completion( ) def wrapper( - ctx: click.Context, param: click.core.Parameter, incomplete: Optional[str] + ctx: click.Context, + args: List[str], + param: click.core.Parameter, + incomplete: Optional[str], ) -> Any: use_params: Dict[str, Any] = {} if ctx_name: use_params[ctx_name] = ctx if args_name: - obj = ctx.obj or {} - use_params[args_name] = obj.get("args", []) if isinstance(obj, dict) else [] + use_params[args_name] = args if param_name: use_params[param_name] = param if incomplete_name: From 3c4bff443bd6148934d38a0558ca9bf9052220c9 Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Fri, 24 Jan 2025 12:29:00 -0800 Subject: [PATCH 06/11] fix linting error introduced on last commit --- typer/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/core.py b/typer/core.py index 1ebaa83a58..c08755578b 100644 --- a/typer/core.py +++ b/typer/core.py @@ -60,7 +60,7 @@ def _typer_param_setup_autocompletion_compat( *, autocompletion: Optional[ Callable[ - [click.Context, click.core.Parameter, str], + [click.Context, List[str], click.core.Parameter, str], List[Union[Tuple[str, str], str, "click.shell_completion.CompletionItem"]], ] ] = None, From d5fcab0cca5e857f510a6b4508af585a55b5888a Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 24 Oct 2025 16:42:04 +0200 Subject: [PATCH 07/11] rename the 'name' option to 'user' throughout this tutorial --- docs/tutorial/options-autocompletion.md | 70 ++++++++++--------- .../options_autocompletion/tutorial001.py | 4 +- .../options_autocompletion/tutorial001_an.py | 4 +- .../options_autocompletion/tutorial002.py | 8 +-- .../options_autocompletion/tutorial002_an.py | 8 +-- .../options_autocompletion/tutorial003.py | 16 ++--- .../options_autocompletion/tutorial003_an.py | 16 ++--- .../options_autocompletion/tutorial004.py | 14 ++-- .../options_autocompletion/tutorial004_an.py | 14 ++-- .../options_autocompletion/tutorial005.py | 14 ++-- .../options_autocompletion/tutorial005_an.py | 14 ++-- .../options_autocompletion/tutorial006.py | 6 +- .../options_autocompletion/tutorial006_an.py | 6 +- .../options_autocompletion/tutorial007.py | 18 ++--- .../options_autocompletion/tutorial007_an.py | 18 ++--- .../options_autocompletion/tutorial008.py | 16 ++--- .../options_autocompletion/tutorial008_an.py | 16 ++--- .../options_autocompletion/tutorial009.py | 18 ++--- .../options_autocompletion/tutorial009_an.py | 18 ++--- .../options_autocompletion/tutorial010.py | 20 +++--- .../options_autocompletion/tutorial010_an.py | 20 +++--- .../test_tutorial002.py | 4 +- .../test_tutorial002_an.py | 4 +- .../test_tutorial003.py | 6 +- .../test_tutorial003_an.py | 6 +- .../test_tutorial004.py | 4 +- .../test_tutorial004_an.py | 4 +- .../test_tutorial007.py | 4 +- .../test_tutorial007_an.py | 4 +- .../test_tutorial008.py | 4 +- .../test_tutorial008_an.py | 4 +- .../test_tutorial009.py | 4 +- .../test_tutorial009_an.py | 4 +- .../test_tutorial010.py | 12 ++-- .../test_tutorial010_an.py | 12 ++-- 35 files changed, 208 insertions(+), 206 deletions(-) diff --git a/docs/tutorial/options-autocompletion.md b/docs/tutorial/options-autocompletion.md index 26fcdea8a7..84f0a98062 100644 --- a/docs/tutorial/options-autocompletion.md +++ b/docs/tutorial/options-autocompletion.md @@ -33,11 +33,11 @@ utils -- Extra utility commands for Typer apps. // Then try with "run" and -- $ typer ./main.py run --[TAB][TAB] -// You will get completion for --name, depending on your terminal it will look something like this ---name -- The name to say hi to. +// You will get completion for --user, depending on your terminal it will look something like this +--user -- The user to say hi to. // And you can run it as if it was with Python directly -$ typer ./main.py run --name Camila +$ typer ./main.py run --user Camila Hello Camila ``` @@ -52,14 +52,14 @@ We can provide completion for the values creating an `autocompletion` function, {* docs_src/options_autocompletion/tutorial002_an.py hl[5:6,15] *} -We return a `list` of strings from the `complete_name()` function. +We return a `list` of strings from the `complete_user()` function. And then we get those values when using completion:
```console -$ typer ./main.py run --name [TAB][TAB] +$ typer ./main.py run --user [TAB][TAB] // We get the values returned from the function 🎉 Camila Carlos Sebastian @@ -75,7 +75,7 @@ Right now, we always return those values, even if users start typing `Sebast` an But we can fix that so that it always works correctly. -Modify the `complete_name()` function to receive a parameter of type `str`, it will contain the incomplete value. +Modify the `complete_user()` function to receive a parameter of type `str`, it will contain the incomplete value. Then we can check and return only the values that start with the incomplete value from the command line: @@ -86,7 +86,7 @@ Now let's try it:
```console -$ typer ./main.py run --name Ca[TAB][TAB] +$ typer ./main.py run --user Ca[TAB][TAB] // We get the values returned from the function that start with Ca 🎉 Camila Carlos @@ -114,7 +114,7 @@ But some shells (Zsh, Fish, PowerShell) are capable of showing extra help text f We can provide that extra help text so that those shells can show it. -In the `complete_name()` function, instead of providing one `str` per completion element, we provide a `tuple` with 2 items. The first item is the actual completion string, and the second item is the help text. +In the `complete_user()` function, instead of providing one `str` per completion element, we provide a `tuple` with 2 items. The first item is the actual completion string, and the second item is the help text. So, in the end, we return a `list` of `tuples` of `str`: @@ -143,7 +143,7 @@ If you have a shell like Zsh, it would look like:
```console -$ typer ./main.py run --name [TAB][TAB] +$ typer ./main.py run --user [TAB][TAB] // We get the completion items with their help text 🎉 Camila -- The reader of books. @@ -183,7 +183,7 @@ But each of the elements for completion has to be a `str` or a `tuple` (when con Let's say that now we want to modify the program to be able to "say hi" to multiple people at the same time. -So, we will allow multiple `--name` *CLI options*. +So, we will allow multiple `--user` *CLI options*. /// tip @@ -202,7 +202,7 @@ And then we can use it like:
```console -$ typer ./main.py run --name Camila --name Sebastian +$ typer ./main.py run --user Camila --user Sebastian Hello Camila Hello Sebastian @@ -212,7 +212,7 @@ Hello Sebastian ### Getting completion for multiple values -And the same way as before, we want to provide **completion** for those names. But we don't want to provide the **same names** for completion if they were already given in previous parameters. +And the same way as before, we want to provide **completion** for those users. But we don't want to provide the names of the **same users** for completion if they were already given in previous parameters. For that, we will access and use the "Context". When you create a **Typer** application it uses Click underneath. And every Click application has a special object called a "Context" that is normally hidden. @@ -222,11 +222,11 @@ And from that context you can get the current values for each parameter. {* docs_src/options_autocompletion/tutorial007_an.py hl[13:14,16] *} -We are getting the `names` already provided with `--name` in the command line before this completion was triggered. +We are getting the `previous_users` already provided with `--user` in the command line before this completion was triggered. -If there's no `--name` in the command line, it will be `None`, so we use `or []` to make sure we have a `list` (even if empty) to check its contents later. +If there's no `--user` in the command line, it will be `None`, so we use `or []` to make sure we have a `list` (even if empty) to check its contents later. -Then, when we have a completion candidate, we check if each `name` was already provided with `--name` by checking if it's in that list of `names` with `name not in names`. +Then, when we have a completion candidate, we check if each `user` was already provided with `--user` by checking if it's in that list of `previous_users` with `user not in previous_users`. And then we `yield` each item that has not been used yet. @@ -235,22 +235,22 @@ Check it:
```console -$ typer ./main.py run --name [TAB][TAB] +$ typer ./main.py run --user [TAB][TAB] -// The first time we trigger completion, we get all the names +// The first time we trigger completion, we get all the users Camila -- The reader of books. Carlos -- The writer of scripts. Sebastian -- The type hints guy. -// Add a name and trigger completion again -$ typer ./main.py run --name Sebastian --name Ca[TAB][TAB] +// Add a user and trigger completion again +$ typer ./main.py run --user Sebastian --user Ca[TAB][TAB] -// Now we get completion only for the names we haven't used 🎉 +// Now we get completion only for the users we haven't used 🎉 Camila -- The reader of books. Carlos -- The writer of scripts. -// And if we add another of the available names: -$ typer ./main.py run --name Sebastian --name Camila --name [TAB][TAB] +// And if we add another of the available users: +$ typer ./main.py run --user Sebastian --user Camila --user [TAB][TAB] // We get completion for the only available one Carlos -- The writer of scripts. @@ -266,9 +266,11 @@ It's quite possible that if there's only one option left, your shell will comple ## Reusing generic completer functions -You may want to reuse completer functions across CLI applications or within the same CLI application. If you need to filter out previously supplied parameters the completer function will first have to determine which parameter it is being asked to complete. +You may want to reuse completer functions across CLI applications or within the same CLI application. In this case, you need to first determine which parameter is being asked to complete. -We can declare a parameter of type click.Parameter along with the `click.Context` in our completer function to determine this. For example, lets revisit our above context example where we filter out duplicates but add a second greeter argument that reuses the same completer function: +This can be done by declaring a parameter of type click.Parameter, and accessing its `param.name` attribute. + +For example, lets revisit our above example and a second greeter argument that reuses the same completer function, now called `complete_user_or_greeter`: {* docs_src/options_autocompletion/tutorial010_an.py hl[15:16] *} @@ -284,9 +286,9 @@ Check it:
```console -$ typer ./main.py run --name Sebastian --greeter Camila --greeter [TAB][TAB] +$ typer ./main.py run --user Sebastian --greeter Camila --greeter [TAB][TAB] -// Our function returns Sebastian too because it is completing greeter +// Our function returns Sebastian too because it is completing 'greeter' Carlos -- The writer of scripts. Sebastian -- The type hints guy. ``` @@ -298,7 +300,7 @@ Sebastian -- The type hints guy. You can also get the raw *CLI parameters*, just a `list` of `str` with everything passed in the command line before the incomplete value. -For example, something like `["typer", "main.py", "run", "--name"]`. +For example, something like `["typer", "main.py", "run", "--user"]`. /// tip @@ -349,10 +351,10 @@ And then we just print it to "standard error".
```console -$ typer ./main.py run --name [TAB][TAB] +$ typer ./main.py run --user [TAB][TAB] // First we see the raw CLI parameters -['./main.py', 'run', '--name'] +['./main.py', 'run', '--user'] // And then we see the actual completion Camila -- The reader of books. @@ -372,7 +374,7 @@ But it's probably useful only in very advanced use cases. ## Getting the Context and the raw *CLI parameters* -Of course, you can declare everything if you need it, the context, the raw *CLI parameters*, the Parameter and the incomplete `str`: +Of course, you can declare everything if you need it, the context, the raw *CLI parameters*, the `Parameter` and the incomplete `str`: {* docs_src/options_autocompletion/tutorial009_an.py hl[16] *} @@ -381,20 +383,20 @@ Check it:
```console -$ typer ./main.py run --name [TAB][TAB] +$ typer ./main.py run --user [TAB][TAB] // First we see the raw CLI parameters -['./main.py', 'run', '--name'] +['./main.py', 'run', '--user'] // And then we see the actual completion Camila -- The reader of books. Carlos -- The writer of scripts. Sebastian -- The type hints guy. -$ typer ./main.py run --name Sebastian --name Ca[TAB][TAB] +$ typer ./main.py run --user Sebastian --user Ca[TAB][TAB] // Again, we see the raw CLI parameters -['./main.py', 'run', '--name', 'Sebastian', '--name'] +['./main.py', 'run', '--user', 'Sebastian', '--user'] // And then we see the rest of the valid completion items Camila -- The reader of books. diff --git a/docs_src/options_autocompletion/tutorial001.py b/docs_src/options_autocompletion/tutorial001.py index 1cfc18cc20..6f4b53a875 100644 --- a/docs_src/options_autocompletion/tutorial001.py +++ b/docs_src/options_autocompletion/tutorial001.py @@ -4,8 +4,8 @@ @app.command() -def main(name: str = typer.Option("World", help="The name to say hi to.")): - print(f"Hello {name}") +def main(user: str = typer.Option("World", help="The user to say hi to.")): + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial001_an.py b/docs_src/options_autocompletion/tutorial001_an.py index d39641253b..df5ec665ed 100644 --- a/docs_src/options_autocompletion/tutorial001_an.py +++ b/docs_src/options_autocompletion/tutorial001_an.py @@ -5,8 +5,8 @@ @app.command() -def main(name: Annotated[str, typer.Option(help="The name to say hi to.")] = "World"): - print(f"Hello {name}") +def main(user: Annotated[str, typer.Option(help="The user to say hi to.")] = "World"): + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial002.py b/docs_src/options_autocompletion/tutorial002.py index 6c14a97ce4..934710734f 100644 --- a/docs_src/options_autocompletion/tutorial002.py +++ b/docs_src/options_autocompletion/tutorial002.py @@ -1,7 +1,7 @@ import typer -def complete_name(): +def complete_user(): return ["Camila", "Carlos", "Sebastian"] @@ -10,11 +10,11 @@ def complete_name(): @app.command() def main( - name: str = typer.Option( - "World", help="The name to say hi to.", autocompletion=complete_name + user: str = typer.Option( + "World", help="The user to say hi to.", autocompletion=complete_user ), ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial002_an.py b/docs_src/options_autocompletion/tutorial002_an.py index a2df928a0f..e4dc7377bc 100644 --- a/docs_src/options_autocompletion/tutorial002_an.py +++ b/docs_src/options_autocompletion/tutorial002_an.py @@ -2,7 +2,7 @@ from typing_extensions import Annotated -def complete_name(): +def complete_user(): return ["Camila", "Carlos", "Sebastian"] @@ -11,11 +11,11 @@ def complete_name(): @app.command() def main( - name: Annotated[ - str, typer.Option(help="The name to say hi to.", autocompletion=complete_name) + user: Annotated[ + str, typer.Option(help="The user to say hi to.", autocompletion=complete_user) ] = "World", ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial003.py b/docs_src/options_autocompletion/tutorial003.py index 9af41f23b5..784ac04cf3 100644 --- a/docs_src/options_autocompletion/tutorial003.py +++ b/docs_src/options_autocompletion/tutorial003.py @@ -1,13 +1,13 @@ import typer -valid_names = ["Camila", "Carlos", "Sebastian"] +valid_users = ["Camila", "Carlos", "Sebastian"] -def complete_name(incomplete: str): +def complete_user(incomplete: str): completion = [] - for name in valid_names: - if name.startswith(incomplete): - completion.append(name) + for user in valid_users: + if user.startswith(incomplete): + completion.append(user) return completion @@ -16,11 +16,11 @@ def complete_name(incomplete: str): @app.command() def main( - name: str = typer.Option( - "World", help="The name to say hi to.", autocompletion=complete_name + user: str = typer.Option( + "World", help="The user to say hi to.", autocompletion=complete_user ), ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial003_an.py b/docs_src/options_autocompletion/tutorial003_an.py index ed874388af..0dc622e8ff 100644 --- a/docs_src/options_autocompletion/tutorial003_an.py +++ b/docs_src/options_autocompletion/tutorial003_an.py @@ -1,14 +1,14 @@ import typer from typing_extensions import Annotated -valid_names = ["Camila", "Carlos", "Sebastian"] +valid_users = ["Camila", "Carlos", "Sebastian"] -def complete_name(incomplete: str): +def complete_user(incomplete: str): completion = [] - for name in valid_names: - if name.startswith(incomplete): - completion.append(name) + for user in valid_users: + if user.startswith(incomplete): + completion.append(user) return completion @@ -17,11 +17,11 @@ def complete_name(incomplete: str): @app.command() def main( - name: Annotated[ - str, typer.Option(help="The name to say hi to.", autocompletion=complete_name) + user: Annotated[ + str, typer.Option(help="The user to say hi to.", autocompletion=complete_user) ] = "World", ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial004.py b/docs_src/options_autocompletion/tutorial004.py index 3be0cf35db..cf03a0d10e 100644 --- a/docs_src/options_autocompletion/tutorial004.py +++ b/docs_src/options_autocompletion/tutorial004.py @@ -7,11 +7,11 @@ ] -def complete_name(incomplete: str): +def complete_user(incomplete: str): completion = [] - for name, help_text in valid_completion_items: - if name.startswith(incomplete): - completion_item = (name, help_text) + for user, help_text in valid_completion_items: + if user.startswith(incomplete): + completion_item = (user, help_text) completion.append(completion_item) return completion @@ -21,11 +21,11 @@ def complete_name(incomplete: str): @app.command() def main( - name: str = typer.Option( - "World", help="The name to say hi to.", autocompletion=complete_name + user: str = typer.Option( + "World", help="The user to say hi to.", autocompletion=complete_user ), ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial004_an.py b/docs_src/options_autocompletion/tutorial004_an.py index 673f8a4d34..475a0696a2 100644 --- a/docs_src/options_autocompletion/tutorial004_an.py +++ b/docs_src/options_autocompletion/tutorial004_an.py @@ -8,11 +8,11 @@ ] -def complete_name(incomplete: str): +def complete_user(incomplete: str): completion = [] - for name, help_text in valid_completion_items: - if name.startswith(incomplete): - completion_item = (name, help_text) + for user, help_text in valid_completion_items: + if user.startswith(incomplete): + completion_item = (user, help_text) completion.append(completion_item) return completion @@ -22,11 +22,11 @@ def complete_name(incomplete: str): @app.command() def main( - name: Annotated[ - str, typer.Option(help="The name to say hi to.", autocompletion=complete_name) + user: Annotated[ + str, typer.Option(help="The user to say hi to.", autocompletion=complete_user) ] = "World", ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial005.py b/docs_src/options_autocompletion/tutorial005.py index 10ac532ad2..9f993b89fd 100644 --- a/docs_src/options_autocompletion/tutorial005.py +++ b/docs_src/options_autocompletion/tutorial005.py @@ -7,10 +7,10 @@ ] -def complete_name(incomplete: str): - for name, help_text in valid_completion_items: - if name.startswith(incomplete): - yield (name, help_text) +def complete_user(incomplete: str): + for user, help_text in valid_completion_items: + if user.startswith(incomplete): + yield (user, help_text) app = typer.Typer() @@ -18,11 +18,11 @@ def complete_name(incomplete: str): @app.command() def main( - name: str = typer.Option( - "World", help="The name to say hi to.", autocompletion=complete_name + user: str = typer.Option( + "World", help="The user to say hi to.", autocompletion=complete_user ), ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial005_an.py b/docs_src/options_autocompletion/tutorial005_an.py index bd1b62d7da..522eecacbf 100644 --- a/docs_src/options_autocompletion/tutorial005_an.py +++ b/docs_src/options_autocompletion/tutorial005_an.py @@ -8,10 +8,10 @@ ] -def complete_name(incomplete: str): - for name, help_text in valid_completion_items: - if name.startswith(incomplete): - yield (name, help_text) +def complete_user(incomplete: str): + for user, help_text in valid_completion_items: + if user.startswith(incomplete): + yield (user, help_text) app = typer.Typer() @@ -19,11 +19,11 @@ def complete_name(incomplete: str): @app.command() def main( - name: Annotated[ - str, typer.Option(help="The name to say hi to.", autocompletion=complete_name) + user: Annotated[ + str, typer.Option(help="The user to say hi to.", autocompletion=complete_user) ] = "World", ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial006.py b/docs_src/options_autocompletion/tutorial006.py index 4e6dba302a..81744d5f2b 100644 --- a/docs_src/options_autocompletion/tutorial006.py +++ b/docs_src/options_autocompletion/tutorial006.py @@ -6,9 +6,9 @@ @app.command() -def main(name: List[str] = typer.Option(["World"], help="The name to say hi to.")): - for each_name in name: - print(f"Hello {each_name}") +def main(user: List[str] = typer.Option(["World"], help="The user to say hi to.")): + for u in user: + print(f"Hello {u}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial006_an.py b/docs_src/options_autocompletion/tutorial006_an.py index 46319c2ecd..a00b6b4138 100644 --- a/docs_src/options_autocompletion/tutorial006_an.py +++ b/docs_src/options_autocompletion/tutorial006_an.py @@ -8,10 +8,10 @@ @app.command() def main( - name: Annotated[List[str], typer.Option(help="The name to say hi to.")] = ["World"], + user: Annotated[List[str], typer.Option(help="The user to say hi to.")] = ["World"], ): - for each_name in name: - print(f"Hello {each_name}") + for u in user: + print(f"Hello {u}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial007.py b/docs_src/options_autocompletion/tutorial007.py index 7c56ac7549..acf4fb600c 100644 --- a/docs_src/options_autocompletion/tutorial007.py +++ b/docs_src/options_autocompletion/tutorial007.py @@ -9,11 +9,11 @@ ] -def complete_name(ctx: typer.Context, incomplete: str): - names = ctx.params.get("name") or [] - for name, help_text in valid_completion_items: - if name.startswith(incomplete) and name not in names: - yield (name, help_text) +def complete_user(ctx: typer.Context, incomplete: str): + previous_users = ctx.params.get("user") or [] + for user, help_text in valid_completion_items: + if user.startswith(incomplete) and user not in previous_users: + yield (user, help_text) app = typer.Typer() @@ -21,12 +21,12 @@ def complete_name(ctx: typer.Context, incomplete: str): @app.command() def main( - name: List[str] = typer.Option( - ["World"], help="The name to say hi to.", autocompletion=complete_name + user: List[str] = typer.Option( + ["World"], help="The user to say hi to.", autocompletion=complete_user ), ): - for n in name: - print(f"Hello {n}") + for u in user: + print(f"Hello {u}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial007_an.py b/docs_src/options_autocompletion/tutorial007_an.py index de6c8054e4..c994ed7412 100644 --- a/docs_src/options_autocompletion/tutorial007_an.py +++ b/docs_src/options_autocompletion/tutorial007_an.py @@ -10,11 +10,11 @@ ] -def complete_name(ctx: typer.Context, incomplete: str): - names = ctx.params.get("name") or [] - for name, help_text in valid_completion_items: - if name.startswith(incomplete) and name not in names: - yield (name, help_text) +def complete_user(ctx: typer.Context, incomplete: str): + users = ctx.params.get("user") or [] + for user, help_text in valid_completion_items: + if user.startswith(incomplete) and user not in users: + yield (user, help_text) app = typer.Typer() @@ -22,13 +22,13 @@ def complete_name(ctx: typer.Context, incomplete: str): @app.command() def main( - name: Annotated[ + user: Annotated[ List[str], - typer.Option(help="The name to say hi to.", autocompletion=complete_name), + typer.Option(help="The user to say hi to.", autocompletion=complete_user), ] = ["World"], ): - for n in name: - print(f"Hello {n}") + for u in user: + print(f"Hello {u}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial008.py b/docs_src/options_autocompletion/tutorial008.py index 118f4dd346..8399034f95 100644 --- a/docs_src/options_autocompletion/tutorial008.py +++ b/docs_src/options_autocompletion/tutorial008.py @@ -12,11 +12,11 @@ err_console = Console(stderr=True) -def complete_name(args: List[str], incomplete: str): +def complete_user(args: List[str], incomplete: str): err_console.print(f"{args}") - for name, help_text in valid_completion_items: - if name.startswith(incomplete): - yield (name, help_text) + for user, help_text in valid_completion_items: + if user.startswith(incomplete): + yield (user, help_text) app = typer.Typer() @@ -24,12 +24,12 @@ def complete_name(args: List[str], incomplete: str): @app.command() def main( - name: List[str] = typer.Option( - ["World"], help="The name to say hi to.", autocompletion=complete_name + user: List[str] = typer.Option( + ["World"], help="The user to say hi to.", autocompletion=complete_user ), ): - for n in name: - print(f"Hello {n}") + for u in user: + print(f"Hello {u}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial008_an.py b/docs_src/options_autocompletion/tutorial008_an.py index 9dcb0df77e..d7b1847d95 100644 --- a/docs_src/options_autocompletion/tutorial008_an.py +++ b/docs_src/options_autocompletion/tutorial008_an.py @@ -13,11 +13,11 @@ err_console = Console(stderr=True) -def complete_name(args: List[str], incomplete: str): +def complete_user(args: List[str], incomplete: str): err_console.print(f"{args}") - for name, help_text in valid_completion_items: - if name.startswith(incomplete): - yield (name, help_text) + for user, help_text in valid_completion_items: + if user.startswith(incomplete): + yield (user, help_text) app = typer.Typer() @@ -25,13 +25,13 @@ def complete_name(args: List[str], incomplete: str): @app.command() def main( - name: Annotated[ + user: Annotated[ List[str], - typer.Option(help="The name to say hi to.", autocompletion=complete_name), + typer.Option(help="The user to say hi to.", autocompletion=complete_user), ] = ["World"], ): - for n in name: - print(f"Hello {n}") + for u in user: + print(f"Hello {u}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial009.py b/docs_src/options_autocompletion/tutorial009.py index 9a74cf65d3..958beb821f 100644 --- a/docs_src/options_autocompletion/tutorial009.py +++ b/docs_src/options_autocompletion/tutorial009.py @@ -13,14 +13,14 @@ err_console = Console(stderr=True) -def complete_name( +def complete_user( ctx: typer.Context, args: List[str], param: Parameter, incomplete: str ): err_console.print(f"{args}") - names = ctx.params.get(param.name) or [] - for name, help_text in valid_completion_items: - if name.startswith(incomplete) and name not in names: - yield (name, help_text) + previous_users = ctx.params.get(param.name) or [] + for user, help_text in valid_completion_items: + if user.startswith(incomplete) and user not in previous_users: + yield (user, help_text) app = typer.Typer() @@ -28,12 +28,12 @@ def complete_name( @app.command() def main( - name: List[str] = typer.Option( - ["World"], help="The name to say hi to.", autocompletion=complete_name + user: List[str] = typer.Option( + ["World"], help="The user to say hi to.", autocompletion=complete_user ), ): - for n in name: - print(f"Hello {n}") + for u in user: + print(f"Hello {u}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial009_an.py b/docs_src/options_autocompletion/tutorial009_an.py index a7b4601ff4..64640ebf59 100644 --- a/docs_src/options_autocompletion/tutorial009_an.py +++ b/docs_src/options_autocompletion/tutorial009_an.py @@ -14,14 +14,14 @@ err_console = Console(stderr=True) -def complete_name( +def complete_user( ctx: typer.Context, args: List[str], param: Parameter, incomplete: str ): err_console.print(f"{args}") - names = ctx.params.get(param.name) or [] - for name, help_text in valid_completion_items: - if name.startswith(incomplete) and name not in names: - yield (name, help_text) + previous_users = ctx.params.get(param.name) or [] + for user, help_text in valid_completion_items: + if user.startswith(incomplete) and user not in previous_users: + yield (user, help_text) app = typer.Typer() @@ -29,13 +29,13 @@ def complete_name( @app.command() def main( - name: Annotated[ + user: Annotated[ List[str], - typer.Option(help="The name to say hi to.", autocompletion=complete_name), + typer.Option(help="The user to say hi to.", autocompletion=complete_user), ] = ["World"], ): - for n in name: - print(f"Hello {n}") + for u in user: + print(f"Hello {u}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial010.py b/docs_src/options_autocompletion/tutorial010.py index 450679a549..e7091b6d21 100644 --- a/docs_src/options_autocompletion/tutorial010.py +++ b/docs_src/options_autocompletion/tutorial010.py @@ -11,11 +11,11 @@ ] -def complete_name(ctx: typer.Context, param: click.Parameter, incomplete: str): - names = (ctx.params.get(param.name) if param.name else []) or [] - for name, help_text in valid_completion_items: - if name.startswith(incomplete) and name not in names: - yield CompletionItem(name, help=help_text) +def complete_user_or_greeter(ctx: typer.Context, param: click.Parameter, incomplete: str): + previous_items = (ctx.params.get(param.name) if param.name else []) or [] + for item, help_text in valid_completion_items: + if item.startswith(incomplete) and item not in previous_items: + yield CompletionItem(item, help=help_text) app = typer.Typer() @@ -23,15 +23,15 @@ def complete_name(ctx: typer.Context, param: click.Parameter, incomplete: str): @app.command() def main( - name: List[str] = typer.Option( - ["World"], help="The name to say hi to.", autocompletion=complete_name + user: List[str] = typer.Option( + ["World"], help="The user to say hi to.", autocompletion=complete_user_or_greeter ), greeter: List[str] = typer.Option( - None, help="Who are the greeters?.", autocompletion=complete_name + None, help="The greeters.", autocompletion=complete_user_or_greeter ), ): - for n in name: - print(f"Hello {n}, from {' and '.join(greeter or [])}") + for u in user: + print(f"Hello {u}, from {' and '.join(greeter or [])}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial010_an.py b/docs_src/options_autocompletion/tutorial010_an.py index 74b69abf07..e7b58f4688 100644 --- a/docs_src/options_autocompletion/tutorial010_an.py +++ b/docs_src/options_autocompletion/tutorial010_an.py @@ -12,11 +12,11 @@ ] -def complete_name(ctx: typer.Context, param: click.Parameter, incomplete: str): - names = (ctx.params.get(param.name) if param.name else []) or [] - for name, help_text in valid_completion_items: - if name.startswith(incomplete) and name not in names: - yield CompletionItem(name, help=help_text) +def complete_user_or_greeter(ctx: typer.Context, param: click.Parameter, incomplete: str): + previous_items = (ctx.params.get(param.name) if param.name else []) or [] + for item, help_text in valid_completion_items: + if item.startswith(incomplete) and item not in previous_items: + yield CompletionItem(item, help=help_text) app = typer.Typer() @@ -24,17 +24,17 @@ def complete_name(ctx: typer.Context, param: click.Parameter, incomplete: str): @app.command() def main( - name: Annotated[ + user: Annotated[ List[str], - typer.Option(help="The name to say hi to.", autocompletion=complete_name), + typer.Option(help="The user to say hi to.", autocompletion=complete_user_or_greeter), ] = ["World"], greeter: Annotated[ List[str], - typer.Option(help="Who are the greeters?.", autocompletion=complete_name), + typer.Option(help="The greeters.", autocompletion=complete_user_or_greeter), ] = [], ): - for n in name: - print(f"Hello {n}, from {' and '.join(greeter)}") + for u in user: + print(f"Hello {u}, from {' and '.join(greeter)}") if __name__ == "__main__": diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py index 3e15fad9b6..b953906965 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL002.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial002.py --name ", + "_TYPER_COMPLETE_ARGS": "tutorial002.py --user ", }, ) assert "Camila" in result.stdout @@ -26,7 +26,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila"]) + result = runner.invoke(mod.app, ["--user", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial002_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial002_an.py index 7db64bfa44..93109f6b9f 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial002_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial002_an.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL002_AN.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial002_an.py --name ", + "_TYPER_COMPLETE_ARGS": "tutorial002_an.py --user ", }, ) assert "Camila" in result.stdout @@ -26,7 +26,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila"]) + result = runner.invoke(mod.app, ["--user", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py index 60304e9e55..cc7ba5d1df 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py @@ -17,7 +17,7 @@ def test_completion_zsh(): env={ **os.environ, "_TUTORIAL003.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial003.py --name Seb", + "_TYPER_COMPLETE_ARGS": "tutorial003.py --user Seb", }, ) assert "Camila" not in result.stdout @@ -33,7 +33,7 @@ def test_completion_powershell(): env={ **os.environ, "_TUTORIAL003.PY_COMPLETE": "complete_powershell", - "_TYPER_COMPLETE_ARGS": "tutorial003.py --name Seb", + "_TYPER_COMPLETE_ARGS": "tutorial003.py --user Seb", "_TYPER_COMPLETE_WORD_TO_COMPLETE": "Seb", }, ) @@ -43,7 +43,7 @@ def test_completion_powershell(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila"]) + result = runner.invoke(mod.app, ["--user", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial003_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial003_an.py index 7688f108f5..555e00c8d1 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial003_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial003_an.py @@ -17,7 +17,7 @@ def test_completion_zsh(): env={ **os.environ, "_TUTORIAL003_AN.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial003_an.py --name Seb", + "_TYPER_COMPLETE_ARGS": "tutorial003_an.py --user Seb", }, ) assert "Camila" not in result.stdout @@ -33,7 +33,7 @@ def test_completion_powershell(): env={ **os.environ, "_TUTORIAL003_AN.PY_COMPLETE": "complete_powershell", - "_TYPER_COMPLETE_ARGS": "tutorial003.py --name Seb", + "_TYPER_COMPLETE_ARGS": "tutorial003.py --user Seb", "_TYPER_COMPLETE_WORD_TO_COMPLETE": "Seb", }, ) @@ -43,7 +43,7 @@ def test_completion_powershell(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila"]) + result = runner.invoke(mod.app, ["--user", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial004.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial004.py index 17c5f5197a..94a308765f 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial004.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial004.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL004.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial004_aux.py --name ", + "_TYPER_COMPLETE_ARGS": "tutorial004_aux.py --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -26,7 +26,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila"]) + result = runner.invoke(mod.app, ["--user", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial004_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial004_an.py index dcfce0a4a7..2e3b91dbfa 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial004_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial004_an.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL004_AN.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial004_an_aux.py --name ", + "_TYPER_COMPLETE_ARGS": "tutorial004_an_aux.py --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -26,7 +26,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila"]) + result = runner.invoke(mod.app, ["--user", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py index b8ce1b1598..908d904bd9 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL007.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial007.py --name Sebastian --name ", + "_TYPER_COMPLETE_ARGS": "tutorial007.py --user Sebastian --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -26,7 +26,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + result = runner.invoke(mod.app, ["--user", "Camila", "--user", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial007_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial007_an.py index c35f23eb61..bcfd0e245e 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial007_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial007_an.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL007_AN.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial007_an.py --name Sebastian --name ", + "_TYPER_COMPLETE_ARGS": "tutorial007_an.py --user Sebastian --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -26,7 +26,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + result = runner.invoke(mod.app, ["--user", "Camila", "--user", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py index 0874f23c5d..bf609d76f4 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL008.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial008.py --name ", + "_TYPER_COMPLETE_ARGS": "tutorial008.py --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -27,7 +27,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + result = runner.invoke(mod.app, ["--user", "Camila", "--user", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py index cb2481a67c..ea26462e8a 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL008_AN.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial008_an.py --name ", + "_TYPER_COMPLETE_ARGS": "tutorial008_an.py --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -27,7 +27,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + result = runner.invoke(mod.app, ["--user", "Camila", "--user", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py index 3c7eb0cc64..45c9cd9a89 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL009.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial009.py --name Sebastian --name ", + "_TYPER_COMPLETE_ARGS": "tutorial009.py --user Sebastian --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -27,7 +27,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + result = runner.invoke(mod.app, ["--user", "Camila", "--user", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py index 56182ac3b9..ef76f7953b 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL009_AN.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial009_an.py --name Sebastian --name ", + "_TYPER_COMPLETE_ARGS": "tutorial009_an.py --user Sebastian --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -27,7 +27,7 @@ def test_completion(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + result = runner.invoke(mod.app, ["--user", "Camila", "--user", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial010.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial010.py index 658dac4de8..8496dd5a07 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial010.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial010.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL010.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial010.py --name Sebastian --name ", + "_TYPER_COMPLETE_ARGS": "tutorial010.py --user Sebastian --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -33,7 +33,7 @@ def test_completion_greeter1(): env={ **os.environ, "_TUTORIAL010.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial010.py --name Sebastian --greeter Ca", + "_TYPER_COMPLETE_ARGS": "tutorial010.py --user Sebastian --greeter Ca", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -49,7 +49,7 @@ def test_completion_greeter2(): env={ **os.environ, "_TUTORIAL010.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial010.py --name Sebastian --greeter Carlos --greeter ", + "_TYPER_COMPLETE_ARGS": "tutorial010.py --user Sebastian --greeter Carlos --greeter ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -58,7 +58,7 @@ def test_completion_greeter2(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + result = runner.invoke(mod.app, ["--user", "Camila", "--user", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output @@ -66,7 +66,7 @@ def test_1(): def test_2(): result = runner.invoke( - mod.app, ["--name", "Camila", "--name", "Sebastian", "--greeter", "Carlos"] + mod.app, ["--user", "Camila", "--user", "Sebastian", "--greeter", "Carlos"] ) assert result.exit_code == 0 assert "Hello Camila, from Carlos" in result.output @@ -75,7 +75,7 @@ def test_2(): def test_3(): result = runner.invoke( - mod.app, ["--name", "Camila", "--greeter", "Carlos", "--greeter", "Sebastian"] + mod.app, ["--user", "Camila", "--greeter", "Carlos", "--greeter", "Sebastian"] ) assert result.exit_code == 0 assert "Hello Camila, from Carlos and Sebastian" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial010_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial010_an.py index 64e0cc81ee..de65f478b2 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial010_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial010_an.py @@ -17,7 +17,7 @@ def test_completion(): env={ **os.environ, "_TUTORIAL010_AN.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial010_an.py --name Sebastian --name ", + "_TYPER_COMPLETE_ARGS": "tutorial010_an.py --user Sebastian --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -33,7 +33,7 @@ def test_completion_greeter1(): env={ **os.environ, "_TUTORIAL010_AN.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial010_an.py --name Sebastian --greeter Ca", + "_TYPER_COMPLETE_ARGS": "tutorial010_an.py --user Sebastian --greeter Ca", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -49,7 +49,7 @@ def test_completion_greeter2(): env={ **os.environ, "_TUTORIAL010_AN.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "tutorial010_an.py --name Sebastian --greeter Carlos --greeter ", + "_TYPER_COMPLETE_ARGS": "tutorial010_an.py --user Sebastian --greeter Carlos --greeter ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -58,7 +58,7 @@ def test_completion_greeter2(): def test_1(): - result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + result = runner.invoke(mod.app, ["--user", "Camila", "--user", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output @@ -66,7 +66,7 @@ def test_1(): def test_2(): result = runner.invoke( - mod.app, ["--name", "Camila", "--name", "Sebastian", "--greeter", "Carlos"] + mod.app, ["--user", "Camila", "--user", "Sebastian", "--greeter", "Carlos"] ) assert result.exit_code == 0 assert "Hello Camila, from Carlos" in result.output @@ -75,7 +75,7 @@ def test_2(): def test_3(): result = runner.invoke( - mod.app, ["--name", "Camila", "--greeter", "Carlos", "--greeter", "Sebastian"] + mod.app, ["--user", "Camila", "--greeter", "Carlos", "--greeter", "Sebastian"] ) assert result.exit_code == 0 assert "Hello Camila, from Carlos and Sebastian" in result.output From 7a80fc3cafc3d20d108bca53c0f592552485c659 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Fri, 24 Oct 2025 16:44:17 +0200 Subject: [PATCH 08/11] fix count --- docs/tutorial/options-autocompletion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/options-autocompletion.md b/docs/tutorial/options-autocompletion.md index 84f0a98062..f1e300584b 100644 --- a/docs/tutorial/options-autocompletion.md +++ b/docs/tutorial/options-autocompletion.md @@ -416,7 +416,7 @@ You can declare function parameters of these types: * `click.Parameter`: for the CLI parameter being completed. * `List[str]`: for the raw *CLI parameters*. -It doesn't matter how you name them, in which order, or which ones of the 3 options you declare. It will all "**just work**" ✨ +It doesn't matter how you name them, in which order, or which ones of the 4 options you declare. It will all "**just work**" ✨ ## Comparison to Click functionality From 921a443268ec6f1d254c2e8a7457352a7421b34d Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 24 Oct 2025 16:46:42 +0200 Subject: [PATCH 09/11] reformat --- docs_src/options_autocompletion/tutorial010.py | 8 ++++++-- docs_src/options_autocompletion/tutorial010_an.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs_src/options_autocompletion/tutorial010.py b/docs_src/options_autocompletion/tutorial010.py index e7091b6d21..dce763cead 100644 --- a/docs_src/options_autocompletion/tutorial010.py +++ b/docs_src/options_autocompletion/tutorial010.py @@ -11,7 +11,9 @@ ] -def complete_user_or_greeter(ctx: typer.Context, param: click.Parameter, incomplete: str): +def complete_user_or_greeter( + ctx: typer.Context, param: click.Parameter, incomplete: str +): previous_items = (ctx.params.get(param.name) if param.name else []) or [] for item, help_text in valid_completion_items: if item.startswith(incomplete) and item not in previous_items: @@ -24,7 +26,9 @@ def complete_user_or_greeter(ctx: typer.Context, param: click.Parameter, incompl @app.command() def main( user: List[str] = typer.Option( - ["World"], help="The user to say hi to.", autocompletion=complete_user_or_greeter + ["World"], + help="The user to say hi to.", + autocompletion=complete_user_or_greeter, ), greeter: List[str] = typer.Option( None, help="The greeters.", autocompletion=complete_user_or_greeter diff --git a/docs_src/options_autocompletion/tutorial010_an.py b/docs_src/options_autocompletion/tutorial010_an.py index e7b58f4688..8ced36e546 100644 --- a/docs_src/options_autocompletion/tutorial010_an.py +++ b/docs_src/options_autocompletion/tutorial010_an.py @@ -12,7 +12,9 @@ ] -def complete_user_or_greeter(ctx: typer.Context, param: click.Parameter, incomplete: str): +def complete_user_or_greeter( + ctx: typer.Context, param: click.Parameter, incomplete: str +): previous_items = (ctx.params.get(param.name) if param.name else []) or [] for item, help_text in valid_completion_items: if item.startswith(incomplete) and item not in previous_items: @@ -26,7 +28,9 @@ def complete_user_or_greeter(ctx: typer.Context, param: click.Parameter, incompl def main( user: Annotated[ List[str], - typer.Option(help="The user to say hi to.", autocompletion=complete_user_or_greeter), + typer.Option( + help="The user to say hi to.", autocompletion=complete_user_or_greeter + ), ] = ["World"], greeter: Annotated[ List[str], From d64bc5246bc40f5242b7930c25764294a8502ee2 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 24 Oct 2025 17:10:59 +0200 Subject: [PATCH 10/11] update related tests as well for internal consistency --- tests/assets/completion_argument.py | 2 +- tests/assets/completion_no_types.py | 4 ++-- tests/assets/completion_no_types_order.py | 4 ++-- tests/test_others.py | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/assets/completion_argument.py b/tests/assets/completion_argument.py index f91e2b7cfb..a2536d171d 100644 --- a/tests/assets/completion_argument.py +++ b/tests/assets/completion_argument.py @@ -12,7 +12,7 @@ def shell_complete(ctx: click.Context, param: click.Parameter, incomplete: str): @app.command(context_settings={"auto_envvar_prefix": "TEST"}) -def main(name: str = typer.Argument(shell_complete=shell_complete)): +def main(user: str = typer.Argument(shell_complete=shell_complete)): """ Say hello. """ diff --git a/tests/assets/completion_no_types.py b/tests/assets/completion_no_types.py index 3ee3f3820b..dbf114d5d6 100644 --- a/tests/assets/completion_no_types.py +++ b/tests/assets/completion_no_types.py @@ -16,8 +16,8 @@ def complete(ctx, args, param, incomplete): @app.command() -def main(name: str = typer.Option("World", autocompletion=complete)): - print(f"Hello {name}") +def main(user: str = typer.Option("World", autocompletion=complete)): + print(f"Hello {user}") if __name__ == "__main__": diff --git a/tests/assets/completion_no_types_order.py b/tests/assets/completion_no_types_order.py index 11e8f5a599..0acffdda58 100644 --- a/tests/assets/completion_no_types_order.py +++ b/tests/assets/completion_no_types_order.py @@ -16,8 +16,8 @@ def complete(args, incomplete, ctx, param): @app.command() -def main(name: str = typer.Option("World", autocompletion=complete)): - print(f"Hello {name}") +def main(user: str = typer.Option("World", autocompletion=complete)): + print(f"Hello {user}") if __name__ == "__main__": diff --git a/tests/test_others.py b/tests/test_others.py index 64f8a04eb2..fa3090b9b0 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -202,7 +202,7 @@ def test_completion_argument(): ) assert "Emma" in result.stdout or "_files" in result.stdout assert "ctx: completion_argument" in result.stderr - assert "arg is: name" in result.stderr + assert "arg is: user" in result.stderr assert "incomplete is: E" in result.stderr @@ -215,12 +215,12 @@ def test_completion_untyped_parameters(): env={ **os.environ, "_COMPLETION_NO_TYPES.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "completion_no_types.py --name Sebastian --name Ca", + "_TYPER_COMPLETE_ARGS": "completion_no_types.py --user Sebastian --user Ca", }, ) assert "info name is: completion_no_types.py" in result.stderr assert "args is: []" in result.stderr - assert "param is: name" in result.stderr + assert "param is: user" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout @@ -242,12 +242,12 @@ def test_completion_untyped_parameters_different_order_correct_names(): env={ **os.environ, "_COMPLETION_NO_TYPES_ORDER.PY_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": "completion_no_types_order.py --name Sebastian --name Ca", + "_TYPER_COMPLETE_ARGS": "completion_no_types_order.py --user Sebastian --user Ca", }, ) assert "info name is: completion_no_types_order.py" in result.stderr assert "args is: []" in result.stderr - assert "param is: name" in result.stderr + assert "param is: user" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout @@ -271,7 +271,7 @@ def main(name: str = typer.Option(..., autocompletion=name_callback)): pass # pragma: no cover with pytest.raises(click.ClickException) as exc_info: - runner.invoke(app, ["--name", "Camila"]) + runner.invoke(app, ["--user", "Camila"]) assert exc_info.value.message == "Invalid autocompletion callback parameters: val2" From 7734bcb8a8ce4b2f0e808105d12926c1f13ead6f Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Fri, 24 Oct 2025 17:13:11 +0200 Subject: [PATCH 11/11] update docs --- docs/tutorial/options-autocompletion.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/options-autocompletion.md b/docs/tutorial/options-autocompletion.md index f1e300584b..ad9401baa4 100644 --- a/docs/tutorial/options-autocompletion.md +++ b/docs/tutorial/options-autocompletion.md @@ -270,7 +270,7 @@ You may want to reuse completer functions across CLI applications or within the This can be done by declaring a parameter of type click.Parameter, and accessing its `param.name` attribute. -For example, lets revisit our above example and a second greeter argument that reuses the same completer function, now called `complete_user_or_greeter`: +For example, lets revisit our above example and add a second greeter argument that reuses the same completer function, now called `complete_user_or_greeter`: {* docs_src/options_autocompletion/tutorial010_an.py hl[15:16] *} @@ -288,7 +288,7 @@ Check it: ```console $ typer ./main.py run --user Sebastian --greeter Camila --greeter [TAB][TAB] -// Our function returns Sebastian too because it is completing 'greeter' +// Our function returns Sebastian too because it is completing 'greeter', not 'user' Carlos -- The writer of scripts. Sebastian -- The type hints guy. ```