动态指定选项和参数 [英] Specify options and arguments dynamically
问题描述
我想从数据库中加载参数和选项.我允许用户定义自己的选项和参数.用户可以在命令行上调用远程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屋!