带有子命令的命令和子命令请求的 argparse 解决方案

Posted

技术标签:

【中文标题】带有子命令的命令和子命令请求的 argparse 解决方案【英文标题】:argparse solution requested for commands and subcommands with subcommands 【发布时间】:2020-01-14 12:31:57 【问题描述】:

希望这将转化为一个优雅的解决方案,但我自己无法弄清楚。我一直在阅读大量示例和解释,但我似乎无法让它发挥作用。

我正在编写一个需要以下选项的程序:

myprogram:
-h              help
--DEBUG         debugging on

Commands are: dashboard / promote / deploy / auto-tag / dbquery
Some commands have sub-commands.

Dashboard prosesing:
  dashboard <ALL | DEBUG | AAA [sub1-aaa |  ...] >

Promoting:
  promote <environment> [to environment] <system> [version]

Deployment
  deploy <system> [version] <in environment>

Auto-tagging
  auto-tag <repo> <project> [--days-back]

Database queries  (--tab is optional)
         (system or env or both are required)
  dbquery lock set    <system | environment>   [--tab]
  dbquery lock clear  <system | environment>   [--tab]
  dbquery lock show   <system | environment | before-date | after-date>   [--tab]

  dbquery promote list <system| version| environment | before-date | after-date>  [--tab]
  dbquery deploy list <system| version| environment | before-date | after-date>   [--tab]

如果使用命令,则需要一些子命令或选项。

我很难使用 argparse 库来完成这项工作。我尝试使用 add_argument_group、subparsers 等。但我认为我在这里缺少一些基本的东西。我发现接近的所有例子都是关于 svn 的,但它们似乎只在 svn 之后上升了 1 级。我需要更多,或者不同的方法。

如果可能的话,我想让 dbquery deploy list 之后的所有参数都是可选的,至少需要 1 个选项。但是要区分系统名称和环境名称可能会变得很棘手,因此最好更改一下:

dbquery lock set    <system | environment>

进入

dbquery lock set    <system=system | env=environment>

附言[] 之间的选项是可选的, 之间的选项是必需的。

提前致谢。

针对提供我的代码的评论,让我们专注于 dbquery,因为其余的可能是重复的:

import argparse
parser = argparse.ArgumentParser(description="Main cli tool for processing in the CD pipeline (%s)" % VERSION)
subparsers = parser.add_subparsers(help='additional help',title='subcommands')
dbq_parser=subparsers.add_parser("dbqueue", aliases=['dbq'])
dbq_group_lock = dbq_parser.add_argument_group('lock', 'lock desc')
dbq_group_promote =dbq_parser.add_argument_group('promote')
dbq_group_deploy = dbq_parser.add_argument_group('deploy','Deployment description')

dbq_group_lock.add_argument('set', help="Sets lock")
dbq_group_lock.add_argument('clear', help='Clears lock')
dbq_group_lock.add_argument('show', help='Show lock status')

dbq_group_deploy.add_argument('name system etc')

执行结果:

# python3 main.py -h
usage: main.py [-h] [--debug] dbqueue,dbq ...

Main cli tool for processing in the CD pipeline (cdv3, Jun 2019, F.IJskes)

optional arguments:
  -h, --help     show this help message and exit
  --debug        Generate debug output and keep temp directories

subcommands:
  dbqueue,dbq  additional help

这看起来不错,但是:

#python3 main.py dbq -h
usage: main.py dbqueue [-h] set clear show name system etc

optional arguments:
  -h, --help       show this help message and exit

lock:
  lock desc

  set              Sets lock
  clear            Clears lock
  show             Show lock status

表明预期的参数不是锁定、提升或部署。

好的,反馈有助于我理解。我现在明白解析器可以得到子解析器,而那些可以得到解析器。因此,一个人可以去的深度可能没有限制。 这个新见解让我明白了这一点:(我的工作示例的部分副本)


import argparse
main_parser = argparse.ArgumentParser(description="Main cli tool for processing in the CD pipeline (%s)" % VERSION)

