配置¶
简介¶
invoke 提供了多方面的配置机制,通过配置文件、环境变量、任务名称空间 和 CLI 标志的层次结构,允许配置核心行为和任务的行为。
配置查找、加载、解析和合并的最终结果是 Config
对象,它的行为类似于(嵌套的)Python 字典。当它运行时调用引用此对象(确定方法的默认行为,如 Context.run
),并将它作为 Context.config
暴露给用户的任务或作为 Context
上的快捷属性访问。
配置的层次结构¶
简而言之,配置值相互覆盖的顺序如下:
通过配置可以控制 内部默认行为值。详细信息请参见 Default configuration values。
Collection-driven configurations defined in tasks modules via
Collection.configure
. (See 基于 Collection 的配置 below for details.)Sub-collections’ configurations get merged into the top level collection and the final result forms the basis of the overall configuration setup.
System-level configuration file stored in
/etc/
, such as/etc/invoke.yaml
. (See Configuration files for details on this and the other config-file entries.)User-level configuration file found in the running user’s home directory, e.g.
~/.invoke.yaml
.Project-level configuration file living next to your top level
tasks.py
. For example, if your run of Invoke loads/home/user/myproject/tasks.py
(see our docs on the load process), this might be/home/user/myproject/invoke.yaml
.Environment variables found in the invoking shell environment.
These aren’t as strongly hierarchical as the rest, nor is the shell environment namespace owned wholly by Invoke, so we must rely on slightly verbose prefixing instead - see Environment variables for details.
Runtime configuration file whose path is given to
-f
, e.g.inv -f /random/path/to/config_file.yaml
. This path may also be set via theINVOKE_RUNTIME_CONFIG
env var.Command-line flags for certain core settings, such as
-e
.Modifications made by user code at runtime.
Default configuration values¶
Below is a list of all the configuration values and/or section Invoke itself
uses to control behaviors such as Context.run
’s echo
and pty
flags, task deduplication, and so forth.
备注
The storage location for these values is inside the Config
class,
specifically as the return value of Config.global_defaults
; see its API
docs for more details.
For convenience, we refer to nested setting names with a dotted syntax, so e.g.
foo.bar
refers to what would be (in a Python config context) {'foo':
{'bar': <value here>}}
. Typically, these can be read or set on Config
and
Context
objects using attribute syntax, which looks nearly identical:
c.foo.bar
.
The
tasks
config tree holds settings relating to task execution.tasks.dedupe
controls Task deduplication and defaults toTrue
. It can also be overridden at runtime via--no-dedupe
.tasks.auto_dash_names
controls whether task and collection names have underscores turned to dashes on the CLI. Default:True
. See also 破折号和下划线.tasks.collection_name
controls the Python import name sought out by collection discovery, and defaults to"tasks"
.tasks.executor_class
allows users to override the class instantiated and used for task execution.Must be a fully-qualified dotted path of the form
module(.submodule...).class
, where all but.class
will be handed toimportlib.import_module
, andclass
is expected to be an attribute on that resulting module object.Defaults to
None
, meaning to use the runningProgram
object’sexecutor_class
attribute.警告
Take care if using this setting in tandem with custom program binaries, since custom programs may specify their own default executor class (which your use of this setting will override!) and assume certain behaviors stemming from that.
tasks.search_root
allows overriding the default collection discovery root search location. It defaults toNone
, which indicates to use the executing process’ current working directory.
The
run
tree controls the behavior ofRunner.run
. Each member of this tree (such asrun.echo
orrun.pty
) maps directly to aRunner.run
keyword argument of the same name; see that method’s docstring for details on what these settings do & what their default values are.The
runners
tree controls _which_ runner classes map to which execution contexts; if you’re using Invoke by itself, this will only tend to have a single member,runners.local
. Client libraries may extend it with additional key/value pairs, such asrunners.remote
.The
sudo
tree controls the behavior ofContext.sudo
:sudo.password
controls the autoresponse password submitted to sudo’s password prompt. Default:None
.警告
While it’s possible to store this setting, like any other, in configuration files – doing so is inherently insecure. We highly recommend filling this config value in at runtime from a secrets management system of some kind.
sudo.prompt
holds the sudo password prompt text, which is both supplied tosudo -p
, and searched for when performing auto-response. Default:[sudo] password:
.
A top level config setting,
debug
, controls whether debug-level output is logged; it defaults toFalse
.debug
can be toggled via the-d
CLI flag, which enables debugging after CLI parsing runs. It can also be toggled via theINVOKE_DEBUG
environment variable which - unlike regular env vars - is honored from the start of execution and is thus useful for troubleshooting parsing and/or config loading.A small config tree,
timeouts
, holds various kinds of timeout controls. At present, for Invoke, this only holds acommand
subkey, which controls subprocess execution timeouts.Client code often adds more to this tree, and Invoke itself may add more in the future as well.
Configuration files¶
Loading¶
For each configuration file location mentioned in the previous section, we
search for files ending in .yaml
, .yml
, .json
or .py
(in that
order!), load the first one we find, and ignore any others that might exist.
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.
Format¶
Invoke’s configuration allows arbitrary nesting, and thus so do our config file
formats. All three of the below examples result in a configuration equivalent
to {'debug': True, 'run': {'echo': True}}
:
YAML
debug: true run: echo: true
JSON
{ "debug": true, "run": { "echo": true } }
Python:
debug = True run = { "echo": True }
For further details, see these languages’ own documentation.
Environment variables¶
Environment variables are a bit different from other configuration-setting methods, since they don’t provide a clean way to nest configuration keys, and are also implicitly shared amongst the entire system’s installed application base.
In addition, due to implementation concerns, env vars must be pre-determined by
the levels below them in the config hierarchy (in other words - env vars may
only be used to override existing config values). If you need Invoke to
understand a FOOBAR
environment variable, you must first declare a
foobar
setting in a configuration file or in your task collections.
Basic rules¶
To mitigate the shell namespace problem, we simply prefix all our env vars with
INVOKE_
.
Nesting is performed via underscore separation, so a setting that looks like
e.g. {'run': {'echo': True}}
at the Python level becomes
INVOKE_RUN_ECHO=1
in a typical shell. See Nesting vs underscored names below for
more on this.
Type casting¶
Since env vars can only be used to override existing settings, the previous value of a given setting is used as a guide in casting the strings we get back from the shell:
If the current value is a string or Unicode object, it is replaced with the value from the environment, with no casting whatsoever;
Depending on interpreter and environment, this means that a setting defaulting to a non-Unicode string type (eg a
str
on Python 2) may end up replaced with a Unicode string, or vice versa. This is intentional as it prevents users from accidentally limiting themselves to non-Unicode strings.
If the current value is
None
, it too is replaced with the string from the environment;Booleans are set as follows:
0
and the empty value/string (e.g.SETTING=
, orunset SETTING
, or etc) evaluate toFalse
, and any other value evaluates toTrue
.Lists and tuples are currently unsupported and will raise an exception;
In the future we may implement convenience transformations, such as splitting on commas to form a list; however since users can always perform such operations themselves, it may not be a high priority.
All other types - integers, longs, floats, etc - are simply used as constructors for the incoming value.
For example, a
foobar
setting whose default value is the integer1
will run all env var inputs throughint
, and thusFOOBAR=5
will result in the Python value5
, not"5"
.
Nesting vs underscored names¶
Since environment variable keys are single strings, we must use some form of
string parsing to allow access to nested configuration settings. As mentioned
above, in basic use cases this just means using an underscore character:
{'run': {'echo': True}}
becomes INVOKE_RUN_ECHO=1
.
However, ambiguity is introduced when the settings names themselves contain
underscores: is INVOKE_FOO_BAR=baz
equivalent to {'foo': {'bar':
'baz'}}
, or to {'foo_bar': 'baz'}
? Thankfully, because env vars can only
be used to modify settings declared at the Python level or in config files, we
look at the current state of the config to determine the answer.
There is still a corner case where both possible interpretations exist as
valid config paths (e.g. {'foo': {'bar': 'default'}, 'foo_bar':
'otherdefault'}
). In this situation, we honor the Zen of Python
and refuse to guess; an error is raised instead, counseling users to modify
their configuration layout or avoid using env vars for the setting in question.
基于 Collection
的配置¶
Collection
对象可以包含配置映射,通过 Collection.configure
来设置,并且(按照 层次结构)这通常形成了系统中最低级别的配置。
当集合是 嵌套 时,配置默认是 ‘向下’ 合并:当冲突出现时,相对于靠近被调用任务的内部命名空间,靠近根的外部命名空间将获胜。
备注
这里的“内部”任务是指从根目录到被调用任务所在目录的路径上的任务。’Sibling’ 子集合将被忽略。
举个简单的例子
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 docs 的小模块可以这样开始
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))
这个任务模块只适用于一组用户,但如果我们希望允许重用呢?有些人可能希望将此模块与另一个默认目标一起使用。使用配置数据(通过上下文参数提供)来配置这些设置通常是更好的解决方案。1
任务 collection 配置¶
配置 setting
和 getting
允许将其他的‘硬编码’默认值移动到配置结构中,下游用户可以自由地重新定义。让我们把这个应用到我们的例子中。首先,我们添加一个显式的命名空间对象
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
在这个例子中,那种 local-to-project 的配置文件是最有意义的,但是不要忘记 config hierarchy 提供了额外的配置方法,根据你的需要可能是合适的。
脚注
- 1
复制和修改文件会破坏代码重用;重写模块级的
default_path
变量将不能很好地处理并发性;用不同的默认参数包装任务可以工作,但很脆弱,会增加样板文件。