From 860c3928b590fecccdce32f3615f04c3604707ec Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Tue, 17 Sep 2024 17:23:16 -0700 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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