Общие параметры и флаги между командами

#python #command-line-interface #python-click

#python #интерфейс командной строки #python-щелчок

Вопрос:

Допустим, в моей утилите CLI есть три команды: cmd1 , cmd2 , cmd3

И я хочу cmd3 иметь те же параметры и флаги, что и cmd1 и cmd2 . Похоже на какое-то наследование.

 @click.command()
@click.options("--verbose")
def cmd1():
    pass

@click.command()
@click.options("--directory")
def cmd2():
    pass

@click.command()
@click.inherit(cmd1, cmd2) # HYPOTHETICAL
def cmd3():
    pass
  

Таким образом, cmd3 будут иметь флаг --verbose и опцию --directory . Возможно ли сделать это с помощью Click? Возможно, я просто что-то упустил из виду в документации…

РЕДАКТИРОВАТЬ: я знаю, что могу сделать это с click.group() . Но тогда все параметры группы должны быть указаны перед командой группы. Я хочу, чтобы все параметры обычно выполнялись после команды.

cli.py --verbose --directory /tmp cmd3 -> cli.py cmd3 --verbose --directory /tmp

Ответ №1:

Я нашел простое решение! Я немного отредактировал фрагмент из https://github.com/pallets/click/issues/108 :

 import click


_cmd1_options = [
    click.option('--cmd1-opt')
]

_cmd2_options = [
    click.option('--cmd2-opt')
]


def add_options(options):
    def _add_options(func):
        for option in reversed(options):
            func = option(func)
        return func
    return _add_options


@click.group()
def group(**kwargs):
    pass


@group.command()
@add_options(_cmd1_options)
def cmd1(**kwargs):
    print(kwargs)


@group.command()
@add_options(_cmd2_options)
def cmd2(**kwargs):
    print(kwargs)


@group.command()
@add_options(_cmd1_options)
@add_options(_cmd2_options)
@click.option("--cmd3-opt")
def cmd3(**kwargs):
    print(kwargs)


if __name__ == '__main__':
    group()
  

Комментарии:

1. Отличное решение, спасибо 🙂

Ответ №2:

Определите класс с общими параметрами

 class StdCommand(click.core.Command):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.params.insert(0, click.core.Option(('--default-option',), help='Every command should have one'))
  

Затем передайте класс декоратору при определении командной функции

 @click.command(cls=StdCommand)
@click.option('--other')
def main(default_option, other):
  ...
  

Комментарии:

1. Это самое чистое и элегантное решение. Работает без сбоев. Я предлагаю заменить click.core.Command на click.Command . На самом деле не нужно переходить к нижнему модулю. Кроме того, проще сказать self.params = [...] вместо вставки в пустой список.

2. @d0m1n0 Первый комментарий абсолютно правильный, а второй — нет. Список не обязательно пустой, вы перезаписываете то, что в нем есть, с помощью предлагаемого подхода. Когда вы добавляете дополнительные параметры ( --other ), они исчезают.

Ответ №3:

У вас также может быть другой декоратор для общих параметров. Я нашел это решение здесь

 def common_params(func):
    @click.option('--foo')
    @click.option('--bar')
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper


@click.command()
@common_params
@click.option('--baz')
def cli(foo, bar, baz):
    print(foo, bar, baz)
  

Ответ №4:

Этот код извлекает все параметры из своих аргументов

 def extract_params(*args):
    from click import Command
    if len(args) == 0:
        return ['']
    if any([ not isinstance(a, Command) for a in args ]):
        raise TypeError('Handles only Command instances')

    params = [ p.opts() for cmd_inst in args for p in cmd_inst.params ]
    return list(set(params))
  

теперь вы можете использовать его:

 @click.command()
@click.option(extract_params(cmd1, cmd2))
def cmd3():
    pass
  

Этот код извлекает только параметры и ни одно из их значений по умолчанию, вы можете улучшить его при необходимости.

Ответ №5:

Небольшое улучшение по сравнению с решением @jirinovo. эта версия поддерживает неограниченное количество опций щелчка. одна вещь, о которой стоит упомянуть, важен порядок передачи параметров

 import click

_global_options = [click.option('--foo', '-f')]
_local_options = [click.option('--bar', '-b', required=True)]
_local_options2 = [click.option('--foofoo', required=True)]


def add_options(*args):
    def _add_options(func):
        options = [x for n in args for x in n]
        for option in reversed(options):
            func = option(func)
        return func

    return _add_options


@click.group()
def cli():
    pass


@cli.group()
def subcommand():
    pass


@subcommand.command()
@add_options(_global_options, _local_options)
def echo(foo, bar):
    print(foo, bar, sep='n')


@subcommand.command()
@add_options(_global_options)
def echo2(foo):
    print(foo)


@subcommand.command()
@add_options(_global_options, _local_options2)
def echo3(foo, foofoo):
    print(foo, foofoo, sep='n')


@subcommand.command()
@add_options(_global_options, _local_options, _local_options2)
def echo4(foo, bar, foofoo):
    print(foo, bar, foofoo, sep='n')


if __name__ == '__main__':
    cli()