配置

介绍

invoke提供了一个多方面的配置机制,允许您通过配置文件、环境变量的层次结构来配置核心行为和任务的行为, task namespaces 和cli标志。

配置查找、加载、解析和合并的最终结果是 Config 对象,其行为类似于(嵌套的)python字典。invoke在运行时引用此对象(确定以下方法的默认行为 Context.run )并将其作为 Context.config 或作为快捷属性访问 Context 本身。

配置层次结构

简言之,配置值相互覆盖的顺序如下:

  1. 内部默认值 对于通过配置可以控制的行为。见 默认配置值 有关详细信息。

  2. Collection-driven configurations 通过在任务模块中定义 Collection.configure . (见 Collection -基于配置 详情见下文。)

    • 子集合的配置被合并到顶级集合中,最终结果构成整个配置设置的基础。

  3. System-level configuration file 存储在 /etc/ ,如 /etc/invoke.yaml . (见 配置文件 有关此项和其他配置文件项的详细信息。)

  4. User-level configuration file 在正在运行的用户的主目录中找到,例如。 ~/.invoke.yaml .

  5. Project-level configuration file 生活在你的顶层 tasks.py . 例如,如果运行调用加载 /home/user/myproject/tasks.py (参见我们的文档 the load process ),这可能是 /home/user/myproject/invoke.yaml .

  6. 环境变量 在调用shell环境中找到。

    • 这些名称空间的层次性不如其他名称空间强,shell环境名称空间也不是完全由invoke拥有,因此我们必须依赖稍微冗长的前缀-请参见 环境变量 有关详细信息。

  7. 运行时配置文件 其路径被赋予 -f ,例如 inv -f /random/path/to/config_file.yaml . 此路径也可以通过 INVOKE_RUNTIME_CONFIG EV V.

  8. Command-line flags 对于某些核心设置,例如 -e .

  9. 用户代码所做的修改 在运行时。

默认配置值

下面是所有配置值和/或节invoke本身用于控制以下行为的列表 Context.runechopty 标志、任务重复数据消除等。

注解

这些值的存储位置在 Config 类的返回值。 Config.global_defaults ;有关更多详细信息,请参阅其api文档。

为了方便起见,我们使用点式语法来引用嵌套的设置名称,例如 foo.bar 引用将是什么(在python配置上下文中) {{'foo': {{'bar': <value here>}}}} . 通常,可以读取或设置这些。 ConfigContext 使用看起来几乎相同的属性语法的对象: c.foo.bar .

  • 这个 tasks 配置树保存与任务执行相关的设置。

    • tasks.dedupe Controls 任务重复数据消除 默认值为 True . 它也可以在运行时被重写。 --no-dedupe .

    • tasks.auto_dash_names 控制任务和集合名称是否在cli上将下划线转换为破折号。违约: True . 也见 破折号与下划线 .

    • tasks.collection_name 控制由 collection discovery ,默认为 "tasks" .

    • tasks.executor_class 允许用户重写实例化并用于任务执行的类。

      必须是窗体的完全限定的点路径 module(.submodule...).class ,除了 .class 将交给 importlib.import_moduleclass 应该是该结果模块对象的属性。

      默认为 None ,意思是用跑步 Program 对象的 executor_class 属性。

      警告

      如果将此设置与 custom program binaries ,因为自定义程序可以指定自己的默认执行器类(使用此设置将覆盖该类!)并假设某些行为源于此。

    • tasks.search_root 允许重写默认值 collection discovery 根搜索位置。它默认为 None ,它指示使用正在执行的进程的当前工作目录。

  • 这个 run 树控制的行为 Runner.run . 此树的每个成员(例如 run.echorun.pty )直接映射到 Runner.run 同名的关键字参数;有关这些设置的作用及其默认值的详细信息,请参见该方法的docstring。

  • 这个 runners 树控件 _which_ 运行程序类映射到哪个执行上下文;如果您自己使用invoke,则这将只倾向于有一个成员, runners.local . 客户端库可以使用额外的键/值对来扩展它,例如 runners.remote .

  • 这个 sudo 树控制的行为 Context.sudo

    • sudo.password 控制提交到sudo密码提示的自动响应密码。违约: None .

      警告

      虽然可以像其他任何设置一样将此设置存储在 configuration files --这样做本质上是不安全的。我们强烈建议在运行时从某种机密管理系统中填充此配置值。

    • sudo.prompt 保存sudo密码提示文本,这两个文本都提供给 sudo -p ,并在执行时搜索 auto-response . 违约: [sudo] password: .

  • 顶级配置设置, debug ,控制是否记录调试级别输出;它默认为 False .

    debug 可以通过 -d cli标志,用于在运行cli分析之后启用调试。它也可以通过 INVOKE_DEBUG 与常规env vars不同的是,环境变量从执行开始就受到重视,因此对于解析和/或配置加载的故障排除非常有用。

  • 一个小配置树, timeouts ,保存各种超时控件。目前,对于invoke,这只包含 command 子键,用于控制子进程执行超时。

    • 客户机代码通常会向该树添加更多内容,而调用本身也可能在将来添加更多内容。

