Argparse optional arguments with multilevel parser/subparser

你说的曾经没有我的故事 提交于 2021-02-11 15:40:37

问题


I have a set of parsers and subparsers to build a production or development system. If the user picks production, he can add options and all is well.
If he pics development, he can enter an architecture and then enter build options. This is where it gets sticky. I want him to be able to select build option 'comms' 'server' or 'all', but if he picks server, he has more choices.

My implementation is below. I tried combinations of parsers and subpasers (it seems that arguments can only be added to parsers, not subparsers, is that correct?)

It falls apart for 2 reasons:
1) I can only select arch or build and I need to select both
2) If I select build it always requires that I pick 'server', even if I pick one of the other two.

So I would like something like this ./buildServer.py Dev arch -arm build -comms or ./buildServer.py Dev arch -arm build -server -tcp

I'd appreciate and help/guidance I can get - TIA

Code:

def verify():

main_parser = argparse.ArgumentParser()     
main_subparsers = main_parser.add_subparsers(title="main", dest="main_command")          

# parser production choices                                                                                                     
prod_parser = main_subparsers.add_parser("prod", help="Prod")     
prod_parser.add_argument("-c",  "--change",   action='store_true', dest="change_sig", default=False, help="Change signature information (default = %(default)s)")
prod_parser.add_argument("-sd", "--sign-deb",  action='store_true', dest="sign_deb",   default=False, help="Add signature to the .deb file (default = %(default)s)")
prod_parser.add_argument ("-i",  "--install",  action='store_true', dest="build_deb" , default=False, help="Build .deb file from existing structure (default = %(default)s)")

# parser for development                                                                                                   
dev_parser = main_subparsers.add_parser("Dev", help="Dev")                                                                                                              
dev_subparser = dev_parser.add_subparsers(title="devsubparser")     

# optional development architecture choices
action_arch_parser = dev_subparser.add_parser("arch", help="architecture")
dev_arch_group = action_arch_parser.add_mutually_exclusive_group()
dev_arch_group.add_argument("-x86",  action='store_const', dest="architecture", const='x', default='x',help="Build dev code on X86")
dev_arch_group.add_argument("-arm",  action='store_const', dest="architecture",     const='a', help="Build dev code on arm")

# development build choices - 2 arguments (coms / all) and a third (server) that has its own options.
dev_build_parser = dev_subparser.add_parser("build", help="build")
dev_build_parser.add_argument("-comms", action='store_true',  help="Build comms program")
dev_build_parser.add_argument("-all", action='store_true',  help="Build all programs")
server_parser = dev_build_parser.add_subparsers(title="server", help="server subparser")
server_parser_p = server_parser.add_parser("server", help="server parser")
server_parser_p.add_argument("-tcp", help="tcp option")
server_parser_p.add_argument("-fips", help="fips option")
server_parser_p.add_argument("-sim", help="sim option")


args = main_parser.parse_args()  

回答1:


aparser.add_argument(...) creates an Action class object, actually a subclass specified by the action parameter. That action/argument may be positional or optional (flagged).

sp=aparse.add_subparsers(...) is specialized version of add_argument that creates an Action of the subparser class. print(sp) will show some of its attributes. This is actually a positional argument, with a choices attribute.

p1 = sp.add_parser(...) creates an argparse.ArgumentParser() object, and links it to a name that is placed in the sp.choices list. The parser is returned as the p1 variable.

p1.add_argument(...) defines a Action/argument, just as done with the main parser.

So during parsing, the inputs (sys.argv) are handled one by one, as is usual. But when it hits a string that matches in position and name of a "p1", the parsing task is passed on to p1 (with what remains of the sys.argv list). When p1 has reached the end of sys.argv its namespace and control is passed to the parent parser. The parent doesn't do any further parsing.

In your case you can pass down through several levels of parsers. (the terms, parser and subparsers can be a bit confusing, at least in the description. They are clearly defined in the code.)

This subparser mechanism allows only one choice (at each level). This isn't a multilevel tree transversal tool. You go down one particular thread to end, and back up.

So with

./buildServer.py Dev arch -arm build -comms or ./buildServer.py Dev arch -arm build -server -tcp

main_parser   (sees 'Dev', passes control to:)
    dev_parser   (sees 'arch', passes control to:)
        action_arch_parser
            (sees -arm - sets the const)
            (doesn't have any positional to handle 'build'
                  returns with an unrechognized arguments)

There may be ways of working around this - I or others have suggested things like this in previous SO - but the straight forward argparse usage does not allow for multiple subparsers (just the nesting that you show).



来源:https://stackoverflow.com/questions/62250816/argparse-optional-arguments-with-multilevel-parser-subparser

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!