```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
, 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
+
+```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)