Укажите параметры и аргументы динамически - PullRequest
0 голосов
/ 24 мая 2018

Я хотел бы загрузить аргументы и параметры из базы данных.Я позволяю пользователям определять свои собственные параметры и аргументы.Пользователи могут вызывать удаленный API в командной строке.Они указывают URL и параметры до конечной точки.Вот как выглядят данные из базы данных

[
    {
        "name": "thename1",
        "short": "a",
        "long": "ace"
        "type": "string",
        "required": false
    },
    {
        "name": "thename2",
        "short": "b",
        "long": "bravo"
        "type": "number",
        "required": true
    },
    {
        "name": "thename3",
        "short": "c",
        "long": "candy"
        "type": "array",
        "required": true
    }
]

Эти параметры соответствуют тому, что нужно удаленной конечной точке.Каждая конечная точка API имеет разные параметры, поэтому они должны быть динамическими.Вот как будет выглядеть командная строка.

command run www.mysite.com/api/get -a testparam --bravo testpara2 -c item1 item2

Параметр и значение будут отображены в URL.Есть ли способ настроить динамические параметры в клик?

1 Ответ

0 голосов
/ 24 мая 2018

Это может быть сделано путем создания собственного декоратора, который вызывает декоратор click.option несколько раз после преобразования данной структуры данных в эквиваленты кликов.

Код:

import click

def options_from_db(options):
    map_to_types = dict(
        array=str,
        number=float,
        string=str,
    )
    def decorator(f):
        for opt_params in reversed(options):
            param_decls = (
                '-' + opt_params['short'],
                '--' + opt_params['long'],
                opt_params['name'])
            attrs = dict(
                required=opt_params['required'],
                type=map_to_types.get(
                    opt_params['type'], opt_params['type'])
            )
            if opt_params['type'] == 'array':
                attrs['cls'] = OptionEatAll
                attrs['nargs'] = -1

            click.option(*param_decls, **attrs)(f)
        return f
    return decorator

Использованиеoptions_from_db decorator:

Чтобы использовать новый декоратор, украсьте команду и передайте данные опции из БД, например:

@options_from_db(run_options)
def command(*args, **kwargs):
    ....

Как это работает?

Декоратор @click.option(), как и все декораторы, является функцией.В этом случае он аннотирует декорированную функцию и возвращает ту же функцию.Поэтому мы можем просто вызывать его несколько раз, чтобы аннотировать нашу оформленную функцию.

Примечание: ваш array параметр нарушает требование щелчка, чтобы не разрешать nargs <0 для параметров.Но есть <a href="https://stackoverflow.com/a/48394004/7311767"> другой ответ , который позволяет это, и этот ответ использует код оттуда.

Код от другой ответ :

class OptionEatAll(click.Option):

    def __init__(self, *args, **kwargs):
        self.save_other_options = kwargs.pop('save_other_options', True)
        nargs = kwargs.pop('nargs', -1)
        assert nargs == -1, 'nargs, if set, must be -1 not {}'.format(nargs)
        super(OptionEatAll, self).__init__(*args, **kwargs)
        self._previous_parser_process = None
        self._eat_all_parser = None

    def add_to_parser(self, parser, ctx):

        def parser_process(value, state):
            # method to hook to the parser.process
            done = False
            value = [value]
            if self.save_other_options:
                # grab everything up to the next option
                while state.rargs and not done:
                    for prefix in self._eat_all_parser.prefixes:
                        if state.rargs[0].startswith(prefix):
                            done = True
                    if not done:
                        value.append(state.rargs.pop(0))
            else:
                # grab everything remaining
                value += state.rargs
                state.rargs[:] = []
            value = tuple(value)

            # call the actual process
            self._previous_parser_process(value, state)

        retval = super(OptionEatAll, self).add_to_parser(parser, ctx)
        for name in self.opts:
            our_parser = parser._long_opt.get(
                name) or parser._short_opt.get(name)
            if our_parser:
                self._eat_all_parser = our_parser
                self._previous_parser_process = our_parser.process
                our_parser.process = parser_process
                break
        return retval

Код теста:

run_options = [
    {
        "name": "thename1",
        "short": "a",
        "long": "ace",
        "type": "string",
        "required": False
    }, {
        "name": "thename2",
        "short": "b",
        "long": "bravo",
        "type": "number",
        "required": True
    }, {
        "name": "thename3",
        "short": "c",
        "long": "candy",
        "type": "array",
        "required": True
    }
]

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

@cli.command()
@options_from_db(run_options)
@click.argument('url')
def run(*args, **kwargs):
    click.echo('args: {}'.format(args) )
    click.echo('kwargs: {}'.format(kwargs))


if __name__ == "__main__":
    commands = (
        'run www.mysite.com/api/get -a testparam --bravo 5 -c item1 item2',
        '',
        '--help',
        'run --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

Результаты:

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)]
-----------
> run www.mysite.com/api/get -a testparam --bravo 5 -c item1 item2
args: ()
kwargs: {'thename1': 'testparam', 'thename2': 5.0, 'thename3': ('item1', 'item2'), 'url': 'www.mysite.com/api/get'}
-----------
> 
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  run
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  run
-----------
> run --help
Usage: test.py run [OPTIONS] URL

Options:
  -a, --ace TEXT
  -b, --bravo FLOAT  [required]
  -c, --candy TEXT   [required]
  --help             Show this message and exit.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...