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.command
或 click.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.Group
或 click.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.
我有一个运行良好的 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.command
或 click.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.Group
或 click.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.