main_subparsers = main_parser.add_subparsers(help='',title='Possible commnds')

dashbrd_subparser = main_subparsers.add_parser('dashboard', help="Proces specified dashboards", allow_abbrev=True)
dashbrd_subparser.add_argument('who?',help='Proces dashboard for ALL, Supplier or DEBUG what should happen.')
dashbrd_subparser.add_argument('-subsystem', help='Select subsystem to proces for given supplier')

dbq_main=main_subparsers.add_parser("dbquery", help="Query database for locks,deployments or promotes")
dbq_main_sub=dbq_main.add_subparsers(help="additions help", title='sub commands for dbquery')
dbq_lock=dbq_main_sub.add_parser('lock', help='query db for lock(s)')
dbq_lock_sub=dbq_lock.add_subparsers(help='', title='subcommands for lock')
dbq_lock_sub_set=dbq_lock_sub.add_parser('set', help='sets a lock')
dbq_lock_sub_set.add_argument('-env', required=True)
dbq_lock_sub_set.add_argument('--tab',required=False, action="store_true" )
dbq_lock_sub_clear=dbq_lock_sub.add_parser('clear', help='clears a lock')
# dbq_lock_sub_set.add_argument('-env', required=True)
# dbq_lock_sub_set.add_argument('--tab', required=False)
dbq_lock_sub_show=dbq_lock_sub.add_parser('show', help='shows a lock/locks')
# dbq_lock_sub_set.add_argument('-env', required=True)
# dbq_lock_sub_set.add_argument('--tab', required=False)

print( vars(main_parser.parse_args()))
exit(1)

我现在似乎只在不同子命令中使用参数作为“-env”和“-subsystem”时遇到问题。因为当我将它们添加到另一个解析器时存在冲突。 我也没有关于选择哪些选项的数据。这也是需要的。

【问题讨论】:

我们更愿意回答实际上有 argparse 代码的问题,无论是否有效。它应该关注问题的问题,而不是所有的细节。对于它的价值,子解析器可以定义自己的子解析器参数。 你说得有道理。我有很多尝试,我希望找到一些通用的答案来解决。我刚刚发布了我最近的尝试。 lock 只是一个参数组,一个帮助显示工具。它不会改变解析。这不是subparsers。请改用dbq_parser,add_subparsers 使用add_subparsers(dest='cmd1',...) 将记录使用了哪个子解析器命令。如果您选择 required=True 并期待正确的错误消息,这也会有所帮助。 如果您想为所有命令提供一个参数,例如-env,请考虑在 main 中定义它 - 尽管在这种情况下顺序确实很重要。或者为每个适用的子解析器定义它。您可以使用实用函数(或parents)简化它。 【参考方案1】:

差不多了,我现在拥有的东西在很大程度上可以使用,所以我会发布这个,以便其他用户可以从我的工作中受益,这得益于 hpaulj 的 cmets,这让我找到了可以澄清的其他文件其他部分。

# see: https://pymotw.com/3/argparse/

# BLOCK FOR SHARED options used at more places,
# see also: https://***.com/questions/7498595/python-argparse-add-argument-to-multiple-subparsers
env_shared = argparse.ArgumentParser(add_help=False)
env_shared.add_argument('-env', action="store", help='name of environment <tst|acc|acc2|prod|...>', metavar='<environment>', required=True)
ds_shared = argparse.ArgumentParser(add_help=False)
ds_shared.add_argument('-ds', action="store", help='name of subsystem to use <ivs-gui|ivs-vpo|...>', metavar='<system name>', required=True)
version_shared = argparse.ArgumentParser(add_help=False)
version_shared.add_argument('-version', action="store", help='version to use. If used, specify at least sprint as in 1.61', metavar='<version>')
tab_shared=argparse.ArgumentParser(add_help=False)
tab_shared.add_argument('--tab', required=False, action="store_true", help='Use nice tabulation for output')
# END OF SHARED options

