使用invoke作为库

虽然我们的大多数文档都涉及面向任务管理和命令执行的用户/ CLI用例,但是调用是为其组成部分设计的,可以由高级用户独立使用,或者是箱外的或最少的额外工作。cli解析、子流程命令执行、任务组织等都是作为广泛分离的关注点编写的。

本文档概述了已知可用的用例(因为下游工具,如 Fabric 已经在利用它们)。

将invoke的cli模块重用为不同的二进制文件

一个主要的用例是在幕后使用invoke分发您自己的程序,绑定到不同的二进制名称,并且通常设置一个特定的任务 namespace 作为默认值。(这与 argparse 在某些情况下,还需要删除、替换和/或添加核心cli标志。

正在设置

假设要分发名为 tester 提供两个子命令, unitintegration ,这样用户就可以 pip install tester 并且可以访问如下命令 tester unittester integrationtester integration --fail-fast .

首先,与任何提供cli“二进制文件”的不同python包一样,您将通知 setup.py 你的入口点:

setup(
    name='tester',
    version='0.1.0',
    packages=['tester'],
    install_requires=['invoke'],
    entry_points={
        'console_scripts': ['tester = tester.main:program.run']
    }
)

备注

这只是一个示例代码片段,并不是完全有效的 setup.py ;如果您不知道Python打包是如何工作的,一个很好的起点是 the Python Packaging User's Guide

这里没有特定的调用-这是告诉python安装 tester 执行 run A方法 program 在模块内定义的对象 tester.main .

创建一个 Program

在我们 tester/main.py ,我们开始导入invoke的公共cli功能:

from invoke import Program

然后我们定义 program 我们引用的对象 setup.py ,这很简单 Program 为了完成这项繁重的工作,请先给出我们的版本号:

program = Program(version='0.1.0')

此时,正在安装 tester 将提供与invoke相同的功能 built-in CLI tool ,命名除外 tester 并公开自己的版本号:

$ tester --version
Tester 0.1.0
$ tester --help
Usage: tester [--core-opts] task1 [--task1-opts] ... taskN [--taskN-opts]

Core options:
    ... core Invoke options here ...

$ tester --list
Can't find any collection named 'tasks'!

这对我们还没有多大好处-没有任何子命令(我们的用户也不关心任意的“任务”,所以invoke自己的默认值 --help--list 输出不太合适)。

指定子命令

为了 tester 揭露 unitintegration 子命令,我们需要在常规的invoke tasks模块中定义它们,或者 namespace . 在我们的例子中,我们将创建 tester/tasks.py (但你会看到,这也是任意的,可以是你喜欢的任何东西):

from invoke import task

@task
def unit(c):
    print("Running unit tests!")

@task
def integration(c):
    print("Running integration tests!")

如上所述 构造命名空间 您可以根据需要安排这个模块——为了简洁起见,上面的代码段使用隐式命名空间。

备注

重要的是要认识到这些“子命令”没有什么特别之处——您可以使用vanilla invoke(例如via)轻松地运行它们。 invoke --collection=tester.tasks --list .

现在有用的是:告诉我们的习俗 Program 此任务命名空间应用作 tester ,通过 namespace 克瓦格:

from invoke import Collection, Program
from tester import tasks

program = Program(namespace=Collection.from_module(tasks), version='0.1.0')

结果呢?

$ tester --version
Tester 0.1.0
$ tester --help
Usage: tester [--core-opts] <subcommand> [--subcommand-opts] ...

Core options:
  ... core options here, minus task-related ones ...

Subcommands:
  unit
  integration

$ tester --list
No idea what '--list' is!
$ tester unit
Running unit tests!

注意“usage”行是如何更改的(以指定“subcommands”而不是“tasks”);特定子命令的列表现在作为 --help ;和 --list 已从选项中删除。

您可以启用 tab-completion 对于不同的二进制和子命令。

修改核心分析器参数

这个用例的一个常见需求是调整核心解析器参数。 Program 轻松:默认核心 Arguments 由返回 Program.core_args . 将此方法的返回值扩展为 super 你就完了:

# Presumably, this is your setup.py-designated CLI module...

from invoke import Program, Argument

class MyProgram(Program):
    def core_args(self):
        core_args = super().core_args()
        extra_args = [
            Argument(names=('foo', 'f'), help="Foo the bars"),
            # ...
        ]
        return core_args + extra_args

program = MyProgram()

警告

我们不推荐 省略 任何现有的核心参数;许多基本功能都依赖于它们的存在,即使保留默认值也是如此。

自定义配置系统的默认值

除了上一节中面向cli的内容之外,在重新分发invoke codebase(cli或no cli)时经常需要更新的另一个功能领域是配置。这里通常有两个问题:

  • 配置文件名和env var前缀-如果您希望用户使用配置系统,那么这一点至关重要;

  • 默认配置值-不太重要(大多数默认值没有标记任何调用特定的内容),但有时仍然是可取的。

备注

这两个都涉及子类化 Config (如果使用cli机器,则通知 Program 使用该子类而不是默认子类。)

更改文件名和/或env var前缀

默认情况下,invoke的配置系统会查找如下文件 /etc/invoke.yaml~/.invoke.json ,等等,如果您正在分发名为其他名称的客户端代码,如 Tester 例如,您可能希望加载配置系统 /etc/tester.json$CWD/tester.py .

类似地,环境变量config level查找如下环境变量 INVOKE_RUN_ECHO ;你可能更喜欢 TESTER_RUN_ECHO .

有几个 Config 控制这些值的属性:

  • prefix :一个通用的catchall前缀,直接用作文件前缀,并通过所有大写字母用作env var前缀;

  • file_prefix :仅重写文件名前缀-否则,默认值为 prefix

  • env_prefix :对于只重写env var前缀-正如您可能已经猜到的,它也默认为 prefix .

继续我们的“测试员”示例,您将执行以下操作:

from invoke import Config

class TesterConfig(Config):
    prefix = 'tester'

或者,寻求 tester.yaml 和以前一样,但是 TEST_RUN_ECHO 而不是 TESTER_RUN_ECHO ::

class TesterConfig(Config):
    prefix = 'tester'
    env_prefix = 'TEST'

修改默认配置值

默认配置值很简单-它们只是staticmethod的返回值 Config.global_defaults ,所以重写它并返回您喜欢的任何内容-理想情况下是基于超类的值,因为系统的其他部分假设存在许多默认值。(helper函数 invoke.config.merge_dicts 在这里很有用。

例如,假设您希望tester在您的代码基调用时总是在默认情况下回显shell命令 Context.run ::

from invoke import Program
from invoke.config import Config, merge_dicts

class TesterConfig(Config):
    @staticmethod
    def global_defaults():
        their_defaults = Config.global_defaults()
        my_defaults = {
            'run': {
                'echo': True,
            },
        }
        return merge_dicts(their_defaults, my_defaults)

program = Program(config_class=TesterConfig, version='0.1.0')

作为参考,invoke自己的基本默认值(您可以说,…默认值)记录在 默认配置值 .