Invoke 简介#

invoke 是 Python(2.7 和 3.4+)库,用于管理面向 shell 的子进程并组织可执行的 Python 代码成 CLI 可调用的任务。它从各种来源(make/rake、Fabric 2.x 等)汲取灵感,以获得强大且干净的功能集。

本文档介绍了Invoke的功能集的快速浏览。请参阅整个链接以获取详细的概念和API文档。有关安装帮助,请参阅项目的安装页面。

定义和运行任务函数#

Invoke 的核心用例是设置一组任务函数并执行它们。这非常简单 - 需要创建名为 tasks.py 的文件,导入任务装饰器并装饰一个或多个函数。您还需要添加任意命名的上下文参数(约定是使用 cctxcontext)作为第一个位置参数。目前还不需要担心使用此上下文参数。

让我们从虚拟的 Sphinx 文档构建任务开始:

from invoke import task

@task
def build(c):
    print("Building!")

然后,您可以通过告诉 Invoke 的命令行运行程序 invoke,您希望它运行来执行新任务:

$ invoke build
Building!

函数体可以是任何你想要的 Python 代码 - 任何内容。

任务参数#

函数可以有参数,因此任务也可以。默认情况下,您的任务函数的 args/kwargs 会自动映射到长和短的命令行标志,如命令行文档所述。例如,如果我们添加 clean 参数并给它布尔默认值,它将显示为一组切换标志, --clean-c

@task
def build(c, clean=False):
    if clean:
        print("Cleaning!")
    print("Building!")

命令:

$ invoke build -c
$ invoke build --clean

自然地,其他默认参数值将允许给出字符串或整数值。没有默认值的参数假定为接受字符串,也可以作为位置参数给出。以这个非常人为的代码片段为例:

@task
def hi(c, name):
    print(f"Hi {name}!")

它可以通过以下方式调用,所有结果都是“Hi Name!”:

$ invoke hi Name
$ invoke hi --name Name
$ invoke hi --name=Name
$ invoke hi -n Name
$ invoke hi -nName

通过 @task 添加元数据#

@task 可以在没有任何参数的情况下使用,如上所示,但它也是装饰的任务函数的附加元数据的方便向量。一个常见的示例是通过 help 参数描述任务的参数(除了可选地通过 docstring 给出任务级别的帮助):

@task(help={'name': "Name of the person to say hi to."})
def hi(c, name):
    """
    Say hi to someone.
    """
    print(f"Hi {name}!")

这个描述将在调用 --help 时显示:

$ invoke --help hi
Usage: inv[oke] [--core-opts] hi [--options] [other tasks here ...]

Docstring:
  Say hi to someone.

Options:
  -n STRING, --name=STRING   Name of the person to say hi to.

有关任务参数化和元数据的更多详细信息可以在调用任务中找到(对于命令行和解析方面)以及在任务 API 文档中找到(对于声明方面)。

列出任务#

有时,您希望查看给定的 tasks.py 中可用的任务 - invoke 可以告诉它列出它们而不是执行某些操作:

$ invoke --list
Available tasks:

    build

这还会打印每个任务的 docstring 的第一行(如果有的话)。要查看除了 --list 之外的其他可用内容,请输入 invoke --help

运行 shell 命令#

Invoke的许多用例涉及运行本地 shell 命令,类似于 Make或 Rake 等程序。这是通过 run 函数完成的:

from invoke import task

@task
def build(c):
    c.run("sphinx-build docs docs/_build")

当命令运行时,您将在终端中看到该命令的输出:

$ invoke build
Running Sphinx v1.1.3
loading pickled environment... done
...
build succeeded, 2 warnings.

run() 有许多参数控制其行为,例如为需要它们复杂程序激活伪终端、抑制错误退出行为、隐藏子进程的输出(同时仍然捕获它以供以后审查),等等。

‘context’ 参数到底是什么?

一常见的任务运行器面临的问题是如何传递“全局”数据 - 从配置文件其他配置向量加载的值,通过 CLI 标志提供,在 ‘setup’ 任务中生成等。

一些库(如 Fabric 1.x)通过模块级属性实现这一点,这使得测试困难、容易出错、限制并发并增加实现复杂性。

Invoke 将状态封装在显式的 Context 对象中,当任务执行时将其传递给它们。上下文是主要 API 端点,提供方法以尊重当前状态(如 run()),以及访问该状态本身。

声明预任务#

任务可以通过任务装饰器以多种方式进行配置。其中之一是通过名称选择要在执行任务之前始终运行的一个或多个其他任务。

让我们通过一个新的清理任务扩展我们的文档构建器,该任务在每次构建之前运行(但当然仍然可以独立执行):

from invoke import task

@task
def clean(c):
    c.run("rm -rf docs/_build")

@task(clean)
def build(c):
    c.run("sphinx-build docs docs/_build")

现在,当您调用 build 时,它会自动先运行 clean

备注

如果你不喜欢隐式的“位置参数是预运行任务名称”API,你可以显式地给 pre kwarg:@task(pre=[clean])

创建命名空间#

现在,我们的 tasks.py 仅用于文档,但也许我们的项目需要其他非文档内容,如打包/部署、测试等。那时,单个平坦的命名空间是不够的,所以 Invoke 让您可以轻松地构建嵌套命名空间。这里有一个快速示例。

首先,我们将任务文件重命名为 docs.py;不需要在那里进行其他更改。然后我们创建新任务文件,出于简洁起见,用一个真正顶级的任务填充它,称为 deploy

最后,我们可以使用新的 API 成员 Collection 类将此任务和 docs 模块绑定到单独的显式命名空间。当 Invoke 加载您的任务模块时,如果存在作为 nsnamespace 绑定的 Collection 对象,它将用于根命名空间:

from invoke import Collection, task
import docs

@task
def deploy(c):
    c.run("python setup.py sdist")
    c.run("twine upload dist/*")

namespace = Collection(docs, deploy)

结果:

$ invoke --list
Available tasks:

    deploy
    docs.build
    docs.clean