动态指定选项和参数 [英] Specify options and arguments dynamically

查看:69
本文介绍了动态指定选项和参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想从数据库中加载参数和选项.我允许用户定义自己的选项和参数.用户可以在命令行上调用远程api.它们将URL和参数指定到终点.这是数据库中数据的样子

I'd like to load arguments and options from a database. I am allowing users to define their own options and arguments. Users can call a remote api on the command line. They specify the URL and parameters to the end point. Here is what the data from database looks like

[
    {
        "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端点都有不同的参数,因此它们是动态的.这是命令行的外观.

These parameters align with what the remote end point needs. Each API endpoint has different parameters hence the reason they need to be dynamic. Here is how the command line will look.

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

参数和值将映射到URL中.有没有办法在点击中设置动态参数?

The param and value will be mapped in a URL. Is there a way to setup dynamic parameters in click?

推荐答案

这可以通过构建自定义装饰器来完成,该装饰器将给定的数据结构转换为等效的点击后多次调用click.option装饰器.

This can be done by building a custom decorator that calls the click.option decorator multiple times after translating the given data structure into click equivalents.

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装饰器:

要使用新的装饰器,请装饰命令,然后像下面这样传递来自db的选项数据:

Using the options_from_db decorator:

To use the new decorator, decorate the command, and pass the option data from the db like:

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

这是如何工作的?

与所有装饰器一样,@click.option()装饰器是一个函数.在这种情况下,它将对修饰的函数进行注释,并返回相同的函数.因此,我们可以简单地多次调用它来注释我们的装饰函数.

How does this work?

The @click.option() decorator is, like all decorators, a function. In this case it annotates the decorated function, and returns the same function. So we can simply call it multiple times to annotate our decorated function.

注意:您的array参数违反了点击要求,不允许nargs<选项上为0.但是有另一个答案可以启用此功能,并且此答案使用

Note: your array parameter violates the click requirement to not allow nargs < 0 on options. But there is another answer that enables this, and this answer uses the code from there.

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.

这篇关于动态指定选项和参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