构造命名空间¶
这个 base case 加载任务的单个模块最初工作得很好,但高级用户通常需要更多的组织,例如将任务分成嵌套名称空间树。
这个 Collection
类提供用于组织任务的API(和 their configuration )变成树状结构。当被字符串引用时(例如在cli或pre/post hook中),嵌套名称空间中的任务使用点分隔语法,例如 docs.build
.
在本节中,我们将展示如何使用此api构建名称空间是灵活的,但也允许使用最小的样板文件遵循python包布局。
出发¶
一个未命名 Collection
始终是命名空间根;在隐式基本情况下,invoke从任务模块中的任务为您创建一个命名空间根。创建自己的,命名为 namespace
或 ns
,以设置显式命名空间(即跳过默认的“拉入所有任务对象”行为):
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
别名¶
任务可能有其他名称或别名,如 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_names
到 False
在一个非运行时 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__
属性),但也可以显式提供。
备注
与默认任务模块一样,可以通过声明一个 ns
或 namespace
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_task
和 add_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_collection
和 add_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
如果作为关键字参数给出,则关键字的作用类似于 name
在 add_*
方法。当然,两者也可以混合在一起:
ns = Collection(docs, deploy=release.release)
结果:
$ invoke --list
Available tasks:
deploy
docs.build
docs.clean
备注
你仍然可以说出这些名字 Collection
如果需要的话,带有引导字符串参数的对象,在构建子集合时可以很方便。