配置文件

加载

对于上一节中提到的每个配置文件位置,我们搜索以 .yaml.yml.json.py按顺序! ),加载我们找到的第一个,忽略可能存在的任何其他。

For example, if Invoke is run on a system containing both /etc/invoke.yml and /etc/invoke.json, only the YAML file will be loaded. This helps keep things simple, both conceptually and in the implementation.

格式

invoke的配置允许任意嵌套,因此我们的配置文件格式也是如此。下面的三个例子都得到了一个等价于 {{'debug': True, 'run': {{'echo': True}}}}

  • YAML

    debug: true
    run:
        echo: true
    
  • JSON

    {
        "debug": true,
        "run": {
            "echo": true
        }
    }
    
  • Python ::

    debug = True
    run = {
        "echo": True
    }
    

有关详细信息,请参阅这些语言自己的文档。

环境变量

环境变量与其他配置设置方法稍有不同,因为它们不提供嵌套配置键的干净方法,而且还隐式地在整个系统的已安装应用程序库中共享。

此外,出于实现方面的考虑,env vars必须由配置层次结构中它们下面的级别预先确定(换句话说-env vars只能用于重写现有的配置值)。如果您需要调用来理解 FOOBAR 环境变量,必须首先声明 foobar 在配置文件或任务集合中设置。

基本规则

为了缓解shell名称空间问题,我们只需在所有env vars前面加上 INVOKE_ .

嵌套是通过下划线分隔来执行的,因此一个看起来像 {{'run': {{'echo': True}}}} 在python级别变成 INVOKE_RUN_ECHO=1 在一个典型的贝壳里。见 嵌套与带下划线的名称 下面是关于这个的更多信息。

型铸造

由于EnvVAR只能用于重写现有设置,所以给定的设置的前一个值被用来作为我们从shell中返回字符串的向导。

  • 如果当前值是字符串或Unicode对象,则将其替换为环境中的值,而不进行任何转换;

    • 根据解释器和环境的不同,这意味着设置默认为非Unicode字符串类型(例如 str 在python 2)中,可能最终被unicode字符串替换,反之亦然。这是有意的,因为它可以防止用户意外地将自己限制在非unicode字符串中。

  • 如果当前值是 None ,它也被替换为环境中的字符串;

  • 布尔值设置如下: 0 以及空值/字符串(例如 SETTING=unset SETTING ,或等)评估为 False ,任何其他值的计算结果为 True .

  • 列表和元组当前不受支持,将引发异常;

    • 在将来,我们可以实现便利的转换,例如在逗号上拆分以形成列表;然而,由于用户总是能够自己执行这样的操作,所以它可能不是一个高优先级。

  • 所有其他类型(整数、长整型、浮点数等)都只是用作传入值的构造函数。

    • 例如,a foobar 设置其默认值为整数 1 将通过 int 因此 FOOBAR=5 将产生python值 5 不是 "5" .

嵌套与带下划线的名称

由于环境变量键是单个字符串,我们必须使用某种形式的字符串解析来允许访问嵌套的配置设置。如前所述,在基本用例中,这仅仅意味着使用下划线字符: {{'run': {{'echo': True}}}} 变成 INVOKE_RUN_ECHO=1 .

但是,当设置名称本身包含下划线时,会引入歧义:is INVOKE_FOO_BAR=baz 相当于 {{'foo': {{'bar': 'baz'}}}} ,或 {{'foo_bar': 'baz'}} ?谢天谢地,因为EnvVAR只能用于修改Python级别或配置文件中声明的设置,所以我们查看CONFIG的当前状态来确定答案。

还有一个角落的案子 both 可能的解释以有效的配置路径存在(例如 {{'foo': {{'bar': 'default'}}, 'foo_bar': 'otherdefault'}} )在这种情况下,我们尊重 Zen of Python 拒绝猜测;相反会出现一个错误,建议用户修改其配置布局或避免使用env vars进行设置。

