构造命名空间

这个 base case 加载任务的单个模块最初工作得很好,但高级用户通常需要更多的组织,例如将任务分成嵌套名称空间树。

这个 Collection 类提供用于组织任务的API(和 their configuration )变成树状结构。当被字符串引用时(例如在cli或pre/post hook中),嵌套名称空间中的任务使用点分隔语法,例如 docs.build .

在本节中,我们将展示如何使用此api构建名称空间是灵活的,但也允许使用最小的样板文件遵循python包布局。

出发

一个未命名 Collection 始终是命名空间根;在隐式基本情况下,invoke从任务模块中的任务为您创建一个命名空间根。创建自己的,命名为 namespacens ,以设置显式命名空间(即跳过默认的“拉入所有任务对象”行为):

from invoke import Collection

ns = Collection()
# or: namespace = Collection()

添加任务 Collection.add_task . add_task 可以采取 Task 对象,例如 task 装饰师:

from invoke import Collection, task

@task
def release(c):
    c.run("python setup.py sdist register upload")

ns = Collection()
ns.add_task(release)

我们的可用任务列表如下:

$ invoke --list
Available tasks:

    release

命名任务

默认情况下,任务的函数名用作其命名空间标识符,但可以通过提供 name 其中一个的参数 @task (即在定义时间)或 Collection.add_task (即在装订/连接时)。

例如,假设您的任务模块中有一个变量名冲突——也许您希望公开 dir 任务,它隐藏了一个python内置。命名函数本身 dir 是个坏主意,但是你可以把函数命名为 dir_ 然后告诉我 @task “真名”:

@task(name='dir')
def dir_(c):
    # ...

另一方面,您可能已经获得了一个与您命名空间中的名称不匹配的任务对象,并且可以在附件时间重命名它。也许我们想重新命名 release 要调用的任务 deploy 取而代之的是:

ns = Collection()
ns.add_task(release, name='deploy')

结果:

$ invoke --list
Available tasks:

    deploy

备注

这个 name Kwarg是 add_task 因此,那些匆匆忙忙的人可以把它说成:

ns.add_task(release, 'deploy')

别名

任务可能有其他名称或别名,如 aliases 关键字参数;这些参数被附加到而不是替换任何隐式或显式 name 价值:

ns.add_task(release, aliases=('deploy', 'pypi'))

结果,同一任务有三个名称:

$ invoke --list
Available tasks:

    release
    deploy
    pypi

备注

便利装潢师 @task 是另一种设置别名的方法(例如 @task(aliases=('foo', 'bar')) ,对于确保给定任务无论如何添加到命名空间中都始终设置了某些别名非常有用。

破折号与下划线

在函数作为任务的常见情况下,您经常会发现自己正在编写包含下划线的任务名称:

@task
def my_awesome_task(c):
    print("Awesome!")

与处理任务参数以将其下划线转换为破折号的方式类似(因为这是常见的命令行约定),默认情况下,任务或集合名称中的所有下划线都被解释为破折号:

$ inv --list
Available tasks:

  my-awesome-task

$ inv my-awesome-task
Awesome!

如果您希望保留下划线,则可以将配置更新为设置。 tasks.auto_dash_namesFalse 在一个非运行时 config files (系统、用户或项目。)例如,在 ~/.invoke.yml ::

tasks:
    auto_dash_names: false

备注

为了避免混淆,这个设置在本质上是“独占的”任务名称的下划线版本 无效的 在cli上,除非 auto_dash_names 已禁用。(但是,在python的纯函数级别,它们必须继续被下划线引用,因为虚线名称不是有效的python语法!)

嵌套集合

命名空间的关键是要有子命名空间;要在invoke中执行此操作,请创建其他 Collection 实例并通过 Collection.add_collection . 例如,假设我们有两个文档任务:

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

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

我们可以把它们捆绑成一个新的命名集合,例如:

docs = Collection('docs')
docs.add_task(build_docs, 'build')
docs.add_task(clean_docs, 'clean')

然后在根命名空间下添加此新集合 add_collection ::

ns.add_collection(docs)

结果(现在假设 ns 目前只包含原始的 release 任务):

$ invoke --list
Available tasks:

    release
    docs.build
    docs.clean

与任务一样,集合可以显式地绑定到其父级,其名称与最初通过 name 夸格 add_task ,第二个常规参数)::

ns.add_collection(docs, 'sphinx')

结果:

