Python 具有常用选项的多命令 CLI
Python multi-command CLI with common options
我正在为我的 Python 应用程序添加 CLI。 CLI 应该允许一次 运行 多个命令。这些命令应该有公共选项和个人选项。
示例:
$ python mycliapp.py --common-option1 value1 --common-option2 value2 cmd1 --cmd1-option cmd2 --cmd2-option somevalue cmd3
示例有两个所有命令使用的公共选项,每个命令可以有或没有仅命令使用的选项。
我考虑过Python 点击。它具有丰富的功能,但它不允许(至少我没有找到)在没有一些主要命令的情况下使用常用选项。
上面的例子点击后会是这样:
$ python mycliapp.py maincmd --common-option1 value1 --common-option2 value2 cmd1 --cmd1-option cmd2 --cmd2-option somevalue cmd3
此外,考虑 Python Argparse。看起来它可以做我需要的,我已经设法编写了一个代码,它可以使用通用选项和单个命令,但不能设法使用多个命令。
此页面 Python argparse - Add argument to multiple subparsers 有很好的示例,但似乎 command2 应该是 command1 的子命令。这有点不同,因为我需要命令可以按任何顺序执行。
Click 绝对支持这种语法。一个简单的示例如下所示:
import click
@click.group(chain=True)
@click.option('--common-option1')
@click.option('--common-option2')
def main(common_option1, common_option2):
pass
@main.command()
@click.option('--cmd1-option', is_flag=True)
def cmd1(cmd1_option):
pass
@main.command()
@click.option('--cmd2-option')
def cmd2(cmd2_option):
pass
@main.command()
def cmd3():
pass
if __name__ == '__main__':
main()
假设以上在mycliapp.py
中,我们看到常见的帮助输出:
$ python example.py --help
Usage: example.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
Options:
--common-option1 TEXT
--common-option2 TEXT
--help Show this message and exit.
Commands:
cmd1
cmd2
cmd3
对于cmd1
:
$ python mycliapp.py cmd1 --help
Usage: mycliapp.py cmd1 [OPTIONS]
Options:
--cmd1-option
--help Show this message and exit.
对于cmd2
:
$ python mycliapp.py cmd2 --help
Usage: mycliapp.py cmd2 [OPTIONS]
Options:
--cmd2-option TEXT
--help Show this message and exit.
等等
有了这个我们可以运行你问题的命令行:
python mycliapp.py --common-option1 value1 --common-option2 value2 \
cmd1 --cmd1-option \
cmd2 --cmd2-option somevalue \
cmd3
更新 1
这是一个使用建议的回调模型实现管道的示例 in the documentation:
import click
@click.group(chain=True)
@click.option('--common-option1')
@click.option('--common-option2')
@click.pass_context
def main(ctx, common_option1, common_option2):
ctx.obj = {
'common_option1': common_option1,
'common_option2': common_option2,
}
@main.resultcallback()
def process_pipeline(processors, common_option1, common_option2):
print('common_option1 is', common_option1)
for func in processors:
res = func()
if not res:
raise click.ClickException('Failed processing!')
@main.command()
@click.option('--cmd1-option', is_flag=True)
def cmd1(cmd1_option):
def process():
print('This is cmd1')
return cmd1_option
return process
@main.command()
@click.option('--cmd2-option')
def cmd2(cmd2_option):
def process():
print('This is cmd2')
return cmd2_option != 'fail'
return process
@main.command()
@click.pass_context
def cmd3(ctx):
def process():
print('This is cmd3 (common option 1 is: {common_option1}'.format(**ctx.obj))
return True
return process
if __name__ == '__main__':
main()
每个命令returns一个布尔值,指示它是否成功。失败的命令将中止管道处理。例如,此处 cmd1
失败,因此 cmd2
永远不会执行:
$ python mycliapp.py cmd1 cmd2
This is cmd1
Error: Failed processing!
但如果我们让 cmd1
开心,它就会起作用:
$ python mycliapp.py cmd1 --cmd1-option cmd2
This is cmd1
This is cmd2
同样,比较一下:
$ python mycliapp.py cmd1 --cmd1-option cmd2 --cmd2-option fail cmd3
This is cmd1
This is cmd2
Error: Failed processing!
有了这个:
$ python mycliapp.py cmd1 --cmd1-option cmd2 cmd3
This is cmd1
This is cmd2
This is cmd3
当然你不需要按顺序调用:
$ python mycliapp.py cmd2 cmd1 --cmd1-option
This is cmd2
This is cmd1
不用 main command
使用 argparse
也可以做到。
# maincmd just to tie between arguments and subparsers
parser = argparse.ArgumentParser(prog='maincmd')
parser.add_argument('--common-option1', type=str, required=False)
parser.add_argument('--common-option2', type=str, required=False)
main_subparsers = parser.add_subparsers(title='sub_main', dest='sub_cmd')
parser_cmd1 = main_subparsers.add_parser('cmd1', help='help cmd1')
parser_cmd1.add_argument('--cmd1-option', type=str, required=False)
cmd1_subparsers = parser_cmd1.add_subparsers(title='sub_cmd1', dest='sub_cmd1')
parser_cmd2 = cmd1_subparsers.add_parser('cmd2', help='help cmd2')
options = parser.parse_args(sys.argv[1:])
print(vars(options))
让我们检查一下:
python test.py --common-option1 value1 --common-option2 value2
#{'common_option1': 'value1', 'common_option2': 'value2', 'sub_cmd': None}
python test.py --common-option1 value1 --common-option2 value2 cmd1
# {'common_option1': 'value1', 'common_option2': 'value2', 'sub_cmd': 'cmd1', 'cmd1_option': None, 'sub_cmd1': None}
python test.py --common-option1 value1 --common-option2 value2 cmd1 --cmd1-option cmd1-val
# {'common_option1': 'value1', 'common_option2': 'value2', 'sub_cmd': 'cmd1', 'cmd1_option': 'cmd1-val', 'sub_cmd1': None}
python test.py --common-option1 value1 --common-option2 value2 cmd1 --cmd1-option cmd1-val cmd2
# {'common_option1': 'value1', 'common_option2': 'value2', 'sub_cmd': 'cmd1', 'cmd1_option': 'cmd1-val', 'sub_cmd1': 'cmd2'}
仅供参考。我与 Click
和 argparse
一起工作。 argparse
在我看来更具扩展性和功能性。
希望这对您有所帮助。
我正在为我的 Python 应用程序添加 CLI。 CLI 应该允许一次 运行 多个命令。这些命令应该有公共选项和个人选项。
示例:
$ python mycliapp.py --common-option1 value1 --common-option2 value2 cmd1 --cmd1-option cmd2 --cmd2-option somevalue cmd3
示例有两个所有命令使用的公共选项,每个命令可以有或没有仅命令使用的选项。
我考虑过Python 点击。它具有丰富的功能,但它不允许(至少我没有找到)在没有一些主要命令的情况下使用常用选项。
上面的例子点击后会是这样:
$ python mycliapp.py maincmd --common-option1 value1 --common-option2 value2 cmd1 --cmd1-option cmd2 --cmd2-option somevalue cmd3
此外,考虑 Python Argparse。看起来它可以做我需要的,我已经设法编写了一个代码,它可以使用通用选项和单个命令,但不能设法使用多个命令。 此页面 Python argparse - Add argument to multiple subparsers 有很好的示例,但似乎 command2 应该是 command1 的子命令。这有点不同,因为我需要命令可以按任何顺序执行。
Click 绝对支持这种语法。一个简单的示例如下所示:
import click
@click.group(chain=True)
@click.option('--common-option1')
@click.option('--common-option2')
def main(common_option1, common_option2):
pass
@main.command()
@click.option('--cmd1-option', is_flag=True)
def cmd1(cmd1_option):
pass
@main.command()
@click.option('--cmd2-option')
def cmd2(cmd2_option):
pass
@main.command()
def cmd3():
pass
if __name__ == '__main__':
main()
假设以上在mycliapp.py
中,我们看到常见的帮助输出:
$ python example.py --help
Usage: example.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
Options:
--common-option1 TEXT
--common-option2 TEXT
--help Show this message and exit.
Commands:
cmd1
cmd2
cmd3
对于cmd1
:
$ python mycliapp.py cmd1 --help
Usage: mycliapp.py cmd1 [OPTIONS]
Options:
--cmd1-option
--help Show this message and exit.
对于cmd2
:
$ python mycliapp.py cmd2 --help
Usage: mycliapp.py cmd2 [OPTIONS]
Options:
--cmd2-option TEXT
--help Show this message and exit.
等等
有了这个我们可以运行你问题的命令行:
python mycliapp.py --common-option1 value1 --common-option2 value2 \
cmd1 --cmd1-option \
cmd2 --cmd2-option somevalue \
cmd3
更新 1
这是一个使用建议的回调模型实现管道的示例 in the documentation:
import click
@click.group(chain=True)
@click.option('--common-option1')
@click.option('--common-option2')
@click.pass_context
def main(ctx, common_option1, common_option2):
ctx.obj = {
'common_option1': common_option1,
'common_option2': common_option2,
}
@main.resultcallback()
def process_pipeline(processors, common_option1, common_option2):
print('common_option1 is', common_option1)
for func in processors:
res = func()
if not res:
raise click.ClickException('Failed processing!')
@main.command()
@click.option('--cmd1-option', is_flag=True)
def cmd1(cmd1_option):
def process():
print('This is cmd1')
return cmd1_option
return process
@main.command()
@click.option('--cmd2-option')
def cmd2(cmd2_option):
def process():
print('This is cmd2')
return cmd2_option != 'fail'
return process
@main.command()
@click.pass_context
def cmd3(ctx):
def process():
print('This is cmd3 (common option 1 is: {common_option1}'.format(**ctx.obj))
return True
return process
if __name__ == '__main__':
main()
每个命令returns一个布尔值,指示它是否成功。失败的命令将中止管道处理。例如,此处 cmd1
失败,因此 cmd2
永远不会执行:
$ python mycliapp.py cmd1 cmd2
This is cmd1
Error: Failed processing!
但如果我们让 cmd1
开心,它就会起作用:
$ python mycliapp.py cmd1 --cmd1-option cmd2
This is cmd1
This is cmd2
同样,比较一下:
$ python mycliapp.py cmd1 --cmd1-option cmd2 --cmd2-option fail cmd3
This is cmd1
This is cmd2
Error: Failed processing!
有了这个:
$ python mycliapp.py cmd1 --cmd1-option cmd2 cmd3
This is cmd1
This is cmd2
This is cmd3
当然你不需要按顺序调用:
$ python mycliapp.py cmd2 cmd1 --cmd1-option
This is cmd2
This is cmd1
不用 main command
使用 argparse
也可以做到。
# maincmd just to tie between arguments and subparsers
parser = argparse.ArgumentParser(prog='maincmd')
parser.add_argument('--common-option1', type=str, required=False)
parser.add_argument('--common-option2', type=str, required=False)
main_subparsers = parser.add_subparsers(title='sub_main', dest='sub_cmd')
parser_cmd1 = main_subparsers.add_parser('cmd1', help='help cmd1')
parser_cmd1.add_argument('--cmd1-option', type=str, required=False)
cmd1_subparsers = parser_cmd1.add_subparsers(title='sub_cmd1', dest='sub_cmd1')
parser_cmd2 = cmd1_subparsers.add_parser('cmd2', help='help cmd2')
options = parser.parse_args(sys.argv[1:])
print(vars(options))
让我们检查一下:
python test.py --common-option1 value1 --common-option2 value2
#{'common_option1': 'value1', 'common_option2': 'value2', 'sub_cmd': None}
python test.py --common-option1 value1 --common-option2 value2 cmd1
# {'common_option1': 'value1', 'common_option2': 'value2', 'sub_cmd': 'cmd1', 'cmd1_option': None, 'sub_cmd1': None}
python test.py --common-option1 value1 --common-option2 value2 cmd1 --cmd1-option cmd1-val
# {'common_option1': 'value1', 'common_option2': 'value2', 'sub_cmd': 'cmd1', 'cmd1_option': 'cmd1-val', 'sub_cmd1': None}
python test.py --common-option1 value1 --common-option2 value2 cmd1 --cmd1-option cmd1-val cmd2
# {'common_option1': 'value1', 'common_option2': 'value2', 'sub_cmd': 'cmd1', 'cmd1_option': 'cmd1-val', 'sub_cmd1': 'cmd2'}
仅供参考。我与 Click
和 argparse
一起工作。 argparse
在我看来更具扩展性和功能性。
希望这对您有所帮助。