跳到主要内容

自动补全 CLI Option

https://typer.tiangolo.com/tutorial/options-autocompletion/

typer.Option 中使用 autocompletion 参数来实现自动补全

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


app = typer.Typer()


@app.command()
def main(
name: Annotated[
str, typer.Option(help="The name to say hi to.", autocompletion=complete_name)
] = "World",
):
print(f"Hello {name}")

image-20240711104205542

自动检测未补全的值

Right now, we always return those values, even if users start typing Sebast and then hit TAB, they will also get the completion for Camila and Carlos (depending on the shell), while we should only get completion for Sebastian.

Modify the complete_name() 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:

def complete_name(incomplete: str):
completion = []
for name in valid_names:
if name.startswith(incomplete):
completion.append(name)
return completion

image-20240711105013870

不过,支持补全的值必须是 str,而不能是 int

为补全值添加注释说明

valid_completion_items = [
("Camila", "The reader of books."),
("Carlos", "The writer of scripts."),
("Sebastian", "The type hints guy."),
]


def complete_name(incomplete: str) -> list[tuple[str, str]]:
completion = []
for name, help_text in valid_completion_items:
if name.startswith(incomplete):
completion_item = (name, help_text)
completion.append(completion_item)
return completion

image-20240711110325630

使用 yield 简化语法

Instead of creating and returning a list with values (str or tuple), we can use yield with each value that we want in the completion.

valid_completion_items = [
("Camila", "The reader of books."),
("Carlos", "The writer of scripts."),
("Sebastian", "The type hints guy."),
]


def complete_name(incomplete: str):
for name, help_text in valid_completion_items:
if name.startswith(incomplete):
yield (name, help_text)

在多值 CLI Option 中忽略补全已输入的值

如果一个 CLI Option 允许输入多个值,而在补全时我们又不希望补全之前已经输入过的值,那么可以在补全函数中传入 typer.Context 获取已经输入过的值

from typing import List

import typer
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_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)


app = typer.Typer()


@app.command()
def main(
name: Annotated[
List[str],
typer.Option(help="The name to say hi to.", autocompletion=complete_name),
] = ["World"],
):
for n in name:
print(f"Hello {n}")


if __name__ == "__main__":
app()

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.

获取 raw CLI parameters

raw CLI parameters 就是 a list of str with everything passed in the command line before the incomplete value,例如 ["typer", "main.py", "run", "--name"]

补全系统只会读取来自标准输出(standard output)的内容。所以我们可以把信息打印到标准错误(standard error)上,不让补全系统读到:

err_console = Console(stderr=True)


def complete_name(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)

如此这般,我们就可以把 raw CLI parameters 打印出来啦

image-20240711113357447

This is a very simple (and quite useless) example, just so you know how it works and that you can use it.

But it's probably useful only in very advanced use cases.

当然,综合起来的补全函数的样子如下:

def complete_name(ctx: typer.Context, args: List[str], 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)

Typer uses the type declarations to detect what it has to provide to your autocompletion function.

You can declare function parameters of these types:

  • str: for the incomplete value.
  • typer.Context: for the current context.
  • 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" ✨