Python Click — выполнять подкоманду только в том случае, если родительская команда выполнена успешно

Я использую Click для создания Python CLI и столкнулся с проблемой обработки исключений в Click.

Я не уверен в формулировке («подкоманда», «родительская команда») здесь, но из моего примера вы поймете, как я надеюсь. Предположим, что этот код:

@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

Когда приложение запускается, это вызывается:

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

Затем я запускаю программу следующим образом:

python myapp --something somevalue mycommand

Ожидаемое поведение: сначала вызывается mycli и выполняется код в нем. Если возникает исключение, оно перехватывается блоком исключения, печатается сообщение и возникает исключение. Поскольку у нас нет другого блока try/except, это приведет к завершению скрипта. «Подкоманда» mycommand никогда не вызывается, потому что программа уже завершилась при выполнении «родительской» команды mycli.

Фактическое поведение: исключение перехватывается и сообщение печатается, но mycommand все еще вызывается. Затем происходит сбой с другим сообщением об исключении, потому что требуемая переменная контекста не была установлена.

Как бы я справился с чем-то подобным? По сути, я хочу вызвать подкоманду mycommand только для выполнения, если все в mycli прошло нормально.


person omni    schedule 14.08.2018    source источник


Ответы (1)


Чтобы обработать исключение, но не переходить к подкомандам, вы можете просто вызвать exit(), например:

Код:

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

Тестовый код:

@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

Полученные результаты:

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.
person Stephen Rauch    schedule 15.08.2018
comment
Спасибо, Стивен. Это то, что я делаю сейчас, но это не сработает, если копать кроличью нору глубже. Например. скажем так, я добавляю еще один слой подкоманды, назовем его под-под-командой. И теперь у меня есть проблема в моей подкоманде, и я хочу, чтобы исключение всплывало до родительской команды, а подподкоманда никогда не вызывалась. В этом случае я не могу просто использовать exit() в подкоманде. - person omni; 15.08.2018
comment
Почему ты не можешь позвонить exit()? Чего не дает вызов exit()? - person Stephen Rauch; 15.08.2018
comment
В моем случае - не вдаваясь в подробности (это будет более длинное обсуждение), я хочу выделить исключение, потому что у меня есть обработчик исключений верхнего уровня, который перехватывает все исключения, если используется CLI, и заключает их в сообщение об ошибке JSON. Если модули CLI используются в качестве API, я не хочу такого поведения. Без пузырей мне нужно было бы поместить оболочку исключения JSON в каждую подкоманду, которая нарушает DRY. Я предполагаю, что есть и другие случаи, когда необходимо вздутие. - person omni; 15.08.2018
comment
Итак, вопрос в том, как мне сделать обработчик исключений верхнего уровня. Вы пробовали это? - person Stephen Rauch; 15.08.2018
comment
Извините за поздний повтор. Я уже видел это в документах кликов, но сначала мне не понравилась идея иметь специальный обработчик. Я бы предпочел остаться с поведением всплывающих окон по умолчанию. Однако, поскольку на данный момент такой опции, похоже, нет, и она эффективно приведет к той же функции, я попробую и дам вам знать, как все прошло. Спасибо за ваши предложения! - person omni; 18.08.2018
comment
Я проверил ваше предложение с обработчиком исключений верхнего уровня, но, похоже, оно не работает так, как мне нужно. Я добавил его в группу щелчков, и когда исключение происходит в подкоманде, оно работает, как и ожидалось. но моя цель состояла в том, чтобы никогда не вызывать подкоманду, когда в группе происходит исключение. для исключений, которые происходят в группе, однако обработчик исключений никогда не вызывается, и я вижу, что моя подкоманда все еще вызывается после возникновения исключения. - person omni; 21.08.2018