$ invoke --list
Available tasks:

    release
    sphinx.build
    sphinx.clean

将模块作为集合导入

调用自身的一个简单策略是使用 Collection.from_module --作为替代方法的类方法 Collection 以python模块对象作为其第一个参数的构造函数。

对该方法给出的模块进行扫描 Task 实例,添加到新的 Collection . 默认情况下,此集合的名称取自模块名称( __name__ 属性),但也可以显式提供。

备注

与默认任务模块一样,可以通过声明一个 nsnamespace Collection 在加载的模块中处于顶层的对象。

例如,让我们将前面的单个文件示例重新组织为一个包含多个子模块的python包。第一, tasks/release.py ::

from invoke import task

@task
def release(c):
    c.run("python setup.py sdist register upload")

tasks/docs.py ::

from invoke import task

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

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

把他们绑在一起是 tasks/__init__.py ::

from invoke import Collection

import release, docs

ns = Collection()
ns.add_collection(Collection.from_module(release))
ns.add_collection(Collection.from_module(docs))

这种形式的api在实践中有点笨拙。谢天谢地有一条捷径: add_collection 将注意何时将模块对象作为其第一个参数并调用 Collection.from_module 对于你的内心:

ns = Collection()
ns.add_collection(release)
ns.add_collection(docs)

不管怎样,结果是:

$ invoke --list
Available tasks:

    release.release
    docs.build
    docs.clean

默认任务

任务可以声明为要为其所属集合调用的默认任务,例如 default=True@task (或) Collection.add_task )当名称空间中有一组相关任务,但其中一个任务最常用,并且很好地映射到整个名称空间时,这非常有用。

例如,在我们到目前为止一直在尝试的文档子模块中, build 默认情况下,任务是有意义的,所以我们可以说 invoke docs 作为通往 invoke docs.build . 这很容易做到:

@task(default=True)
def build(c):
    # ...

当导入到根命名空间(如上所示)时,这会改变 --list ,强调 docs.build 可以被调用为 docs 如果需要:

$ invoke --list
Available tasks:

    release.release
    docs.build (docs)
    docs.clean

默认子集合

从1.5版开始,此功能也扩展到子集合:在将子集合添加到其父集合时,可以将其指定为默认值,并且该子集合自己的默认任务(或子集合!)将作为父级的默认设置调用。

举个例子可能会让这一点更清楚。下面是一个很小的内联任务树,它有两个子集合,每个子集合都有自己的默认任务::

from invoke import Collection, task

@task(default=True)
def build_all(c):
    print("build ALL THE THINGS!")

@task
def build_wheel(c):
    print("Just the wheel")

build = Collection(all=build_all, wheel=build_wheel)

@task(default=True)
def build_docs(c):
    print("Code without docs is no code at all")

docs = Collection(build_docs)

然后,我们将这些绑定到一个顶级集合中,将 build 子集合作为总体默认值::

ns = Collection()
ns.add_collection(build, default=True)
ns.add_collection(docs)

结果是, build.all 成为绝对默认任务::

$ invoke
build ALL THE THINGS!

混搭

您不仅限于上面所示的特定策略--现在您知道了 add_taskadd_collection ,使用最适合您需要的方法。

例如,假设您希望将内容组织成子模块,但希望“提升” release.release 为了方便起见回到顶层。仅仅因为它存储在一个模块中并不意味着我们必须使用 add_collection --我们可以导入任务本身并使用 add_task 直接:

from invoke import Collection

import docs
from release import release

ns = Collection()
ns.add_collection(docs)
ns.add_task(release)

结果:

$ invoke --list
Available tasks:

    release
    docs.build
    docs.clean

更多快捷方式

最后,你甚至可以跳过。 add_collectionadd_task 如果你的需求足够简单-- Collection 的构造函数将接受未知参数,并根据其值构建命名空间:

from invoke import Collection

import docs, release

ns = Collection(release.release, docs)

注意我们如何给两个task对象 (release.release )以及包含任务的模块 (docs )结果与上述相同:

$ invoke --list
Available tasks:

    release
    docs.build
    docs.clean

如果作为关键字参数给出,则关键字的作用类似于 nameadd_* 方法。当然,两者也可以混合在一起:

ns = Collection(docs, deploy=release.release)

结果:

$ invoke --list
Available tasks:

    deploy
    docs.build
    docs.clean

备注

你仍然可以说出这些名字 Collection 如果需要的话,带有引导字符串参数的对象,在构建子集合时可以很方便。