簡體   English   中英

帶有常用選項的 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 Click。 它具有豐富的功能,但它不允許(至少我沒有發現)在沒有一些主要命令的情況下使用通用選項。

使用 Click 時,上面的示例將如下所示

$ 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

這是使用文檔中建議的回調模型實現管道的示例:

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()

每個命令返回一個布爾值,指示它是否成功。 失敗的命令將中止管道處理。 例如,這里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

您可以使用argparse在沒有main command情況下main command操作。

# 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'}

JFYI。 我與Clickargparse一起工作。 argparse在我看來更具擴展性和功能性。

希望這可以幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM