How to make python argparse mutually exclusive group arguments without prefix?

Python2.7 argparse only accepts optional arguments (prefixed) in mutually exclusive groups:

parser = argparse.ArgumentParser(prog='mydaemon') action = parser.add_mutually_exclusive_group(required=True) action.add_argument('--start', action='store_true', help='Starts %(prog)s daemon') action.add_argument('--stop', action='store_true', help='Stops %(prog)s daemon') action.add_argument('--restart', action='store_true', help='Restarts %(prog)s daemon') 

$ mydaemon -h

usage: mydaemon [-h] (--start | --stop | --restart) optional arguments: -h, --help show this help message and exit --start Starts mydaemon daemon --stop Stops mydaemon daemon --restart Restarts mydaemon daemon 

Is there a way to make argparse arguments behaves like traditional unix daemon control:

(start | stop | restart) and not (--start | --stop | --restart) ? 

4 Answers

Use ArgumentParser.add_mutually_exclusive_group():

import argparse parser = argparse.ArgumentParser() group = parser.add_mutually_exclusive_group() group.add_argument('-a', action='store_true') group.add_argument('-b', action='store_true') print parser.parse_args() 

output:

$ python argparse_mutually_exclusive.py -h usage: argparse_mutually_exclusive.py [-h] [-a | -b] optional arguments: -h, --help show this help message and exit -a -b $ python argparse_mutually_exclusive.py -a Namespace(a=True, b=False) $ python argparse_mutually_exclusive.py -b Namespace(a=False, b=True) $ python argparse_mutually_exclusive.py -a -b usage: argparse_mutually_exclusive.py [-h] [-a | -b] argparse_mutually_exclusive.py: error: argument -b: not allowed with argument -a 

version2

import argparse parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(help='commands') # A list command list_parser = subparsers.add_parser('list', help='List contents') list_parser.add_argument('dirname', action='store', help='Directory to list') # A create command create_parser = subparsers.add_parser('create', help='Create a directory') create_parser.add_argument('dirname', action='store', help='New directory to create') create_parser.add_argument('--read-only', default=False, action='store_true', help='Set permissions to prevent writing to the directory', ) # A delete command delete_parser = subparsers.add_parser('delete', help='Remove a directory') delete_parser.add_argument('dirname', action='store', help='The directory to remove') delete_parser.add_argument('--recursive', '-r', default=False, action='store_true', help='Remove the contents of the directory, too', ) print parser.parse_args(['list', 'a s d', ]) >>> Namespace(dirname='a s d') print parser.parse_args(['list', 'a s d', 'create' ]) >>> error: unrecognized arguments: create 

For all the abilities and options in argparse I don't think you'll ever get a "canned" usage string that looks like what you want.

That said, have you looked at sub-parsers since your original post?

Here's a barebones implementation:

import argparse parser = argparse.ArgumentParser(prog='mydaemon') sp = parser.add_subparsers() sp_start = sp.add_parser('start', help='Starts %(prog)s daemon') sp_stop = sp.add_parser('stop', help='Stops %(prog)s daemon') sp_restart = sp.add_parser('restart', help='Restarts %(prog)s daemon') parser.parse_args() 

Running this with the -h option yields:

usage: mydaemon [-h] {start,stop,restart} ... positional arguments: {start,stop,restart} start Starts mydaemon daemon stop Stops mydaemon daemon restart Restarts mydaemon daemon 

One of the benefits of this approach is being able to use set_defaults for each sub-parser to hook up a function directly to the argument. I've also added a "graceful" option for stop and restart:

import argparse def my_stop(args): if args.gracefully: print "Let's try to stop..." else: print 'Stop, now!' parser = argparse.ArgumentParser(prog='mydaemon') graceful = argparse.ArgumentParser(add_help=False) graceful.add_argument('-g', '--gracefully', action='store_true', help='tries to terminate the process gracefully') sp = parser.add_subparsers() sp_start = sp.add_parser('start', help='Starts %(prog)s daemon') sp_stop = sp.add_parser('stop', parents=[graceful], description='Stops the daemon if it is currently running.', help='Stops %(prog)s daemon') sp_restart = sp.add_parser('restart', parents=[graceful], help='Restarts %(prog)s daemon') # Hook subparsers up to functions sp_stop.set_defaults(func=my_stop) # Uncomment when my_start() and # my_restart() are implemented # # sp_start.set_defaults(func=my_start) # sp_restart.set_defaults(func=my_restart) args = parser.parse_args() args.func(args) 

Showing the "help" message for stop:

$ python mydaemon.py stop -h usage: mydaemon stop [-h] [-g] Stops the daemon if it is currently running. optional arguments: -h, --help show this help message and exit -g, --gracefully tries to terminate the process gracefully 

Stopping "gracefully":

$ python mydaemon.py stop -g Let's try to stop... 
3

It sounds like you want a positional argument instead of mutually exclusive options. You can use 'choices' to restrict the possible acceptable options.

parser = ArgumentParser() parser.add_argument('action', choices=('start', 'stop', 'restart')) 

This produces a usage line that looks like this:

usage: foo.py [-h] {start,stop,restart} 
5

Building on Adam's answer... if you wanted to specify a default you could always do the following so they can effectively leave it blank.

import argparse ActionHelp = """ Start = Starts the daemon (default) Stop = Stops the daemon Restart = Restarts the daemon """ parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('action', nargs = '?', choices=('start', 'stop', 'restart'), default = 'start', help = ActionHelp) print parser.parse_args(''.split()) print print parser.parse_args('-h'.split()) 

which will print:

Namespace(action='start') usage: program.py [-h] [{start,stop,restart}] postional arguments: {start,stop,restart} Start = Starts the daemon (default) Stop = Stops the daemon Restart = Restarts the daemon optional arguments: -h, --help show this help message and exit 

ncG1vNJzZmirpJawrLvVnqmfpJ%2Bse6S7zGiorp2jqbawutJobnFuaWiBdnvHqK5mrJ9iuqK3xGansqyYpLturdGgp5qqo5p6rsHTrpilpKlisrmvy66qoq6VYrSzu9SpZJqql6q6prrTrGSwoaSdvLbAjKmpnp6ZrQ%3D%3D