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 a577369b16..27a5fd9a59 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -173,7 +173,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 @@ -199,7 +199,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 71ba031860..a9cbb9d216 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: @@ -127,6 +135,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: @@ -176,6 +189,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 aaa03b99cb..0cb76e88ac 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 a621bda6ad..ecc63e95dc 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1030,6 +1030,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[:]: @@ -1040,6 +1041,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) @@ -1051,6 +1055,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) @@ -1061,12 +1068,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)