代码之家  ›  专栏  ›  技术社区  ›  Luke101

动态指定选项和参数

  •  1
  • Luke101  · 技术社区  · 8 年前

    我想从数据库中加载参数和选项。我允许用户定义自己的选项和参数。用户可以在命令行上调用远程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中。是否有方法在Click中设置动态参数?

    1 回复  |  直到 8 年前
        1
  •  2
  •   Stephen Rauch Afsar Ali    8 年前

    这可以通过构建调用 click.option 在将给定的数据结构转换为单击等价物后多次使用decorator。

    代码:

    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,请修饰命令,然后从db传递选项数据,如下所示:

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

    这是怎么工作的?

    这个 @click.option() 就像所有的装饰师一样,装饰师是一个功能。在本例中,它注释修饰函数,并返回相同的函数。所以我们可以多次调用它来注释我们的修饰函数。

    注: 你的 array 参数违反了单击要求,不允许 nargs <0选项。但是有 another answer 这样,这个答案使用 the code 从那里。

    代码来自 other answer :

    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.
    
    推荐文章