如何基于单个选项在Python中的click CLI中定义控制流,并将其余的传递给另一个命令?
这个问题与Python包有关,并且与基于传递给CLI的参数的控制流有关 我正试图构建一个主CLI,使其位于repo的顶部目录,它将从一个中心位置控制许多不同的操作模式。调用此命令时,可能需要如何基于单个选项在Python中的click CLI中定义控制流,并将其余的传递给另一个命令?,python,command-line-interface,python-click,Python,Command Line Interface,Python Click,这个问题与Python包有关,并且与基于传递给CLI的参数的控制流有关 我正试图构建一个主CLI,使其位于repo的顶部目录,它将从一个中心位置控制许多不同的操作模式。调用此命令时,可能需要n选项,其中第一个选项将确定要调用哪个模块,然后将n-1参数传递给另一个模块。但我希望每个模块的命令和选项都在其各自的模块中定义,而不是在主CLI控制器中定义,因为我试图保持主控制器的简单性,并将每个模块很好地抽象出来 以下是我想要的一个简单示例: import click @click.command(
n
选项,其中第一个选项将确定要调用哪个模块,然后将n-1
参数传递给另一个模块。但我希望每个模块的命令和选项都在其各自的模块中定义,而不是在主CLI控制器中定义,因为我试图保持主控制器的简单性,并将每个模块很好地抽象出来
以下是我想要的一个简单示例:
import click
@click.command()
@click.option('--foo-arg', default='asdf')
def foo(foo_arg):
click.echo(f"Hello from foo with arg {foo_arg}")
@click.command()
@click.option('--bar-arg', default='zxcv')
def bar(bar_arg):
click.echo(f"Hello from bar with arg {bar_arg}")
@click.command()
@click.option('--mode', type=click.Choice(["foo", "bar"]))
def cli(mode):
if mode == "foo":
foo()
if mode == "bar":
bar()
if __name__ == '__main__':
cli()
在本例中,foo()
和bar()
应被视为嵌入到repo中的模块,并且可能还需要大量CLI选项,我不想用这些选项重载mainCLI.py
。出于上下文原因,我觉得foo()
的CLI选项应该位于foo
中。以下是我希望它如何工作
例1:
python -m cli --mode foo --foo-arg asdf
应该产生
Hello from foo with arg asdf
Hello from bar with arg zxcv
例2:
python -m cli --mode bar --bar-arg zxcv
应该产生
Hello from foo with arg asdf
Hello from bar with arg zxcv
例3:
python -m cli --mode foo --bar-arg qwer
应该失败,因为foo()
没有--bar arg
选项
免责声明:我知道我可以将foo
和bar
注册为单独的命令(通过python-m cli foo--foo arg asd调用,即使用foo
而不是--foo
)。但是,由于超出此问题范围的原因,我需要由--mode
选项标识符指定foo
或bar
。这是与我的应用程序交互的工具的一个限制,不幸的是,这超出了我的控制范围
是否有一种方法可以解析arg并基于arg的子集使控制流成为可能,然后将剩余的arg传递给后续模块,而不是将每个模块的选项定义为def cli()
?上的装饰器?您可以使用回调进行验证:
另外,使用多链接命令,这样每个参数都将传递给它所需的命令
使用选项调用子命令可以通过使用click.multi命令和自定义parse_args()
方法实现,如:
自定义类
使用自定义类
要使用自定义类,请调用mode\u opts\u cmds()
函数创建自定义类,然后使用cls
参数将该类传递给click.group()
装饰器
@click.group(cls=mode_opts_cmds('mode'))
@click.option('--mode', type=click.Choice(["foo", "bar"]))
def cli(mode):
"""My wonderful cli"""
这是怎么回事?
这是因为click是一个设计良好的OO框架。@click.group()
decorator通常实例化click.group
对象,但允许使用cls
参数过度处理此行为。因此,在我们自己的类中继承click.Group
并超越所需的方法是一件相对容易的事情
在本例中,我们跳过了单击.Group.parse_args()
,这样在解析命令行时,我们只需删除--mode
,然后命令行就可以像普通子命令一样进行解析
使其成为一个库函数
如果自定义类将驻留在库中,则需要传入名称空间,而不是使用库文件中的默认globals()
。然后,需要使用以下内容调用自定义类创建者方法:
@click.group(cls=mode_opts_cmds('mode', namespace=globals()))
测试代码
测试结果
我认为问题在于我需要由选项而不是命令来驱动逻辑。这意味着在python cli.py
之外,后面的所有内容都必须采用--选项名option\u值
格式。我知道这是一个有点奇怪的要求,但我正在编写的应用程序是由一个外部控制器调用的,该控制器只能使用选项名称和值的字典(而不是命令名称)来形成命令。我可以让上面的示例使用命令,例如python cli.py foo--foo arg asdf
,但不是python cli.py--mode foo--foo arg asdf
(这是必需的)。我看到的唯一问题是当我尝试将mode\u opts\u cmds()
移动到单独的utils模块中并导入它时。在这种情况下,我得到了KeyError:'foo'
。你有什么建议吗?我将把foo
和bar
放在cli()
旁边的同一个文件中。我移动了自定义类def mode\u opts\u cmds(mode\u opt\u name)
。。。在中单击utils.py
并在cli.py
顶部单击utils导入模式选择cmds
。这是当它失败并产生KeyError:'foo'
错误时。很抱歉,您是正确的,我使用了globals()
,它将特定于类定义所在的文件。检查更新以获得处理此问题的一种方法。是否可以将其修改为以任意顺序接受参数,如--foo arg asdf--mode foo
?我问这个问题的原因是,与我的应用程序交互的外部服务按字母顺序对CL参数进行排序,这是我无法控制的。@BenD,当然可以。需要做的不是简单地args.remove(self.mode\u opt\u name)
,而是跟踪---mode
在列表中的位置,然后将以下arg移动到列表的前面。
Click Version: 7.0
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> --mode foo --foo-arg asdf
Hello from foo with arg asdf
-----------
> --mode bar --bar-arg zxcv
Hello from bar with arg zxcv
-----------
> --mode foo --bar-arg qwer
Usage: test.py foo [OPTIONS]
Try "test.py foo --help" for help.
Error: no such option: --bar-arg
-----------
> --mode foo --help
Usage: test.py foo [OPTIONS]
the foo command is ok
Options:
--foo-arg TEXT
--help Show this message and exit.
-----------
>
Usage: test.py [OPTIONS] COMMAND [ARGS]...
My wonderful cli command
Options:
--mode [foo|bar]
--help Show this message and exit.
Commands:
bar bar is my favorite
foo the foo command is ok