跳至内容

使用 Click

警告

这是一个更高级的主题,如果您是 **Typer** 的初学者,可以跳过它。

它主要对已经使用 Click 并对它有疑问的人有用。

**Typer** 由 Click 提供支持。它在底层完成所有工作。

以下是一些关于将两者结合使用的更多信息。

一个同时使用 Click 和 **Typer** 的应用程序

如果您已经有一个 Click 应用程序,并且想要迁移到 **Typer**,或者添加一些 Typer 组件,您可以从 Typer 应用程序获取 Click Command,然后直接使用 Click。

Click 的工作原理

在了解如何组合 Click 和 **Typer** 之前,让我们先了解一下 Click 的工作原理。

Click Command

任何 Click 应用程序都包含一个 Command 类的对象。这或多或少是 Click 最基本的对象。

一个 Command 可以拥有自己的CLI 参数CLI 选项,并且它有一个被调用的函数。

例如,在这个 Click 应用程序中

import click


@click.command()
@click.option("--count", default=1, help="Number of greetings.")
@click.option("--name", prompt="Your name", help="The person to greet.")
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo("Hello %s!" % name)


if __name__ == "__main__":
    hello()

原始的 hello 变量被 Click 从一个函数转换为一个 Command 对象。原始的 hello 函数被这个 Command 在内部使用,但它不再被称为 hello(因为 hello 现在是一个 Click Command)。

Click Group

然后 Click 还包含一个 Group 类,它继承自 Command。因此,一个 Group 对象也是一个 Command

一个 Group 也可以拥有自己的CLI 参数CLI 选项

一个 Group 可以拥有 Command 类的子命令或 Group 类的子组。

一个 Group 也可以拥有一个它调用的函数,在调用任何特定子命令的函数之前调用。

例如

import click


@click.group()
def cli():
    pass


@click.command()
def initdb():
    click.echo("Initialized the database")


@click.command()
def dropdb():
    click.echo("Dropped the database")


cli.add_command(initdb)
cli.add_command(dropdb)


if __name__ == "__main__":
    cli()

cli 变量被 Click 从一个函数转换为一个 Group 对象。原始的 cli 函数被这个 Group 在内部使用。

提示

原始的 cli 函数相当于一个 Typer 回调

然后,现在是 Group 对象的 cli 变量被用来添加子命令。

Typer 的工作原理

Typer 不会修改函数。您创建一个 typer.Typer 类的显式变量,并使用它来注册这些函数。

然后,当您调用应用程序时,Typer 会创建一个 Click Command(或 Group),然后调用它。

如果您的应用程序只有一个命令,那么当您调用它时,Typer 会创建一个 Click Command 对象并调用它。

但如果您的应用程序包含以下任何一项,Typer 会创建一个 Click Group 对象

  • 多个命令。
  • 一个回调。
  • 子 Typer 应用程序(子命令)。

提示

如果您想了解更多信息,请查看 单个或多个命令 部分。

组合 Click 和 Typer

Typer 使用内部函数 typer.main.get_command()typer.Typer 对象生成 Click Command(或 Group)。

您可以直接使用它,并将 Click 对象与其他 Click 应用程序一起使用。

Typer 应用程序中包含 Click 应用程序

例如,您可以拥有一个 Typer 应用程序,从中生成一个 Click Group,然后在其中包含其他 Click 应用程序。

import click
import typer

app = typer.Typer()


@app.command()
def top():
    """
    Top level command, form Typer
    """
    print("The Typer app is at the top level")


@app.callback()
def callback():
    """
    Typer app, including Click subapp
    """


@click.command()
@click.option("--name", prompt="Your name", help="The person to greet.")
def hello(name):
    """Simple program that greets NAME for a total of COUNT times."""
    click.echo("Hello %s!" % name)


typer_click_object = typer.main.get_command(app)

typer_click_object.add_command(hello, "hello")

if __name__ == "__main__":
    typer_click_object()

请注意,我们添加了一个不执行任何操作的回调(仅记录 CLI 程序),以确保 Typer 创建一个 Click Group。这样我们就可以向该 Click Group 添加子命令。

然后我们从 typer.Typer 应用程序(typer_click_object)生成一个 Click 对象,然后我们可以将另一个 Click 对象(hello)包含在这个 Click Group 中。

这样,我们的 Typer 应用程序将有一个使用 Typer 构建的子命令 top,以及一个使用 Click 构建的子命令 hello

检查一下

$ python main.py --help

// Notice we have both subcommands, top and hello
Usage: main.py [OPTIONS] COMMAND [ARGS]...

Options:
  --install-completion  Install completion for the current shell.
  --show-completion     Show completion for the current shell, to copy it or customize the installation.
  --help                Show this message and exit.

Commands:
  hello
  top

// Call the Typer part
$ python main.py top

The Typer app is at the top level

// Call the Click part
$ python main.py hello --name Camila

Hello Camila!

在 Click 应用程序中包含 Typer 应用程序

同样,您可以做相反的事情,将 Typer 子应用程序包含在一个更大的 Click 应用程序中。

import click
import typer


@click.group()
def cli():
    pass


@cli.command()
def initdb():
    click.echo("Initialized the database")


@cli.command()
def dropdb():
    click.echo("Dropped the database")


app = typer.Typer()


@app.command()
def sub():
    """
    A single-command Typer sub app
    """
    print("Typer is now below Click, the Click app is the top level")


typer_click_object = typer.main.get_command(app)

cli.add_command(typer_click_object, "sub")

if __name__ == "__main__":
    cli()

请注意,我们不必添加回调或更多命令,我们只需创建一个生成单个 Click CommandTyper 应用程序,因为我们不需要在 Typer 应用程序下包含任何内容。

然后我们从 typer.Typer 应用程序(typer_click_object)生成一个 Click 对象,然后我们使用 Click cli 来包含来自 Typer 应用程序的 Click 对象。

在这种情况下,原始 Click 应用程序包含 Typer 应用程序。

然后我们调用原始 Click 应用程序,而不是 Typer 应用程序。

检查一下

$ python main.py

// We get our Typer app down there in the sub command
Usage: main.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  dropdb
  initdb
  sub     A single-command Typer sub app


// Use the Click part
$ python main.py initdb

Initialized the database

// And use the Typer part
$ python main.py sub

Typer is now below Click, the Click app is the top level

关于 Click 装饰器

Typer 应用程序无法直接使用 Click 装饰器。

这是因为 Typer 不会修改函数以添加元数据或将其转换为 Click 所做的其他对象。

因此,诸如 @click.pass_context 之类的东西将不起作用。

Click 中装饰器提供的多数功能在 Typer 中都有替代方法。

例如,要访问上下文,您只需声明一个类型为 typer.Context 的函数参数。

提示

您可以在文档中阅读有关使用上下文的更多信息:命令:使用上下文

但是,如果您需要使用基于 Click 装饰器的功能,您可以始终使用上面描述的方法生成一个 Click 对象,并像通常使用 Click 一样使用它。