Collection -基于配置

Collection 对象可以包含配置映射,通过 Collection.configure ,和(根据 the hierarchy )这通常形成系统中的最低配置级别。

当集合是 nested ,默认情况下,配置是“向下”合并的:当发生冲突时,更接近根的外部命名空间将获胜,而更接近被调用任务的内部命名空间将获胜。

注解

这里的“内部”任务是指从根目录到包含被调用任务的目录路径上的任务。忽略同级子集合。

这意味着什么的一个简单例子:

from invoke import Collection, task

# This task & collection could just as easily come from
# another module somewhere.
@task
def mytask(c):
    print(c['conflicted'])
inner = Collection('inner', mytask)
inner.configure({'conflicted': 'default value'})

# Our project's root namespace.
ns = Collection(inner)
ns.configure({'conflicted': 'override value'})

呼叫的结果 inner.mytask ::

$ inv inner.mytask
override value

实际配置使用示例

前几节中有一些小示例;本节提供了一组更逼真的示例,展示了配置系统的工作原理。

安装程序

我们将从硬编码其值的半现实任务开始,并逐步使用各种配置机制。建筑用的小模块 Sphinx 文档可能是这样开始的:

from invoke import task

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

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

那么也许你重构了构建目标:

target = "docs/_build"

@task
def clean(c):
    c.run("rm -rf {}".format(target))

@task
def build(c):
    c.run("sphinx-build docs {}".format(target))

我们还可以允许运行时参数化:

default_target = "docs/_build"

@task
def clean(c, target=default_target):
    c.run("rm -rf {}".format(target))

@task
def build(c, target=default_target):
    c.run("sphinx-build docs {}".format(target))

这个任务模块只适用于一组用户,但是如果我们想允许重用呢?可能有人想将此模块与其他默认目标一起使用。使用配置数据(通过上下文arg提供)配置这些设置通常是更好的解决方案 1.

通过任务集合配置

配置 settinggetting api允许将其他“硬编码”默认值移动到配置结构中,下游用户可以自由重新定义该结构。让我们把这个应用到我们的例子中。首先,我们添加一个显式命名空间对象:

from invoke import Collection, task

default_target = "docs/_build"

@task
def clean(c, target=default_target):
    c.run("rm -rf {}".format(target))

@task
def build(c, target=default_target):
    c.run("sphinx-build docs {}".format(target))

ns = Collection(clean, build)

然后,我们可以将默认的构建目标值移到集合的默认配置中,并通过上下文引用它。此时,我们还将Kwarg默认值更改为 None 因此,我们可以确定是否给出了运行时值。结果:

@task
def clean(c, target=None):
    if target is None:
        target = c.sphinx.target
    c.run("rm -rf {}".format(target))

@task
def build(c, target=None):
    if target is None:
        target = c.sphinx.target
    c.run("sphinx-build docs {}".format(target))

ns = Collection(clean, build)
ns.configure({'sphinx': {'target': "docs/_build"}})

结果并没有比我们开始时复杂得多,接下来我们将看到,用户以各种方式覆盖您的默认值已经很简单了。

配置重写

当然,最低级别的重写只是修改本地 Collection 已导入分布式模块的树。例如,如果上述模块作为 myproject.docs 某人可以定义一个 tasks.py 就这样:

from invoke import Collection, task
from myproject import docs

@task
def mylocaltask(c):
    # Some local stuff goes here
    pass

# Add 'docs' to our local root namespace, plus our own task
ns = Collection(mylocaltask, docs)

然后他们可以把这个添加到底部:

# Our docs live in 'built_docs', not 'docs/_build'
ns.configure({'sphinx': {'target': "built_docs"}})

现在我们有一个 docs 其生成目标默认为的子命名空间 built_docs 而不是 docs/_build . 运行时用户仍然可以通过标志覆盖这个(例如)。 inv docs.build --target='some/other/dir' )和以前一样。

如果您更喜欢配置文件,而不是在python中调整命名空间树,那么这也同样有效;与其将上面的行添加到前面的代码片段中,不如将其放到 tasks.py 已命名 invoke.yaml ::

sphinx:
    target: built_docs

对于本例,这种本地到项目的conf文件最有意义,但不要忘记 config hierarchy 提供其他配置方法,这些方法可能适合您的需要。

脚注

1

复制和修改文件中断代码重用;重写模块级 default_path 变量不能很好地处理并发;用不同的默认参数包装任务可以工作,但是很脆弱,并且添加了样板。