Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
860c392
allow Parameter to be acception by autocompletion functions, also pas…
bckohan Sep 18, 2024
40fa0d5
add reusable completer function tutorial, add tutorial test for coverage
bckohan Nov 8, 2024
c2ffc6b
update completion_no_types tests to check for param as well
bckohan Nov 8, 2024
46c406b
remove python 3.7 tabs for reusable completer example
bckohan Dec 5, 2024
031a59e
Merge branch 'master' into feat/autocompletion
bckohan Dec 10, 2024
05cb6f0
Merge branch 'master' into feat/autocompletion
bckohan Dec 18, 2024
4c356f5
Merge branch 'master' into feat/autocompletion
bckohan Jan 17, 2025
c4ef5ab
revert bug fix for args parameter in completer functions, this will b…
bckohan Jan 24, 2025
3c4bff4
fix linting error introduced on last commit
bckohan Jan 24, 2025
a60d98c
Merge branch 'master' into feat/autocompletion
bckohan Jan 24, 2025
bd49bcf
Merge branch 'master' into feat/autocompletion
bckohan Jan 31, 2025
793921e
Merge branch 'master' into feat/autocompletion
bckohan Feb 10, 2025
723cf94
Merge branch 'master' into feat/autocompletion
bckohan Feb 27, 2025
fffccef
Merge branch 'master' into feat/autocompletion
svlandeg Oct 24, 2025
d5fcab0
rename the 'name' option to 'user' throughout this tutorial
svlandeg Oct 24, 2025
7a80fc3
fix count
svlandeg Oct 24, 2025
921a443
reformat
svlandeg Oct 24, 2025
96fe8c9
Merge remote-tracking branch 'upstream_bckohan/feat/autocompletion' i…
svlandeg Oct 24, 2025
d64bc52
update related tests as well for internal consistency
svlandeg Oct 24, 2025
7734bcb
update docs
svlandeg Oct 24, 2025
2d109aa
Merge branch 'master' into feat/autocompletion
svlandeg Nov 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 65 additions & 32 deletions docs/tutorial/options-autocompletion.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand All @@ -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:

<div class="termy">

```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
Expand All @@ -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:

Expand All @@ -86,7 +86,7 @@ Now let's try it:
<div class="termy">

```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
Expand Down Expand Up @@ -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`:

Expand Down Expand Up @@ -143,7 +143,7 @@ If you have a shell like Zsh, it would look like:
<div class="termy">

```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.
Expand Down Expand Up @@ -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

Expand All @@ -202,7 +202,7 @@ And then we can use it like:
<div class="termy">

```console
$ typer ./main.py run --name Camila --name Sebastian
$ typer ./main.py run --user Camila --user Sebastian

Hello Camila
Hello Sebastian
Expand All @@ -212,21 +212,21 @@ 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 <a href="https://click.palletsprojects.com/en/7.x/commands/#nested-handling-and-contexts" class="external-link" target="_blank">"Context"</a> 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 <a href="https://click.palletsprojects.com/en/stable/commands/#nested-handling-and-contexts" class="external-link" target="_blank">"Context"</a> that is normally hidden.

But you can access the context by declaring a function parameter of type `typer.Context`.

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.

Expand All @@ -235,22 +235,22 @@ Check it:
<div class="termy">

```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.
Expand All @@ -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 <a href="https://click.palletsprojects.com/en/stable/api/#click.Parameter" class="external-link" target="_blank">click.Parameter</a>, 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 <a href="https://click.palletsprojects.com/en/stable/api/#click.shell_completion.CompletionItem" class="external-link" target="_blank">click.shell_completion.CompletionItem</a> objects from completer functions instead of 2-tuples.

///


Check it:

<div class="termy">

```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.
```

</div>


## 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

Expand Down Expand Up @@ -319,10 +351,10 @@ And then we just print it to "standard error".
<div class="termy">

```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.
Expand All @@ -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] *}

Expand All @@ -351,20 +383,20 @@ Check it:
<div class="termy">

```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.
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions docs_src/options_autocompletion/tutorial001.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__":
Expand Down
4 changes: 2 additions & 2 deletions docs_src/options_autocompletion/tutorial001_an.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__":
Expand Down
8 changes: 4 additions & 4 deletions docs_src/options_autocompletion/tutorial002.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import typer


def complete_name():
def complete_user():
return ["Camila", "Carlos", "Sebastian"]


Expand All @@ -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__":
Expand Down
8 changes: 4 additions & 4 deletions docs_src/options_autocompletion/tutorial002_an.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing_extensions import Annotated


def complete_name():
def complete_user():
return ["Camila", "Carlos", "Sebastian"]


Expand All @@ -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__":
Expand Down
16 changes: 8 additions & 8 deletions docs_src/options_autocompletion/tutorial003.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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__":
Expand Down
16 changes: 8 additions & 8 deletions docs_src/options_autocompletion/tutorial003_an.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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__":
Expand Down
Loading
Loading