Require и option только в том случае, если выбор сделан при использовании click

При использовании click я знаю, как определить множественный выбор. Я также знаю, как сделать параметр обязательным. Но как я могу указать, что опция B требуется только в том случае, если значение опции A равно foo?

Вот пример:

import click

@click.command()
@click.option('--output',
              type=click.Choice(['stdout', 'file']), default='stdout')
@click.option('--filename', type=click.STRING)
def main(output, filename):
    print("output: " + output)
    if output == 'file':
        if filename is None:
            print("filename must be provided!")
        else:
            print("filename: " + str(filename))

if __name__ == "__main__":
    main()

Если параметр output равен stdout, то filename не нужен. Однако, если пользователь выбирает output вместо file, необходимо предоставить другой вариант filename. Поддерживается ли этот шаблон кликом?

В начале функции я могу добавить что-то вроде:

if output == 'file' and filename is None:
    raise ValueError('When output is "file", a filename must be provided')

Но мне интересно, есть ли более красивое/чистое решение.


person Dror    schedule 27.09.2017    source источник


Ответы (2)


В конкретном случае этого примера я думаю, что более простым методом было бы избавиться от --output и просто принять stdout, если --filename не указано, и если указано --filename, то использовать его вместо stdout.

Но предполагая, что это надуманный пример, вы можете наследоваться от click.Option, чтобы разрешить подключение к обработке кликов:

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

class OptionRequiredIf(click.Option):

    def full_process_value(self, ctx, value):
        value = super(OptionRequiredIf, self).full_process_value(ctx, value)

        if value is None and ctx.params['output'] == 'file':
            msg = 'Required if --output=file'
            raise click.MissingParameter(ctx=ctx, param=self, message=msg)
        return value

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

Чтобы использовать пользовательский класс, передайте его в качестве аргумента cls декоратору параметров, например:

@click.option('--filename', type=click.STRING, cls=OptionRequiredIf)

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

import click

@click.command()
@click.option('--output',
              type=click.Choice(['stdout', 'file']), default='stdout')
@click.option('--filename', type=click.STRING, cls=OptionRequiredIf)
def main(output, filename):
    print("output: " + output)
    if output == 'file':
        if filename is None:
            print("filename must be provided!")
        else:
            print("filename: " + str(filename))


main('--output=file'.split())

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

Usage: test.py [OPTIONS]

Error: Missing option "--filename".  Required if --output=file
person Stephen Rauch    schedule 27.09.2017
comment
Это кажется очень элегантным решением. Я попытался немного изменить его и предоставить еще два параметра классу OptionRequiredIf. Один будет depends_on_opt, который в этом примере будет output, а другой depends_on_val будет содержать file. Я провалил... - person Dror; 28.09.2017
comment
Боюсь, это решение не работает... При предоставлении filename соответствующая переменная не заполняется. Я разместил полный пример в суть - person Dror; 28.09.2017
comment
@dror, извините, я пропустил возвращаемое значение, я опаздывал на работу, поэтому я не тестировал так хорошо, как должен был. Рад, что все получилось и спасибо за правку... - person Stephen Rauch; 28.09.2017

Я расширил ответ Стивена и сделал его более общим:

class OptionRequiredIf(click.Option):
    """
    Option is required if the context has `option` set to `value`
    """

    def __init__(self, *a, **k):
        try:
            option = k.pop('option')
            value  = k.pop('value')
        except KeyError:
            raise(KeyError("OptionRequiredIf needs the option and value "
                           "keywords arguments"))

        click.Option.__init__(self, *a, **k)
        self._option = option
        self._value = value

    def full_process_value(self, ctx, value):
        value = super(OptionRequiredIf, self).full_process_value(ctx, value)
        if value is None and ctx.params[self._option] == self._value:
            msg = 'Required if --{}={}'.format(self._option, self._value)
            raise click.MissingParameter(ctx=ctx, param=self, message=msg)
        return value

Пример использования:

@click.option('--email', type=click.STRING,
              help='Settings for sending emails.',
              option='output', value='email', cls=OptionRequiredIf)

Меня вдохновил этот ответ

person Dror    schedule 10.10.2017