diff --git a/docs/tutorial/options-autocompletion.md b/docs/tutorial/options-autocompletion.md index c0dcddb9fb..ad9401baa4 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,9 +212,9 @@ 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. +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`. @@ -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. @@ -264,11 +264,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.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 @@ -319,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. @@ -342,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*, 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] *} @@ -351,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. @@ -381,9 +413,10 @@ 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**" ✨ ## Comparison to Click functionality 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 7e82c7ff07..958beb821f 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,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: 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 c5b825eaf0..64640ebf59 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,12 +14,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() @@ -26,13 +29,13 @@ 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: - 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 new file mode 100644 index 0000000000..dce763cead --- /dev/null +++ b/docs_src/options_autocompletion/tutorial010.py @@ -0,0 +1,42 @@ +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_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/docs_src/options_autocompletion/tutorial010_an.py b/docs_src/options_autocompletion/tutorial010_an.py new file mode 100644 index 0000000000..8ced36e546 --- /dev/null +++ b/docs_src/options_autocompletion/tutorial010_an.py @@ -0,0 +1,45 @@ +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_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/pyproject.toml b/pyproject.toml index ac89bbd9bb..0987bc5754 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -187,6 +187,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/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 a8ba207a5f..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,11 +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: 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 @@ -241,11 +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: 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 @@ -269,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" 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 new file mode 100644 index 0000000000..8496dd5a07 --- /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 --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(): + 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 --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(): + 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 --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(): + 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(): + 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(): + 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(): + 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..de65f478b2 --- /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 --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(): + 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 --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(): + 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 --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(): + 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(): + 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(): + 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(): + 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 e9631e56cf..4f2619e495 100644 --- a/typer/core.py +++ b/typer/core.py @@ -53,7 +53,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, List[str], click.core.Parameter, str], + List[Union[Tuple[str, str], str, "click.shell_completion.CompletionItem"]], + ] ] = None, ) -> None: if self._custom_shell_complete is not None: @@ -75,9 +78,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 71a25e6c4b..20698639e8 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1026,6 +1026,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[:]: @@ -1036,6 +1037,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) @@ -1047,6 +1051,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) @@ -1057,12 +1064,19 @@ 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, + 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: 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)