# MAIN
main_p = argparse.ArgumentParser(description="Main cli tool for use in the CD v3 pipeline (%s)" % VERSION, epilog='Defaults are taken from the configuration file, which is pulled from git when needed.', )
# Change formatting of help output for main-parser
main_p.formatter_class = lambda prog: argparse.HelpFormatter(prog, max_help_position=30, width=80)
main_p.add_argument("--debug", action="store_true", help="Generate debug output and keep temp directories")

# MAIN-SUB
main_s_p = main_p.add_subparsers(title='Commands', dest='main_cmd')

# MAIN-SUB-DASHBOARD
dashbrd_p = main_s_p.add_parser('dashboard', aliases=['db'], help="Proces specified dashboards", parents=[ds_shared])
dashbrd_p.formatter_class = lambda prog: argparse.HelpFormatter(prog, max_help_position=40, width=80)
dashbrd_p.add_argument('which', help='Proces dashboard for <ALL|Supplier|DEBUG what would happen>. If supplier is specified an optional subsystem to be processed can be passed using -ds')

# MAIN-SUB-QUERY
query_p = main_s_p.add_parser("query", aliases=['qry'], help="Query database for locks,deployments or promotes")
query_p.formatter_class = lambda prog: argparse.HelpFormatter(prog, max_help_position=40, width=80)
# MAIN-SUB-QUERY-SUB
query_s_p = query_p.add_subparsers(help="additions help", title='sub commands for query', dest='query_cmd')
# MAIN-SUB-QUERY-LOCK
q_lock_p = query_s_p.add_parser('lock', help='query db for lock(s)', )
# MAIN-SUB-QUERY-LOCK-SUB
q_lock_sub = q_lock_p.add_subparsers(help='', title='subcommands for dbquery lock', dest='lock_cmd')
# MAIN-SUB-QUERY-LOCK-SUB-SET
q_lock_set_p = q_lock_sub.add_parser('set', help='sets a lock', parents=[env_shared, ds_shared])
# MAIN-SUB-QUERY-LOCK-SUB-CLEAR
q_lock_clear_p = q_lock_sub.add_parser('clear', help='clears a lock', parents=[env_shared, ds_shared], )
# MAIN-SUB-QUERY-LOCK-SUB-SHOW
q_lock_show_p = q_lock_sub.add_parser('show', help='shows a lock/locks', parents=[env_shared, tab_shared])


# MAIN-SUB-QUERY-PROMOTE
q_promote_p = query_s_p.add_parser('promote', help='query db for promotions', parents=[env_shared,ds_shared] )

# MAIN-SUB-QUERY-DEPLOY
q_deploy_p = query_s_p.add_parser('deploy', help='query db for deployments')

# MAIN-SUB-PROMOTE
promote_p = main_s_p.add_parser('promote', help='Do a promotion.', parents=[env_shared, ds_shared, version_shared])
promote_p.add_argument('-dest', required=False, action="store", help='If used specifies the destination, otherwise defaults are used. (-dest is optional, -env is not)', metavar='<dest_env>')

# MAIN-SUB-DEPLOY
deploy_p = main_s_p.add_parser('deploy', help='Deploy software', parents=[ds_shared, env_shared])

# MAIN-SUB-AUTOTAG
autotag_p = main_s_p.add_parser('autotag', help="Autotags specified repository", parents=[ds_shared])

print("------------arguments-----------")
print(vars(main_p.parse_args()))

这创造了很多我想要的功能。

【讨论】:

以上是关于带有子命令的命令和子命令请求的 argparse 解决方案的主要内容,如果未能解决你的问题,请参考以下文章

parser.add_argument()用法——命令行选项参数和子命令解析器

带有通用子解析器命令的 Python argparse

如何有一个特定的子命令需要带有 argparse 的标志?

带有嵌套命名空间的 argparse 子命令

带有可选子命令的 argparse 未按预期工作

argparse详细使用手册