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

使用Python`click`CLI,如何将公共选项添加到子命令中,这些子命令可以*在*子命令的名称之后

  •  1
  • halloleo  · 技术社区  · 7 年前

    使用CLI库 click app.py read write :

    @click.group()
    @click.pass_context
    def cli(ctx):
        pass
    
    @cli.command()
    @click.pass_context
    def read(ctx):
        print("read")
    
    @cli.command()
    @click.pass_context
    def write(ctx):
        print("write")
    

    我想声明一个公共选项 --format . 我知道我可以把它作为一个选项添加到命令中 通过

    @click.group()
    @click.option('--format', default='json')
    @click.pass_context
    def cli(ctx, format):
        ctx.obj['format'] = format
    

    但我不能给你这个选择 之后 命令,在我的用例中是更自然的。我希望能够在shell中发布:

    app.py read --format XXX 
    

    Error: no such option: --format . 脚本只接受该选项 之前 命令。

    1 回复  |  直到 5 年前
        1
  •  10
  •   matejcik    7 年前

    AFAICT,这是不可能的点击。文件规定:

    单击可严格分隔命令和子命令之间的参数。 这意味着特定命令的选项和参数 其他命令名。

    一种可能的解决方法是编写 common_options 装饰工。下面的示例使用以下事实 click.option 是一个函数,返回一个修饰符函数,该函数期望以串联方式应用。瞧,下面是:

    @click.option("-a")
    @click.option("-b")
    def hello(a, b):
        pass
    

    相当于:

    def hello(a, b):
        pass
    
    hello = click.option("-a")(click.option("-b")(hello))
    

    缺点是需要在所有子命令上设置公共参数。这可以通过 **kwargs ,它将关键字参数收集为dict。

    这样,我们就可以制作一个程序:

    import click
    import functools
    
    @click.group()
    def cli():
        pass
    
    def common_options(f):
        options = [
            click.option("-a", is_flag=True),
            click.option("-b", is_flag=True),
        ]
        return functools.reduce(lambda x, opt: opt(x), options, f)
    
    @cli.command()
    @common_options
    def hello(**kwargs):
        print(kwargs)
        # to get the value of b:
        print(kwargs["b"])
    
    @cli.command()
    @common_options
    @click.option("-c", "--citrus")
    def world(citrus, a, **kwargs):
        print("citrus is", citrus)
        if a:
            print(kwargs)
        else:
            print("a was not passed")
    
    if __name__ == "__main__":
        cli()