Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Click - only execute subcommand if parent command executed successfully

I'm using Click to build a Python CLI and am running into an issue with how exceptions are handles in Click.

I'm not sure about the wording ("subcommand", "parentcommand") here but from my example you'll get the idea I hope. Let's assume this code:

@click.group()
@click.option("--something")
def mycli(something):
    try:
        #do something with "something" and set ctx
        ctx.obj = {}
        ctx.obj["somevar"] = some_result
    except:
        print("Something went wrong")
        raise

    #only if everything went fine call mycommand

@click.group()
@click.pass_context
def mygroup(ctx):
    pass

@mygroup.command(name="mycommand")
@click.pass_context
def mycommand(ctx):
    #this only works if somevar is set in ctx so don't call this if setting went wrong in mycli

When the application starts this is called:

if __name__ == "__main__":
    mycli.add_command(mygroup)
    mycli()

I then start the program like this:

python myapp --something somevalue mycommand

Expected behaviour: first mycli is called and the code in it is executed. If an exception is thrown it's caught by the except block, a message is printed and the exception is raised. Because we have no other try/except block this will result in termination of the script. The "sub"-command mycommand is never called because the program already terminated when running the "parent"-command mycli.

Actual behaviour: the exception is caughtand the message is printed, but mycommand is still called. It then fails with another exception message because the required context variable was not set.

How would I handle something like that? Basically I only want to call the subcommand mycommand only to be executed if everything in mycli went fine.

like image 453
omni Avatar asked Aug 14 '18 18:08

omni


Video Answer


1 Answers

To handle the exception, but not continue onto the subcommands, you can simply call exit() like:

Code:

import click

@click.group()
@click.option("--something")
@click.pass_context
def mycli(ctx, something):
    ctx.obj = dict(a_var=something)
    try:
        if something != '1':
            raise IndexError('An Error')
    except Exception as exc:
        click.echo('Exception: {}'.format(exc))
        exit()

Test Code:

@mycli.group()
@click.pass_context
def mygroup(ctx):
    click.echo('mygroup: {}'.format(ctx.obj['a_var']))
    pass


@mygroup.command()
@click.pass_context
def mycommand(ctx):
    click.echo('mycommand: {}'.format(ctx.obj['a_var']))


if __name__ == "__main__":
    commands = (
        'mygroup mycommand',
        '--something 1 mygroup mycommand',
        '--something 2 mygroup mycommand',
        '--help',
        '--something 1 mygroup --help',
        '--something 1 mygroup mycommand --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)
            mycli(cmd.split())

        except BaseException as exc:
            if str(exc) != '0' and \
                    not isinstance(exc, (click.ClickException, SystemExit)):
                raise

Results:

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)]
-----------
> mygroup mycommand
Exception: An Error
-----------
> --something 1 mygroup mycommand
mygroup: 1
mycommand: 1
-----------
> --something 2 mygroup mycommand
Exception: An Error
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  --something TEXT
  --help            Show this message and exit.

Commands:
  mygroup
-----------
> --something 1 mygroup --help
Usage: test.py mygroup [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  mycommand
-----------
> --something 1 mygroup mycommand --help
mygroup: 1
Usage: test.py mygroup mycommand [OPTIONS]

Options:
  --help  Show this message and exit.
like image 86
Stephen Rauch Avatar answered Oct 01 '22 21:10

Stephen Rauch