Python Click - 只有在父命令成功执行后才执行子命令

7
我将使用Click构建Python CLI,并在处理Click中的异常时遇到了问题。
我不确定这里的措辞(“子命令”,“父命令”),但从我的示例中,您会明白我的意思。让我们假设有以下代码:
@click.group()
@click.option("--something")
def mycli(something):
    try:
        #do something with "something" and set ctx
        ctx.obj = {}
        ctx.obj["somevar"] = some_result
    except:
        print("Something went wrong")
        raise

    #only if everything went fine call mycommand

@click.group()
@click.pass_context
def mygroup(ctx):
    pass

@mygroup.command(name="mycommand")
@click.pass_context
def mycommand(ctx):
    #this only works if somevar is set in ctx so don't call this if setting went wrong in mycli

应用程序启动时会调用此方法:

if __name__ == "__main__":
    mycli.add_command(mygroup)
    mycli()

I then start the program like this:

python myapp --something somevalue mycommand

期望行为:首先调用mycli,并执行其中的代码。如果抛出异常,则被except块捕获,并打印一条消息并引发异常。由于我们没有其他的try/except块,这将导致脚本终止。"子"命令mycommand永远不会被调用,因为在运行"父"命令mycli时程序已经终止。

实际行为:异常被捕获并打印了消息,但是mycommand仍然被调用。然后它因为未设置所需的上下文变量而失败并报告另一个异常消息。
如何处理这样的情况?基本上我只想在mycli中的所有内容都正常执行完毕后才调用子命令mycommand
1个回答

3

要处理异常,但不想继续执行子命令,您可以简单地调用exit()

代码:

import click

@click.group()
@click.option("--something")
@click.pass_context
def mycli(ctx, something):
    ctx.obj = dict(a_var=something)
    try:
        if something != '1':
            raise IndexError('An Error')
    except Exception as exc:
        click.echo('Exception: {}'.format(exc))
        exit()

测试代码:

@mycli.group()
@click.pass_context
def mygroup(ctx):
    click.echo('mygroup: {}'.format(ctx.obj['a_var']))
    pass


@mygroup.command()
@click.pass_context
def mycommand(ctx):
    click.echo('mycommand: {}'.format(ctx.obj['a_var']))


if __name__ == "__main__":
    commands = (
        'mygroup mycommand',
        '--something 1 mygroup mycommand',
        '--something 2 mygroup mycommand',
        '--help',
        '--something 1 mygroup --help',
        '--something 1 mygroup mycommand --help',
    )

    import sys, time

    time.sleep(1)
    print('Click Version: {}'.format(click.__version__))
    print('Python Version: {}'.format(sys.version))
    for cmd in commands:
        try:
            time.sleep(0.1)
            print('-----------')
            print('> ' + cmd)
            time.sleep(0.1)
            mycli(cmd.split())

        except BaseException as exc:
            if str(exc) != '0' and \
                    not isinstance(exc, (click.ClickException, SystemExit)):
                raise

结果:

Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> mygroup mycommand
Exception: An Error
-----------
> --something 1 mygroup mycommand
mygroup: 1
mycommand: 1
-----------
> --something 2 mygroup mycommand
Exception: An Error
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  --something TEXT
  --help            Show this message and exit.

Commands:
  mygroup
-----------
> --something 1 mygroup --help
Usage: test.py mygroup [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  mycommand
-----------
> --something 1 mygroup mycommand --help
mygroup: 1
Usage: test.py mygroup mycommand [OPTIONS]

Options:
  --help  Show this message and exit.

谢谢Stephen。这是我目前正在做的,但当深入挖掘时它不起作用。例如,假设我添加了另一个子命令层,我们称之为子子命令。现在我在我的子命令中遇到问题,并希望异常向上冒泡到父命令,而不会调用子子命令。在这种情况下,我不能只在子命令中使用exit() - omni
为什么不能调用 exit()?调用 exit() 无法实现什么? - Stephen Rauch
在我的情况下 - 不详细讨论(需要更长的讨论),我想将异常上抛,因为我有一个顶级异常处理程序,如果使用CLI,则会捕获所有异常并将它们包装成JSON错误消息。如果使用CLI模块作为API,则不希望出现这种行为。如果不进行上抛,我需要在每个子命令中放置JSON异常包装器,这会破坏DRY原则。我想象还有其他需要上抛的情况。 - omni
1
好的,问题实际上是如何编写一个顶层异常处理程序。你尝试过这个吗?这里有一个参考答案。 - Stephen Rauch
抱歉回复晚了。我已经在点击文档中看到了这个,但一开始并不喜欢有一个特殊的处理程序的想法。我更愿意保持默认的“冒泡”行为。然而,由于现在似乎没有这样的选项,而且它实际上会产生同样的功能,我会试一下并告诉你效果如何。感谢您的建议! - omni
我使用了您的建议并添加了一个顶级异常处理程序,但它似乎不能按照我需要的方式工作。我将其添加到了 click 组中,当子命令中发生异常时,它按预期工作。但我的目标是在组中发生异常时永远不调用子命令。然而,对于在组中发生的异常,异常处理程序从未被调用,我可以看到在异常发生后仍然调用了我的子命令。 - omni

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接