Python Click: обработка исключений под setuptools

У меня есть приложение Python Click, которое прекрасно работает, но я хочу получать уведомления, когда пользователь вводит неизвестную команду. Например, если mycli foo допустимо, но они вводят mycli barЯ хочу переопределить поведение обработки исключений по умолчанию и запустить ошибку для отслеживания ошибок, таких как rollbar.

Я нашел эту страницу, которая описывает, как переопределить обработку исключений, но предполагается, что у меня есть Command, Проблема, с которой я столкнулся, состоит в том, что я также интегрировался с setuptools, следуя этому руководству, и оно указывает на мой Command в [console_scripts] раздел. Например, yourscript=yourscript:cli указывает на cli команда.

Я не знаю как позвонить cli.main() изнутри [console_scripts] или если это даже правильный способ думать об этом.

1 ответ

Решение

С кастомом click.Command класс, вы можете захватить вызывающую командную строку и затем сообщить о любой ошибке в командной строке в обработчике исключений, используя пользовательский класс, такой как:

Пользовательский класс

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))

Использование пользовательского класса

Затем, чтобы использовать пользовательскую команду / группу, передайте ее как 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 требуется подкласс, а также обработчик для отправки информации об исключении.

Как это работает?

Это работает, потому что щелчок - это хорошо спроектированная структура OO. @click.group() а также @click.command() декораторы обычно создают click.Group или же click.Command объекты, но позволяет этому поведению быть перегруженным cls параметр. Так что это относительно легко унаследовать от 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.
Другие вопросы по тегам