如何向子命令添加常用选项,这些选项可以*在*子命令的名称之后



使用 CLI 库click我有一个应用程序脚本app.py,其中包含两个子命令readwrite

@click.group()
@click.pass_context
def cli(ctx):
pass
@cli.command()
@click.pass_context
def read(ctx):
print("read")
@cli.command()
@click.pass_context
def write(ctx):
print("write")

我想声明一个通用选项--format。我知道我可以通过以下方式将其作为选项添加到命令

@click.group()
@click.option('--format', default='json')
@click.pass_context
def cli(ctx, format):
ctx.obj['format'] = format

但是我无法在命令之后给出选项,这在我的用例中要自然得多。我希望能够在外壳中发出:

app.py read --format XXX 

但是通过概述的设置,我得到的消息Error: no such option: --format.脚本仅接受命令之前的选项。

所以我的问题是:如何为两个子命令添加一个通用选项,以便它像为每个子命令提供了该选项一样工作?

AFAICT,这在Click上是不可能的。文档指出:

单击 严格分隔命令和子命令之间的参数。 这意味着特定命令的选项和参数 必须在命令名称本身之后指定,但在任何命令名称之前 其他命令名称。

一种可能的解决方法是编写common_options装饰器。下面的示例使用的事实click.option是一个返回期望串联应用的装饰器函数的函数。IOW,如下:

@click.option("-a")
@click.option("-b")
def hello(a, b):
pass

等效于以下内容:

def hello(a, b):
pass
hello = click.option("-a")(click.option("-b")(hello))

缺点是您需要在所有子命令上设置公共参数。这可以通过**kwargs来解决,它将关键字参数收集为字典。

(或者,你可以写一个更高级的装饰器,将参数输入上下文或类似的东西,但我的简单尝试没有奏效,我还没有准备好尝试更高级的方法。我可能会稍后编辑答案并添加它们。

有了这个,我们可以制作一个程序:

import click
import functools
@click.group()
def cli():
pass
def common_options(f):
options = [
click.option("-a", is_flag=True),
click.option("-b", is_flag=True),
]
return functools.reduce(lambda x, opt: opt(x), options, f)
@cli.command()
@common_options
def hello(**kwargs):
print(kwargs)
# to get the value of b:
print(kwargs["b"])
@cli.command()
@common_options
@click.option("-c", "--citrus")
def world(citrus, a, **kwargs):
print("citrus is", citrus)
if a:
print(kwargs)
else:
print("a was not passed")
if __name__ == "__main__":
cli()

是的,您可以向子命令添加通用选项,这些选项可以位于子命令名称之后。
您可以在父命令和子命令上都有选项。

查看下面的代码片段

import click
from functools import wraps
@click.group()
def cli():
pass
def common_options(f):
@wraps(f)
@click.option('--option1', '-op1', help='Option 1 help text', type=click.FLOAT)
@click.option('--option2', '-op2', help='Option 2 help text', type=click.FLOAT)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
@cli.group(invoke_without_command=True)
@common_options
@click.pass_context
def parent(ctx, option1, option2):
ctx.ensure_object(dict)
if ctx.invoked_subcommand is None:
click.secho('Parent group is invoked. Perform specific tasks to do!', fg='bright_green')
@parent.command()
@click.option('--sub_option1', '-sop1', help='Sub option 1 help text', type=click.FLOAT)
@common_options
def sub_command1(option1, option2, sub_option1):
click.secho('Perform sub command 1 operations', fg='bright_green')
@parent.command()
@click.option('--sub_option2', '-sop2', help='Sub option 2 help text', type=click.FLOAT)
@common_options
def sub_command2(option1, option2, sub_option2):
click.secho('Perform sub command 2 operations', fg='bright_green')
if __name__ == "__main__":
cli()

用法

parent --help
=> prints parent group help text with options and sub commands
parent --option1 10 --option2 12
=> Parent group is invoked. Perform specific tasks to do!
parent sub_command1 --help
=> prints sub command 1 help text with options on sub commands
parent sub_command1 --option1 15 --option2 7 --sub_option1 5
=> Perform sub command 1 operations
parent sub_command2 --option1 15 --option2 7 --sub_option2 4
=> Perform sub command 2 operations

最新更新