可以通过构建一个自定义装饰器来完成,该装饰器在将给定数据结构转换为click等效项后多次调用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
装饰器:
要使用这个新的装饰器,需要在命令上添加装饰器,并像下面这样从数据库中传递选项数据:
@options_from_db(run_options)
def command(*args, **kwargs):
....
这是如何工作的?
@click.option()
装饰器像所有装饰器一样,都是一个函数。 在这种情况下,它会注释已装饰的函数,并返回相同的函数。所以我们可以多次调用它来注释我们的已装饰函数。
注意:您的array
参数违反了click不允许选项上的nargs
<0的要求。但有另一个答案可以使其成为可能,而这个答案使用了那里的代码。
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):
done = False
value = [value]
if self.save_other_options:
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:
value += state.rargs
state.rargs[:] = []
value = tuple(value)
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.