跳至内容

版本 CLI 选项,is_eager

你可以使用回调来实现一个 --version CLI 选项

它将显示你的 CLI 程序的版本,然后终止它。甚至在处理任何其他 CLI 参数 之前。

--version 的第一个版本

让我们看看它的第一个版本可能是什么样子

from typing import Optional

import typer
from typing_extensions import Annotated

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def main(
    name: Annotated[str, typer.Option()] = "World",
    version: Annotated[
        Optional[bool], typer.Option("--version", callback=version_callback)
    ] = None,
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

提示

如果可能,最好使用 Annotated 版本。

from typing import Optional

import typer

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def main(
    name: str = typer.Option("World"),
    version: Optional[bool] = typer.Option(
        None, "--version", callback=version_callback
    ),
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

提示

请注意,我们不必获取 typer.Context 并检查 ctx.resilient_parsing 是否可以完成,因为我们仅在传递 --version 时才打印和修改程序,否则,不会从回调中打印或更改任何内容。

如果传递了 --version CLI 选项,我们将在回调中获得一个值 True

然后,我们可以打印版本并引发 typer.Exit() 以确保在执行任何其他操作之前终止程序。

我们还声明显式的 CLI 选项 名称 --version,因为我们不想要自动的 --no-version,它看起来很奇怪。

检查一下

$ python main.py --help

// We get a --version, and don't get an awkward --no-version 🎉
Usage: main.py [OPTIONS]

Options:
  --version
  --name TEXT
  --help                Show this message and exit.


// We can call it normally
$ python main.py --name Camila

Hello Camila

// And we can get the version
$ python main.py --version

Awesome CLI Version: 0.1.0

// Because we exit in the callback, we don't get a "Hello World" message after the version 🚀

之前的参数和 is_eager

但现在假设我们之前在 --version 之前声明的 --name CLI 选项 是必需的,并且它有一个可以退出程序的回调

from typing import Optional

import typer
from typing_extensions import Annotated

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def name_callback(name: str):
    if name != "Camila":
        raise typer.BadParameter("Only Camila is allowed")


def main(
    name: Annotated[str, typer.Option(callback=name_callback)],
    version: Annotated[
        Optional[bool], typer.Option("--version", callback=version_callback)
    ] = None,
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

提示

如果可能,最好使用 Annotated 版本。

from typing import Optional

import typer

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def name_callback(name: str):
    if name != "Camila":
        raise typer.BadParameter("Only Camila is allowed")


def main(
    name: str = typer.Option(..., callback=name_callback),
    version: Optional[bool] = typer.Option(
        None, "--version", callback=version_callback
    ),
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

那么我们的 CLI 程序在某些情况下可能无法按预期工作,因为它现在是,因为如果我们在 --name 之后使用 --version,那么 --name 的回调将被提前处理,并且我们可以得到它的错误

$ python main.py --name Rick --version

Only Camila is allowed
Aborted!

提示

我们不必在 name_callback() 中检查 ctx.resilient_parsing 以便完成工作,因为我们没有使用 typer.echo(),而是引发了一个 typer.BadParameter。

技术细节

typer.BadParameter 将错误打印到“标准错误”,而不是“标准输出”,并且因为完成系统只从“标准输出”读取,所以它不会破坏完成。

信息

如果你需要复习一下什么是“标准输出”和“标准错误”,请查看 打印和颜色:“标准输出”和“标准错误” 中的部分。

使用 is_eager 修复

对于这些情况,我们可以使用 is_eager=True 标记一个CLI 参数(一个CLI 选项CLI 参数)。

这将告诉 Typer(实际上是 Click)它应该在其他参数之前处理这个CLI 参数

from typing import Optional

import typer
from typing_extensions import Annotated

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def name_callback(name: str):
    if name != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return name


def main(
    name: Annotated[str, typer.Option(callback=name_callback)],
    version: Annotated[
        Optional[bool],
        typer.Option("--version", callback=version_callback, is_eager=True),
    ] = None,
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

提示

如果可能,最好使用 Annotated 版本。

from typing import Optional

import typer

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def name_callback(name: str):
    if name != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return name


def main(
    name: str = typer.Option(..., callback=name_callback),
    version: Optional[bool] = typer.Option(
        None, "--version", callback=version_callback, is_eager=True
    ),
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

检查一下

$ python main.py --name Rick --version

// Now we only get the version, and the name is not used
Awesome CLI Version: 0.1.0