Python点击:setuptools下的Exception Handling

Python Click: Exception Handling under setuptools

我有一个运行良好的 python click 应用程序,但我想在用户输入未知命令时收到通知。例如,如果 mycli foo 有效,但他们输入 mycli bar,我想覆盖默认的异常处理行为并向错误跟踪器发出错误,例如 rollbar.

我找到了 this page which describes how to override the exception handling, but it assumes I have a Command. The problem I've run into is that I've also integrated with setuptools by following this guide,它指向我在 [console_scripts] 部分的 Command。例如,yourscript=yourscript:cli 指向 cli 命令。

我不确定如何从 [console_scripts] 中调用 cli.main() 或者这是否是正确的思考方式。

使用自定义 click.Command class,您可以捕获调用命令行,然后使用自定义 class 在异常处理程序中报告命令行中的任何错误,例如:

自定义Class

def CatchAllExceptions(cls, handler):

    class Cls(cls):

        _original_args = None

        def make_context(self, info_name, args, parent=None, **extra):

            # grab the original command line arguments
            self._original_args = ' '.join(args)

            try:
                return super(Cls, self).make_context(
                    info_name, args, parent=parent, **extra)
            except Exception as exc:
                # call the handler
                handler(self, info_name, exc)

                # let the user see the original error
                raise

        def invoke(self, ctx):
            try:
                return super(Cls, self).invoke(ctx)
            except Exception as exc:
                # call the handler
                handler(self, ctx.info_name, exc)

                # let the user see the original error
                raise

    return Cls


def handle_exception(cmd, info_name, exc):
    # send error info to rollbar, etc, here
    click.echo(':: Command line: {} {}'.format(info_name, cmd._original_args))
    click.echo(':: Raised error: {}'.format(exc))

使用自定义 class

然后使用自定义 command/group,将其作为 cls 参数传递给 click.commandclick.group 装饰器,例如:

@click.command(cls=CatchAllExceptions(click.Command, handler=report_exception))

@click.group(cls=CatchAllExceptions(click.Group, handler=report_exception))

@click.group(cls=CatchAllExceptions(click.MultiCommand, handler=report_exception))

注意需要指定需要哪个 click.Command subclass 以及 将异常信息发送到的处理程序。

这是如何工作的?

之所以可行,是因为 click 是一个设计良好的 OO 框架。 @click.group()@click.command() 装饰器通常实例化 click.Groupclick.Command 对象,但允许使用 cls 参数覆盖此行为。因此,在我们自己的class中继承click.Command(等)并覆盖所需的方法是一件相对容易的事情。

在这种情况下,我们越过 click.Command.make_context() 来获取原始命令行,click.Command.invoke() 来捕获异常,然后调用我们的异常处理程序。

测试代码:

import click

@click.group(cls=CatchAllExceptions(click.Group, handler=report_exception))
def cli():
    """A wonderful test program"""
    pass

@cli.command()
def foo():
    """A fooey command"""
    click.echo('Foo!')


if __name__ == "__main__":
    commands = (
        'foo',
        'foo --unknown',
        'foo still unknown',
        '',
        '--help',
        'foo --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)
            cli(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)]
-----------
> foo
Foo!
-----------
> foo --unknown
Error: no such option: --unknown
:: Command line: test.py foo --unknown
:: Raised error: no such option: --unknown
-----------
> foo still unknown
:: Command line: test.py foo still unknown
:: Raised error: Got unexpected extra arguments (still unknown)
Usage: test.py foo [OPTIONS]

Error: Got unexpected extra arguments (still unknown)
-----------
> 
Usage: test.py [OPTIONS] COMMAND [ARGS]...

  A wonderful test program

Options:
  --help  Show this message and exit.

Commands:
  foo  A fooey command
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...

  A wonderful test program

Options:
  --help  Show this message and exit.

Commands:
  foo  A fooey command
-----------
> foo --help
Usage: test.py foo [OPTIONS]

  A fooey command

Options:
  --help  Show this message and exit.