Python Click: обработка исключений под setuptools - PullRequest
0 голосов
/ 07 сентября 2018

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

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

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

1 Ответ

0 голосов
/ 07 сентября 2018

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

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

Это работает, потому что click - это хорошо спроектированная 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.
...