0

我正在尝试构建一个 Click MultiCommand 组,以便能够从命令行和配置文件中提供选项、子命令和子命令选项的组合。

已经有一些从配置文件中驱动 Click 的示例:
https://github.com/click-contrib/click-configfile
https://github.com/psf/black/blob/37861b4ce264f16754f4459d19522a05844daf9f/src/black/__init__ .py#L99
Python Click - 从配置文件中提供参数和选项

这些对我来说很有意义,我可以按照下面的示例从 JSON 文件中实现读取选项(基于 Black 的执行方式)。这些示例实现的共同点是它们通过急切的回调函数覆盖默认选项值来工作(因此这些默认值是在解析命令行的其余部分之前设置的)。因此,如果同时提供了配置文件和命令行选项,则将使用命令行选项。这很好,但要真正让 Click 调用任何子命令,它们必须从命令行提供(但可能有纯粹的配置驱动选项)。

我还想指定从传入的配置文件(以及选项)中运行哪些子命令,但无法确定执行此操作的最佳方法。我曾尝试在修改args属性的同时修改 Click 上下文属性default_map,但这只是在解析命令行参数(未传递子命令)时被 Clicks 默认行为覆盖并args返回到空列表。

# commandline.py
import click
import json


def read_config(ctx, param, value):
    if value is None:
        return

    with open(value) as fh:
        config = json.load(fh)

    new_defaults = {}
    if ctx.default_map:
        new_defaults.update(ctx.default_map)

    config = {key: val if isinstance(val, (list, dict)) else str(val) for key, val in config.items()}
    new_defaults.update(config)

    ctx.default_map = new_defaults

    return value


@click.option(
    "--opt_a",
)
@click.option(
    "--config",
    is_eager=True,
    callback=read_config,
)
@click.group(
    chain=True,
    no_args_is_help=True,
    invoke_without_command=True,
)
@click.pass_context
def cli(ctx, opt_a, config):
    click.echo(f"opt_a was '{opt_a}'")
    click.echo(f"config was '{config}'")


@click.option(
    "--sub_opt_a",
)
@cli.command("sub_cmd_a")
def sub_cmd_a(sub_opt_a):
    click.echo(f"sub_cmd_a called with '{sub_opt_a}'")


@click.option(
    "--sub_opt_b",
)
@cli.command("sub_cmd_b")
def sub_cmd_b(sub_opt_b):
    click.echo(f"sub_cmd_b called with '{sub_opt_b}'")


if __name__ == "__main__":
    cli()

我的目标是使这些调用等效:
完全指定命令行
python commandline.py --opt_a "Test Opt A" sub_cmd_a --sub_opt_a "Test SubOpt A"

指定的完整配置文件
python commandline.py --config "config_file.json"
config_file.json 在哪里

{
    "opt_a": "Test Opt A",
    "sub_cmd_a": {
        "sub_opt_a": "Test SubOpt A"
    }
}

感谢任何关于能够将子命令参数注入 Click 的指针。谢谢。

4

0 回答 0