Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Argparse - How to Specify a Default Subcommand

Tags:

I am using the argparse package of Python 2.7 to write some option-parsing logic for a command-line tool. The tool should accept one of the following arguments:

"ON": Turn a function on.
"OFF": Turn a function off.
[No arguments provided]: Echo the current state of the function.

Looking at the argparse documentation led me to believe that I wanted two--possibly three--subcommands to be defined, since these three states are mutually exclusive and represent different conceptual activities. This is my current attempt at the code:

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser.set_defaults(func=print_state) # I think this line is wrong.

parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=set_state, newstate='ON')

parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=set_state, newstate='OFF')

args = parser.parse_args()

if(args.func == set_state):
    set_state(args.newstate)
elif(args.func == print_state):
    print_state()
else:
    args.func() # Catchall in case I add more functions later

I was under the impression that if I provided 0 arguments, the main parser would set func=print_state, and if I provided 1 argument, the main parser would use the appropriate subcommand's defaults and call func=set_state. Instead, I get the following error with 0 arguments:

usage: cvsSecure.py [-h] {ON,OFF} ...
cvsSecure.py: error: too few arguments

And if I provide "OFF" or "ON", print_state gets called instead of set_state. If I comment out the parser.set_defaults line, set_state is called correctly.

I'm a journeyman-level programmer, but a rank beginner to Python. Any suggestions about how I can get this working?

Edit: Another reason I was looking at subcommands was a potential fourth function that I am considering for the future:

"FORCE txtval": Set the function's state to txtval.

like image 564
Will Shipley Avatar asked Mar 03 '11 04:03

Will Shipley


People also ask

How do you add an optional argument in Argparse?

To add an optional argument, simply omit the required parameter in add_argument() . args = parser. parse_args()if args.

How do you make an argument mandatory in Python?

required is a parameter of the ArugmentParser object's function add_argument() . By default, the arguments of type -f or --foo are optional and can be omitted. If a user is required to make an argument, they can set the keyword argument required to True .

What is Argparse ArgumentParser ()?

The argparse module provides a convenient interface to handle command-line arguments. It displays the generic usage of the program, help, and errors. The parse_args() function of the ArgumentParser class parses arguments and adds value as an attribute dest of the object.


1 Answers

The defaults of the top-level parser override the defaults on the sub-parsers, so setting the default value of func on the sub-parsers is ignored, but the value of newstate from the sub-parser defaults is correct.

I don't think you want to use subcommands. Subcommands are used when the available options and positional arguments change depending on which subcommand is chosen. However, you have no other options or positional arguments.

The following code seems to do what you require:

import argparse

def print_state():
    print "Print state"

def set_state(s):
    print "Setting state to " + s

parser = argparse.ArgumentParser()
parser.add_argument('state', choices = ['ON', 'OFF'], nargs='?')

args = parser.parse_args()

if args.state is None:
    print_state()
elif args.state in ('ON', 'OFF'):
    set_state(args.state)

Note the optional parameters to parser.add_argument. The "choices" parameter specifies the allowable options, while setting "nargs" to "?" specifies that 1 argument should be consumed if available, otherwise none should be consumed.

Edit: If you want to add a FORCE command with an argument and have separate help text for the ON and OFF command then you do need to use subcommands. Unfortunately there doesn't seem to be a way of specifying a default subcommand. However, you can work around the problem by checking for an empty argument list and supplying your own. Here's some sample code illustrating what I mean:

import argparse
import sys

def print_state(ignored):
    print "Print state"

def set_state(s):
    print "Setting state to " + s

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
on = subparsers.add_parser('ON', help = 'On help here.')
on.set_defaults(func = set_state, newstate = 'ON')
off = subparsers.add_parser('OFF', help = 'Off help here.')
off.set_defaults(func = set_state, newstate = 'OFF')
prt = subparsers.add_parser('PRINT')
prt.set_defaults(func = print_state, newstate = 'N/A')
force = subparsers.add_parser('FORCE' , help = 'Force help here.')
force.add_argument('newstate', choices = [ 'ON', 'OFF' ])
force.set_defaults(func = set_state)

if (len(sys.argv) < 2):
    args = parser.parse_args(['PRINT'])
else:
    args = parser.parse_args(sys.argv[1:])

args.func(args.newstate)
like image 171
srgerg Avatar answered Oct 16 '22 03:10

srgerg