2 votes

Cliquez sur Python : Gestion des exceptions sous setuptools

J'ai un python click qui fonctionne très bien, mais je veux être averti chaque fois qu'un utilisateur tape une commande inconnue. Par exemple, si mycli foo est valide, mais ils tapent mycli bar Je veux passer outre le comportement de gestion des exceptions par défaut et envoyer une erreur à un outil de suivi des erreurs, tel que rollbar.

J'ai trouvé cette page qui décrit comment surcharger la gestion des exceptions, mais il suppose que j'ai un Command . Le problème que j'ai rencontré est que j'ai également intégré setuptools en suivant la procédure suivante ce guide et il pointe vers mon Command dans le [console_scripts] section. Par exemple, yourscript=yourscript:cli pointe vers le cli commande.

Je ne sais pas comment appeler cli.main() de l'intérieur [console_scripts] ou si c'est même la bonne façon de penser.

1voto

Stephen Rauch Points 29619

Avec un click.Command vous pouvez capturer la ligne de commande invoquée et signaler toute erreur dans la ligne de commande dans un gestionnaire d'exception à l'aide d'une classe personnalisée comme :

Classe personnalisée

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

Utilisation de la classe personnalisée

Ensuite, pour utiliser la commande/groupe personnalisée, transmettez-la en tant que paramètre cls à l'argument click.command o click.group décorateur comme l'un d'entre eux :

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

Notez qu'il est nécessaire de préciser quel click.Command est nécessaire ainsi que la sous-classe le gestionnaire auquel envoyer les informations relatives à l'exception.

Comment cela fonctionne-t-il ?

Cela fonctionne parce que click est un cadre OO bien conçu. Les @click.group() y @click.command() instancie généralement un décorateur click.Group o click.Command mais permet d'outrepasser ce comportement à l'aide de l'option cls paramètre. Il est donc relativement facile d'hériter de click.Command (etc.) dans notre propre classe et surcharger les méthodes souhaitées.

Dans ce cas, nous passons outre click.Command.make_context() pour récupérer la ligne de commande originale, et click.Command.invoke() pour attraper l'exception et appeler ensuite notre gestionnaire d'exception.

Code de test :

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

Résultats :

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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X