python - 动态指定选项和参数

标签 python command-line-interface python-click

我想从数据库加载参数和选项。我允许用户定义自己的选项和参数。用户可以通过命令行调用远程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.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 参数违反了点击要求,不允许选项上出现 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.

关于python - 动态指定选项和参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50499340/

相关文章:

bash - GitLab 作业作业成功但未完成(创建/删除 Azure AKS)

python - 如何为点击处理的参数列表指定默认值?

python - 使用 Python Click 保留用户提供的参数的顺序

python - 模拟返回模拟对象而不是返回值

python - 如何在 Python3 中使用构造函数模拟对象?

python - 为什么列表上的 os.path.join() 和列表上的 os.path.sep.join() 在功能上不相同?

python - 是否可以从命令行找到 python 包的路径?

python - 使用多个 Numpy 数组作为一个 Seaborn 箱线图输入的简单方法

php - 如何在 Linux 上的 xampp 安装中访问 mysql 和 php cli?

python - 如何根据第一个输入显示/隐藏输入选项?