复杂的应用程序 — 单击文档

来自菜鸟教程
Click/docs/7.x/complex
跳转至:导航、​搜索

复杂的应用

Click 旨在帮助创建复杂和简单的 CLI 工具。 然而,其设计的强大之处在于能够将系统任意嵌套在一起。 例如,如果您曾经使用过 Django,您就会意识到它提供了一个命令行实用程序,但 Celery 也是如此。 在 Django 中使用 Celery 时,有两个工具需要相互交互并进行交叉配置。

在两个独立的 Click 命令行实用程序的理论世界中,它们可以通过将一个嵌套在另一个中来解决这个问题。 例如,Web 框架还可以加载消息队列框架的命令。

基本概念

要了解其工作原理,您需要了解两个概念:上下文和调用约定。

上下文

每当执行 Click 命令时,都会创建一个 Context 对象,该对象保存此特定调用的状态。 它记住解析的参数、创建它的命令、需要在函数结束时清理哪些资源等等。 它还可以选择保存应用程序定义的对象。

上下文对象构建一个链表,直到它们到达顶部。 每个上下文都链接到一个父上下文。 这允许一个命令在另一个命令下工作并在那里存储它自己的信息,而不必担心改变父命令的状态。

但是,由于父数据可用,因此可以在需要时导航到它。

大多数时候,您看不到上下文对象,但是在编写更复杂的应用程序时它会派上用场。 这将我们带到下一点。


调用约定

当执行 Click 命令回调时,它会将所有非隐藏参数作为关键字参数传递。 值得注意的是没有上下文。 但是,回调可以通过用 pass_context() 标记自己来选择传递给上下文对象。

那么如果您不知道它是否应该接收上下文,您如何调用命令回调? 答案是上下文本身提供了一个辅助函数 (Context.invoke()),它可以为您执行此操作。 它接受回调作为第一个参数,然后正确调用该函数。


构建 Git 克隆

在这个例子中,我们想要构建一个类似于版本控制系统的命令行工具。 像 Git 这样的系统通常提供一个已经接受一些参数和配置的总体命令,然后有额外的子命令来做其他事情。

根命令

在顶层,我们需要一个可以容纳我们所有命令的组。 在这种情况下,我们使用基本的 click.group(),它允许我们在其下方注册其他 Click 命令。

对于此命令,我们还希望接受一些配置工具状态的参数:

让我们了解一下这是做什么的。 我们创建了一个可以有子命令的组命令。 当它被调用时,它会创建一个 Repo 类的实例。 这保存了我们的命令行工具的状态。 在这种情况下,它只是记住了一些参数,但此时它也可以开始加载配置文件等。

然后这个状态对象被上下文记住为 obj。 这是一个特殊的属性,命令应该记住他们需要传递给他们的孩子的东西。

为了让它起作用,我们需要用 pass_context() 标记我们的函数,否则,上下文对象将完全对我们隐藏。


第一个子命令

让我们向其中添加第一个子命令,即 clone 命令:

所以现在我们有了一个 clone 命令,但是我们如何访问 repo 呢? 正如您可以想象的那样,一种方法是使用 pass_context() 函数,这将再次使我们的回调也获取传递给我们记住 repo 的上下文。 然而,这个装饰器的第二个版本叫做 pass_obj() ,它只会传递存储的对象,(在我们的例子中是 repo):


交错命令

虽然与我们想要构建的特定程序无关,但对交织系统也有很好的支持。 想象一下,例如我们的版本控制系统有一个非常酷的插件,它需要大量配置,并且想要将自己的配置存储为 obj。 如果我们在下面附加另一个命令,我们会突然获得插件配置而不是我们的 repo 对象。

解决此问题的一种明显方法是在插件中存储对 repo 的引用,但随后需要知道它附加在此类插件下方的命令。

有一个更好的系统可以通过利用上下文的链接特性来构建。 我们知道插件上下文链接到创建我们的 repo 的上下文。 因此,我们可以开始搜索上下文存储的对象是存储库的最后一层。

对此的内置支持由 make_pass_decorator() 工厂提供,它将为我们创建寻找对象的装饰器(它内部调用 Context.find_object())。 在我们的例子中,我们知道我们想要找到最近的 Repo 对象,所以让我们为此创建一个装饰器:

如果我们现在使用 pass_repo 而不是 pass_obj,我们将总是得到一个 repo 而不是其他东西:


确保对象创建

上面的示例仅在存在创建 Repo 对象并将其存储在上下文中的外部命令时才有效。 对于一些更高级的用例,这可能会成为一个问题。 make_pass_decorator() 的默认行为是调用 Context.find_object() 它将找到对象。 如果找不到对象,make_pass_decorator() 将引发错误。 另一种行为是使用 Context.ensure_object() 它将找到对象,如果找不到它,将创建一个并将其存储在最内部的上下文中。 也可以通过传递 ensure=Truemake_pass_decorator() 启用此行为:

在这种情况下,最内层的上下文会在它丢失时创建一个对象。 这可能会取代之前放置在那里的对象。 在这种情况下,该命令保持可执行,即使外部命令没有运行。 为此,对象类型需要有一个不接受任何参数的构造函数。

因此它独立运行:

如您所见: