diff --git a/docs/tutorial/options-autocompletion.md b/docs/tutorial/options-autocompletion.md index 9b0e296924..23c3a8892d 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_py310.py hl[6:7,16] *} -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`: @@ -141,7 +141,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. @@ -181,7 +181,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 @@ -200,7 +200,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 @@ -210,7 +210,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". Every Typer application has a special object called a "Context" that is normally hidden. @@ -220,11 +220,11 @@ And from that context you can get the current values for each parameter. {* docs_src/options_autocompletion/tutorial007_an_py310.py hl[12:13,15] *} -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. @@ -233,22 +233,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. @@ -262,11 +262,43 @@ 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. In this case, you need to first determine which parameter is being asked to complete. + +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 add a second greeter argument that reuses the same completer function, now called `complete_user_or_greeter`: + +{* docs_src/options_autocompletion/tutorial010_an_py310.py hl[15:16] *} + +/// tip + +You may also return click.shell_completion.CompletionItem objects from completer functions instead of 2-tuples. + +/// + + +Check it: + +
+ +```console +$ typer ./main.py run --user Sebastian --greeter Camila --greeter [TAB][TAB] + +// Our function returns Sebastian too because it is completing 'greeter', not 'user' +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. -For example, something like `["typer", "main.py", "run", "--name"]`. +For example, something like `["typer", "main.py", "run", "--user"]`. /// tip @@ -317,10 +349,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. @@ -340,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_py310.py hl[15] *} @@ -349,20 +381,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. @@ -379,6 +411,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**" ✨ +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**" ✨ diff --git a/docs_src/options_autocompletion/tutorial001_an_py310.py b/docs_src/options_autocompletion/tutorial001_an_py310.py index 908aebe3aa..755f060dea 100644 --- a/docs_src/options_autocompletion/tutorial001_an_py310.py +++ b/docs_src/options_autocompletion/tutorial001_an_py310.py @@ -6,8 +6,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/tutorial001_py310.py b/docs_src/options_autocompletion/tutorial001_py310.py index 1cfc18cc20..6f4b53a875 100644 --- a/docs_src/options_autocompletion/tutorial001_py310.py +++ b/docs_src/options_autocompletion/tutorial001_py310.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/tutorial002_an_py310.py b/docs_src/options_autocompletion/tutorial002_an_py310.py index 837800e822..40ef7c634c 100644 --- a/docs_src/options_autocompletion/tutorial002_an_py310.py +++ b/docs_src/options_autocompletion/tutorial002_an_py310.py @@ -3,7 +3,7 @@ import typer -def complete_name(): +def complete_user(): return ["Camila", "Carlos", "Sebastian"] @@ -12,11 +12,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/tutorial002_py310.py b/docs_src/options_autocompletion/tutorial002_py310.py index 6c14a97ce4..934710734f 100644 --- a/docs_src/options_autocompletion/tutorial002_py310.py +++ b/docs_src/options_autocompletion/tutorial002_py310.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/tutorial003_an_py310.py b/docs_src/options_autocompletion/tutorial003_an_py310.py index a57cd38005..ae430366bd 100644 --- a/docs_src/options_autocompletion/tutorial003_an_py310.py +++ b/docs_src/options_autocompletion/tutorial003_an_py310.py @@ -2,14 +2,14 @@ import typer -valid_names = ["Camila", "Carlos", "Sebastian"] +valid_users = ["Camila", "Carlos", "Sebastian"] def complete_name(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 @@ -18,11 +18,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_name) ] = "World", ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial003_py310.py b/docs_src/options_autocompletion/tutorial003_py310.py index 9af41f23b5..7228569563 100644 --- a/docs_src/options_autocompletion/tutorial003_py310.py +++ b/docs_src/options_autocompletion/tutorial003_py310.py @@ -1,13 +1,13 @@ import typer -valid_names = ["Camila", "Carlos", "Sebastian"] +valid_users = ["Camila", "Carlos", "Sebastian"] def complete_name(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( + user: str = typer.Option( "World", help="The name to say hi to.", autocompletion=complete_name ), ): - print(f"Hello {name}") + print(f"Hello {user}") if __name__ == "__main__": diff --git a/docs_src/options_autocompletion/tutorial004_an_py310.py b/docs_src/options_autocompletion/tutorial004_an_py310.py index d07927e1e5..147cdc2eac 100644 --- a/docs_src/options_autocompletion/tutorial004_an_py310.py +++ b/docs_src/options_autocompletion/tutorial004_an_py310.py @@ -9,11 +9,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 @@ -23,11 +23,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_py310.py b/docs_src/options_autocompletion/tutorial004_py310.py index 3be0cf35db..cf03a0d10e 100644 --- a/docs_src/options_autocompletion/tutorial004_py310.py +++ b/docs_src/options_autocompletion/tutorial004_py310.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/tutorial005_an_py310.py b/docs_src/options_autocompletion/tutorial005_an_py310.py index c7d7bf53ad..347468e1db 100644 --- a/docs_src/options_autocompletion/tutorial005_an_py310.py +++ b/docs_src/options_autocompletion/tutorial005_an_py310.py @@ -9,10 +9,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() @@ -20,11 +20,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_py310.py b/docs_src/options_autocompletion/tutorial005_py310.py index 10ac532ad2..9f993b89fd 100644 --- a/docs_src/options_autocompletion/tutorial005_py310.py +++ b/docs_src/options_autocompletion/tutorial005_py310.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/tutorial006_an_py310.py b/docs_src/options_autocompletion/tutorial006_an_py310.py index 57e2ee285d..7e2c705fc3 100644 --- a/docs_src/options_autocompletion/tutorial006_an_py310.py +++ b/docs_src/options_autocompletion/tutorial006_an_py310.py @@ -7,10 +7,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/tutorial006_py310.py b/docs_src/options_autocompletion/tutorial006_py310.py index ef23bf6468..1d669e0132 100644 --- a/docs_src/options_autocompletion/tutorial006_py310.py +++ b/docs_src/options_autocompletion/tutorial006_py310.py @@ -4,9 +4,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/tutorial007_an_py310.py b/docs_src/options_autocompletion/tutorial007_an_py310.py index 2b25a1075c..606d9abbc5 100644 --- a/docs_src/options_autocompletion/tutorial007_an_py310.py +++ b/docs_src/options_autocompletion/tutorial007_an_py310.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,13 +21,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/tutorial007_py310.py b/docs_src/options_autocompletion/tutorial007_py310.py index 17bb7ead37..eb5b643a58 100644 --- a/docs_src/options_autocompletion/tutorial007_py310.py +++ b/docs_src/options_autocompletion/tutorial007_py310.py @@ -7,11 +7,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() @@ -19,12 +19,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/tutorial008_an_py310.py b/docs_src/options_autocompletion/tutorial008_an_py310.py index c4ae20ed23..4dd758b4ed 100644 --- a/docs_src/options_autocompletion/tutorial008_an_py310.py +++ b/docs_src/options_autocompletion/tutorial008_an_py310.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,13 +24,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/tutorial008_py310.py b/docs_src/options_autocompletion/tutorial008_py310.py index 7a71c3f1b2..00af7d4b3f 100644 --- a/docs_src/options_autocompletion/tutorial008_py310.py +++ b/docs_src/options_autocompletion/tutorial008_py310.py @@ -10,11 +10,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() @@ -22,12 +22,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 name 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_py310.py b/docs_src/options_autocompletion/tutorial009_an_py310.py index 5b86ff718d..80d42844a0 100644 --- a/docs_src/options_autocompletion/tutorial009_an_py310.py +++ b/docs_src/options_autocompletion/tutorial009_an_py310.py @@ -1,6 +1,7 @@ from typing import Annotated import typer +from click.core import Parameter from rich.console import Console valid_completion_items = [ @@ -12,12 +13,14 @@ err_console = Console(stderr=True) -def complete_name(ctx: typer.Context, args: list[str], incomplete: str): +def complete_user( + ctx: typer.Context, args: list[str], param: Parameter, incomplete: str +): err_console.print(f"{args}") - 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) + 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() @@ -25,12 +28,12 @@ def complete_name(ctx: typer.Context, 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: + for n in user: print(f"Hello {n}") diff --git a/docs_src/options_autocompletion/tutorial009_py310.py b/docs_src/options_autocompletion/tutorial009_py310.py index 6f00da3758..cd39347288 100644 --- a/docs_src/options_autocompletion/tutorial009_py310.py +++ b/docs_src/options_autocompletion/tutorial009_py310.py @@ -1,4 +1,5 @@ import typer +from click.core import Parameter from rich.console import Console valid_completion_items = [ @@ -10,12 +11,14 @@ err_console = Console(stderr=True) -def complete_name(ctx: typer.Context, args: list[str], incomplete: str): +def complete_user( + ctx: typer.Context, args: list[str], param: Parameter, incomplete: str +): err_console.print(f"{args}") - 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) + 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() @@ -23,12 +26,12 @@ def complete_name(ctx: typer.Context, 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/tutorial010_an_py310.py b/docs_src/options_autocompletion/tutorial010_an_py310.py new file mode 100644 index 0000000000..90424ea645 --- /dev/null +++ b/docs_src/options_autocompletion/tutorial010_an_py310.py @@ -0,0 +1,44 @@ +from typing import Annotated + +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_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() + + +@app.command() +def main( + user: Annotated[ + list[str], + typer.Option( + help="The user to say hi to.", autocompletion=complete_user_or_greeter + ), + ] = ["World"], + greeter: Annotated[ + list[str], + typer.Option(help="The greeters.", autocompletion=complete_user_or_greeter), + ] = [], +): + for u in user: + print(f"Hello {u}, from {' and '.join(greeter)}") + + +if __name__ == "__main__": + app() diff --git a/docs_src/options_autocompletion/tutorial010_py310.py b/docs_src/options_autocompletion/tutorial010_py310.py new file mode 100644 index 0000000000..e50cc9e4d1 --- /dev/null +++ b/docs_src/options_autocompletion/tutorial010_py310.py @@ -0,0 +1,40 @@ +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_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() + + +@app.command() +def main( + 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="The greeters.", autocompletion=complete_user_or_greeter + ), +): + for u in user: + print(f"Hello {u}, from {' and '.join(greeter or [])}") + + +if __name__ == "__main__": + app() diff --git a/pyproject.toml b/pyproject.toml index e2348509be..54db2f625c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -179,6 +179,7 @@ ignore = [ "docs_src/options_autocompletion/tutorial007_an_py310.py" = ["B006"] "docs_src/options_autocompletion/tutorial008_an_py310.py" = ["B006"] "docs_src/options_autocompletion/tutorial009_an_py310.py" = ["B006"] +"docs_src/options_autocompletion/tutorial010_an_py310.py" = ["B006"] "docs_src/parameter_types/enum/tutorial003_an_py310.py" = ["B006"] # Loop control variable `value` not used within loop body "docs_src/progressbar/tutorial001_py310.py" = ["B007"] 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 8dc610a1b2..dbf114d5d6 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."), @@ -15,8 +16,8 @@ def complete(ctx, args, 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 dbbbc77f19..0acffdda58 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."), @@ -15,8 +16,8 @@ def complete(args, incomplete, ctx): @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 b389ed353f..a20a2efdb5 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -200,7 +200,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 @@ -213,11 +213,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: 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 @@ -239,11 +240,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: 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 @@ -267,7 +269,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" diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial001.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial001.py index bf8e3b9879..14d70f193e 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial001.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial001.py @@ -23,7 +23,7 @@ def get_mod(request: pytest.FixtureRequest) -> ModuleType: def test_1(mod: ModuleType): - 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.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py index 22bccc9a8d..193eb29448 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py @@ -33,7 +33,7 @@ def test_completion(mod: ModuleType): env={ **os.environ, f"_{file_name.upper()}_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": f"{file_name} --name ", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user ", }, ) assert "Camila" in result.stdout @@ -42,7 +42,7 @@ def test_completion(mod: ModuleType): def test_1(mod: ModuleType): - 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 31a371cde6..0c51e11639 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py @@ -33,7 +33,7 @@ def test_completion_zsh(mod: ModuleType): env={ **os.environ, f"_{file_name.upper()}_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": f"{file_name} --name Seb", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user Seb", }, ) assert "Camila" not in result.stdout @@ -50,7 +50,7 @@ def test_completion_powershell(mod: ModuleType): env={ **os.environ, f"_{file_name.upper()}_COMPLETE": "complete_powershell", - "_TYPER_COMPLETE_ARGS": f"{file_name} --name Seb", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user Seb", "_TYPER_COMPLETE_WORD_TO_COMPLETE": "Seb", }, ) @@ -60,7 +60,7 @@ def test_completion_powershell(mod: ModuleType): def test_1(mod: ModuleType): - 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_tutorial005.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial004_tutorial005.py index b0a2aad640..3d50019ec7 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial004_tutorial005.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial004_tutorial005.py @@ -35,7 +35,7 @@ def test_completion(mod: ModuleType): env={ **os.environ, f"_{file_name.upper()}_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": f"{file_name} --name ", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -44,7 +44,7 @@ def test_completion(mod: ModuleType): def test_1(mod: ModuleType): - 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_tutorial006.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial006.py index 6dd1893f09..34c504b860 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial006.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial006.py @@ -23,13 +23,13 @@ def get_mod(request: pytest.FixtureRequest) -> ModuleType: def test_1(mod: ModuleType): - 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 def test_2(mod: ModuleType): - 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.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py index 6d48dfeaef..0676c91233 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py @@ -33,7 +33,7 @@ def test_completion(mod: ModuleType): env={ **os.environ, f"_{file_name.upper()}_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": f"{file_name} --name Sebastian --name ", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user Sebastian --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -42,7 +42,7 @@ def test_completion(mod: ModuleType): def test_1(mod: ModuleType): - 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 f813ce41e6..fe20e0cee0 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py @@ -33,7 +33,7 @@ def test_completion(mod: ModuleType): env={ **os.environ, f"_{file_name.upper()}_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": f"{file_name} --name ", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -43,7 +43,7 @@ def test_completion(mod: ModuleType): def test_1(mod: ModuleType): - 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 c90f7f495c..cf5c987b99 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py @@ -33,7 +33,7 @@ def test_completion(mod: ModuleType): env={ **os.environ, f"_{file_name.upper()}_COMPLETE": "complete_zsh", - "_TYPER_COMPLETE_ARGS": f"{file_name} --name Sebastian --name ", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user Sebastian --user ", }, ) assert '"Camila":"The reader of books."' in result.stdout @@ -43,7 +43,7 @@ def test_completion(mod: ModuleType): def test_1(mod: ModuleType): - 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 new file mode 100644 index 0000000000..abf34eb383 --- /dev/null +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial010.py @@ -0,0 +1,108 @@ +import importlib +import os +import subprocess +import sys +from pathlib import Path +from types import ModuleType + +import pytest +from typer.testing import CliRunner + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial010_py310"), + pytest.param("tutorial010_an_py310"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.options_autocompletion.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_completion(mod: ModuleType): + file_name = Path(mod.__file__).name + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + f"_{file_name.upper()}_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user Sebastian --user ", + }, + ) + 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(mod: ModuleType): + file_name = Path(mod.__file__).name + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + f"_{file_name.upper()}_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user 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(mod: ModuleType): + file_name = Path(mod.__file__).name + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + f"_{file_name.upper()}_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": f"{file_name} --user 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(mod: ModuleType): + 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 + + +def test_2(mod: ModuleType): + result = runner.invoke( + mod.app, ["--user", "Camila", "--user", "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(mod: ModuleType): + result = runner.invoke( + mod.app, ["--user", "Camila", "--greeter", "Carlos", "--greeter", "Sebastian"] + ) + assert result.exit_code == 0 + assert "Hello Camila, from Carlos and Sebastian" in result.output + + +def test_script(mod: ModuleType): + 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/core.py b/typer/core.py index 3e72d83989..f450bf1319 100644 --- a/typer/core.py +++ b/typer/core.py @@ -48,7 +48,8 @@ def _typer_param_setup_autocompletion_compat( self: click.Parameter, *, autocompletion: Callable[ - [click.Context, list[str], str], list[tuple[str, str] | str] + [click.Context, list[str], click.core.Parameter, str], + list[Union[tuple[str, str], str, "click.shell_completion.CompletionItem"]], ] | None = None, ) -> None: @@ -71,9 +72,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 f4f21bb844..f09c83b8c9 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1847,6 +1847,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[:]: @@ -1857,6 +1858,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.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) @@ -1868,6 +1872,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) @@ -1878,12 +1885,19 @@ def get_param_completion( f"Invalid autocompletion callback parameters: {show_params}" ) - def wrapper(ctx: click.Context, args: list[str], incomplete: str | None) -> Any: + def wrapper( + ctx: click.Context, + args: list[str], + param: click.core.Parameter, + incomplete: str | None, + ) -> Any: use_params: dict[str, Any] = {} if ctx_name: use_params[ctx_name] = ctx if args_name: use_params[args_name] = args + if param_name: + use_params[param_name] = param if incomplete_name: use_params[incomplete_name] = incomplete return callback(**use_params)