4

I would like to get the following functionality while using the add_subparsers method of the argparse library and without using the keyword argument nargs:

$ python my_program.py scream Hello
You just screamed Hello!!
$ python my_program.py count ten
You just counted to ten.

I know I could do this:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("cmd", help="Execute a command", action="store",
  nargs='*')
args = parser.parse_args()
args_list = args.cmd

if len(args.cmd) == 2:
    if args.cmd[0] == "scream":
        if args.cmd[1] == "Hello":
            print "You just screamed Hello!!"
        else:
            print "You just screamed some other command!!"

    elif args.cmd[0] == "count":
        if args.cmd[1]:
            print "You just counted to %s." % args.cmd[1]
        else:
            pass

    else:
        print "These two commands are undefined"

else:
    print "These commands are undefined"

But then when I do $ python my_program.py I lose that default arparse text that shows a list of arguments etc. .

I know there is an add_subparsers method of the argparse library that can handle more than one positional argument but I have not found a way to get it properly working. Could anyone show me how?

Bentley4
  • 10,678
  • 25
  • 83
  • 134

2 Answers2

7
import argparse

def scream(args):
    print "you screamed "+' '.join(args.words)

def count(args):
    print "you counted to {0}".format(args.count)

parser = argparse.ArgumentParser()

#tell the parser that there will be subparsers
subparsers = parser.add_subparsers(help="subparsers")

#Add parsers to the object that was returned by `add_subparsers`
parser_scream = subparsers.add_parser('scream')

#use that as you would any other argument parser
parser_scream.add_argument('words',nargs='*')

#set_defaults is nice to call a function which is specific to each subparser
parser_scream.set_defaults(func=scream) 

#repeat for our next sub-command
parser_count = subparsers.add_parser('count')
parser_count.add_argument('count')
parser_count.set_defaults(func=count)

#parse the args
args = parser.parse_args()
args.func(args)  #args.func is the function that was set for the particular subparser

now run it:

>python test.py scream Hello World!  #you screamed Hello World!
>python test.py count 10             #you counted to 10
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • When I do `$ python my_program` I get `usage: dummy.py [-h] {scream,count} ...` `my_program.py: error: too few arguments`. Any idea how I can gracefully get rid of that error line in a call without any arguments? – Bentley4 Sep 06 '12 at 19:44
  • 1
    @Bentley4 -- when you add subparsers, you're implying that one of them *should* be called. If you want it to do something different, you can mess with `sys.argv` ahead of time. (`if len(sys.argv) == 1: sys.exit()`) or `if len(sys.argv) == 1: sys.argv.append('scream')`... – mgilson Sep 06 '12 at 19:58
2

When using the add_subparsers you basically are creating a nested parser:

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='sub-command help')

parser_scream = subparsers.add_parser('scream', help='scream help')

Now you have a new parser object, that you can add switches to. Or, you can add another level of nesting:

scream_subparsers = parser_scream.add_subparsers(help='scream sub-command help')
parser_scream_hello = scream_subparsers.add_parser('hello', help='scream hello help')

This can go as deep as you need to to control exact formatting. Each level provides help:

>>> parser.print_help()
usage: [-h] {scream} ...

positional arguments:
  {scream}    sub-command help
    scream    scream help

optional arguments:
  -h, --help  show this help message and exit
>>> parser_scream.print_help()
usage:  scream [-h] {hello} ...

positional arguments:
  {hello}     scream sub-command help
    hello     scream hello help

optional arguments:
  -h, --help  show this help message and exit
>>> parser_scream_hello.print_help()
usage: scream hello [-h]

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

You can have each end-point call a function, by using set_defaults(func=yourfunction) on the subparser in question, then using that default func argument to call the selected function for the current arguments:

>>> def scream_hello(args):
...     print "You screamed hello!"
...
>>> parser_scream_hello.set_defaults(func=scream_hello)
>>> parser.parse_args(['scream', 'hello'])
Namespace(func=<function scream_hello at 0x10bd73c80>)
>>> args = parser.parse_args(['scream', 'hello'])
>>> args.func(args)
You screamed hello!
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I chose mgilson's answer because I found his code example a bit easier to understand. But you gave a nice explanation, thank you! – Bentley4 Sep 06 '12 at 19:49