argparse optional subparser (for --version)

前端 未结 7 1378
长情又很酷
长情又很酷 2020-12-09 03:38

I have the following code (using Python 2.7):

# shared command line options, like --version or --verbose
parser_shared = argparse.ArgumentParser(add_help=Fal         


        
相关标签:
7条回答
  • 2020-12-09 04:18

    FWIW, I ran into this also, and ended up "solving" it by not using subparsers (I already had my own system for printing help, so didn't lose anything there).

    Instead, I do this:

    parser.add_argument("command", nargs="?",
                        help="name of command to execute")
    
    args, subcommand_args = parser.parse_known_args()
    

    ...and then the subcommand creates its own parser (similar to a subparser) which operates only on subcommand_args.

    0 讨论(0)
  • According to documentation, --version with action='version' (and not with action='store_true') prints automatically the version number:

    parser.add_argument('--version', action='version', version='%(prog)s 2.0')
    
    0 讨论(0)
  • 2020-12-09 04:22

    Yeah, I just checked svn, which is used as an object example in the add_subparsers() documentation, and it only supports '--version' on the main command:

    python zacharyyoung$ svn log --version
    Subcommand 'log' doesn't accept option '--version'
    Type 'svn help log' for usage.
    

    Still:

    # create common parser
    parent_parser = argparse.ArgumentParser('parent', add_help=False)
    parent_parser.add_argument('--version', action='version', version='%(prog)s 2.0')
    
    # create the top-level parser
    parser = argparse.ArgumentParser(parents=[parent_parser])
    subparsers = parser.add_subparsers()
    
    # create the parser for the "foo" command
    parser_foo = subparsers.add_parser('foo', parents=[parent_parser])
    

    Which yields:

    python zacharyyoung$ ./arg-test.py --version
    arg-test.py 2.0
    python zacharyyoung$ ./arg-test.py foo --version
    arg-test.py foo 2.0
    
    0 讨论(0)
  • 2020-12-09 04:23

    This seems to implement the basic idea of an optional subparser. We parse the standard arguments that apply to all subcommands. Then, if anything is left, we invoke the parser on the rest. The primary arguments are a parent of the subcommand so the -h appears correctly. I plan to enter an interactive prompt if no subcommands are present.

    import argparse
    
    p1 = argparse.ArgumentParser( add_help = False )    
    p1.add_argument( ‘–flag1′ )
    
    p2 = argparse.ArgumentParser( parents = [ p1 ] )
    s = p2.add_subparsers()
    p = s.add_parser( ‘group’ )
    p.set_defaults( group=True )
    
    ( init_ns, remaining ) = p1.parse_known_args( )
    
    if remaining:
        p2.parse_args( args = remaining, namespace=init_ns )
    else:
        print( ‘Enter interactive loop’ )
    
    print( init_ns )
    
    0 讨论(0)
  • 2020-12-09 04:30

    Although @eumiro's answer address the --version option, it can only do so because that is a special case for optparse. To allow general invocations of:

     prog
     prog --verbose
     prog --verbose main
     prog --verbose db 
    

    and have prog --version work the same as prog --verbose main (and prog main --verbose) you can add a method to Argumentparser and call that with the name of the default subparser, just before invoking parse_args():

    import argparse
    import sys
    
    def set_default_subparser(self, name, args=None):
        """default subparser selection. Call after setup, just before parse_args()
        name: is the name of the subparser to call by default
        args: if set is the argument list handed to parse_args()
    
        , tested with 2.7, 3.2, 3.3, 3.4
        it works with 2.6 assuming argparse is installed
        """
        subparser_found = False
        for arg in sys.argv[1:]:
            if arg in ['-h', '--help']:  # global help if no subparser
                break
        else:
            for x in self._subparsers._actions:
                if not isinstance(x, argparse._SubParsersAction):
                    continue
                for sp_name in x._name_parser_map.keys():
                    if sp_name in sys.argv[1:]:
                        subparser_found = True
            if not subparser_found:
                # insert default in first position, this implies no
                # global options without a sub_parsers specified
                if args is None:
                    sys.argv.insert(1, name)
                else:
                    args.insert(0, name)
    
    argparse.ArgumentParser.set_default_subparser = set_default_subparser
    
    def do_main(args):
        print 'main verbose', args.verbose
    
    def do_db(args):
        print 'db verbose:', args.verbose
    
    parser = argparse.ArgumentParser()
    parser.add_argument('--verbose', action='store_true')
    parser.add_argument('--version', action='version', version='%(prog)s 2.0')
    subparsers = parser.add_subparsers()
    sp = subparsers.add_parser('main')
    sp.set_defaults(func=do_main)
    sp.add_argument('--verbose', action='store_true')
    sp = subparsers.add_parser('db')
    sp.set_defaults(func=do_db)
    
    parser.set_default_subparser('main')
    args = parser.parse_args()
    
    if hasattr(args, 'func'):
        args.func(args)
    

    The set_default_subparser() method is part of the ruamel.std.argparse package.

    0 讨论(0)
  • 2020-12-09 04:36

    As discussed in http://bugs.python.org/issue9253 (argparse: optional subparsers), as of Python 3.3, subparsers are now optional. This was an unintended result of a change in how parse_args checked for required arguments.

    I found a fudge that restores the previous (required subparsers) behavior, explicitly setting the required attribute of the subparsers action.

    parser = ArgumentParser(prog='test')
    subparsers = parser.add_subparsers()
    subparsers.required = True   # the fudge
    subparsers.dest = 'command'
    subparser = subparsers.add_parser("foo", help="run foo")
    parser.parse_args()
    

    See that issue for more details. I expect that if and when this issue gets properly patched, subparsers will be required by default, with some sort of option to set its required attribute to False. But there is a big backlog of argparse patches.

    0 讨论(0)
提交回复
热议问题