【问题标题】:Can I have a main() Click function that invokes all other sub-commands?我可以有一个调用所有其他子命令的 main() Click 函数吗?
【发布时间】:2018-05-16 16:05:33
【问题描述】:

我有一个类似的脚本:

#myscript.py
import click

def step1(arg1):
    print('Step 1: ' + arg1)

def step2(arg2):
    print('Step 2: ' + arg2)

def main(arg1, arg2):
    step1(arg1)
    step2(arg2)

大部分时间我想用myscript arg1 arg2 运行脚本,但有时我可能只想运行一个步骤:例如myscript step1 arg1如何设置 click 来执行此操作? 有没有办法让一个默认命令然后是其他可选命令?

这似乎是Click discourages一件事

有时,从另一个命令调用一个命令可能会很有趣。 Click 通常不鼓励这种模式,但尽管如此。

我需要使用这个click.invoke() 模式吗?

【问题讨论】:

  • 我相信作者是在劝阻您不要让一个命令使用subprocess.call 或类似命令调用另一个命令。使用命令行标志可以正常工作。此外,添加字符串,尤其是在 print 语句内部,通常被认为是糟糕的风格。对于您的情况,您可以只使用print('Step1:', arg1)

标签: python python-3.x python-click


【解决方案1】:

我认为Multi Command ChainingMulti Command Pipelines 功能旨在涵盖这种情况。流水线提供了所要求的确切行为(step1step2 在命令行上没有给出任何内容时都会调用),但它更冗长,并且chain=True 没有任何参数可以是可选的;您必须 (a) 始终同时提供 arg1arg2,即使只调用 step2;或 (b) 将这些参数转换为选项(--arg1 foo 而不是 foo)。

链接

import click

@click.group(chain=True)
def cli():
    pass

@cli.command()
@click.argument('arg1')
def step1(arg1):
    click.echo('Step 1: ' + arg1)

@cli.command()
@click.argument('arg2')
def step2(arg2):
    click.echo('Step 2: ' + arg2)

cli()

然后:

$ python3 chain.py step1 foo step2 bar
Step 1: foo
Step 2: bar
$ python3 chain.py step2 bar
Step 2: bar

流水线

import click

@click.group(chain=True, invoke_without_command=True)
@click.argument('arg1')
@click.argument('arg2')
def cli(arg1, arg2):
    pass

@cli.resultcallback()
def process_pipeline(processors, **kwargs):
    # If no commands given, invoke step1 then step2
    processors = processors if len(processors) else [step1, step2]
    for processor in processors:
        processor(**kwargs)

def step1(**kwargs):
    click.echo('Step 1: ' + kwargs['arg1'])

def step2(**kwargs):
    click.echo('Step 2: ' + kwargs['arg2'])

@cli.command('step1')
def make_step1():
    return step1

@cli.command('step2')
def make_step2():
    return step2

cli()

然后

$ python3 pipeline.py foo bar
Step 1: foo
Step 2: bar
$ python3 pipeline.py foo bar step2
Step 2: bar

【讨论】:

  • invoke_without_command=True 让我走上了正确的道路。对不起,我应该把问题写得更好。将调查chain=True 元素以链接步骤。
【解决方案2】:

我没有用这个问题来完全解释我想要做什么,因为每个步骤还需要前一个步骤的输出。非常感谢Paul 让我走上了正确的道路。

我的解决方案是这样的:

@click.group(invoke_without_command=True)
@click.option('--arg1')
@click.option('--arg2')
@click.pass_context
def cli(ctx, arg1, arg2):
    '''Description
    '''
    if ctx.invoked_subcommand is None:
        do_everything(ctx, arg1, arg2)

@cli.command()
@click.option('--arg1')
def step_1(arg1):
    return do_something(arg1)

@cli.command()
@click.argument('step_one_result')
@click.option('--arg2')
def step_2(step_one_result, arg2):
    do_something_else(step_one_result, arg2)

def do_everything(ctx, arg1, arg2):
    step_one_result = ctx.invoke(step_1, arg1=arg1)
    ctx.invoke(do_something_else, step_one_result=step_one_result, arg2=arg2)

#and because of weirdness with pass_context and using setuptools

def main():
    cli(obj={})

if __name__ == '__main__':
    main()

编辑:您会注意到ctx.invoke() 的使用是调用函数所必需的,而不会出现以下错误

line 619, in make_context
    ctx = Context(self, info_name=info_name, parent=parent, **extra)
TypeError: __init__() got an unexpected keyword argument 'arg1'

【讨论】:

  • 大声笑我刚刚意识到我的答案编辑答案使用了我的第二个粗体问题
最近更新 更多