Easy Command Line Apps with Argparse
argparse, which will be included in Python's Standard Library when 2.7 is released, is a great command line parsing library. It's both easier to use and more capable than optparse -- a fine combination! Here's a quick-and-dirty example of creating a script that supports commands, using argparse.
#!/usr/bin/env python
import sys
class Command(object):
"""Base class for commands.
"""
# optional command-specific args
args = []
def execute(self, args):
raise NotImplementedError()
def arg(*args, **kw):
# sugar for arg declaration
return (args, kw)
def commands():
"""Return list of Command sub classes from current module.
"""
mod = sys.modules[__name__]
classes = []
for attr in [getattr(mod, name) for name in dir(mod)]:
if Command in getattr(attr, '__bases__', []):
classes.append(attr)
return classes
def register_commands(arg_parser):
"""Register all subclasses of Command.
"""
cmd_parsers = arg_parser.add_subparsers()
for cmd in commands():
# use first line of docstring as help
help = (cmd.__doc__ or 'no help').strip().splitlines()[0]
cmd_parser = cmd_parsers.add_parser(cmd.__name__, help=help)
# add optional command-specific arguments
for (args, kw) in cmd.args:
cmd_parser.add_argument(*args, **kw)
# This is really, really important!
# Without it we won't know which command to execute.
cmd_parser.set_defaults(command_class=cmd)
Define some commands:
class Date(Command):
"""show current date
"""
args = [
arg('format', default=['%Y-%m-%d'], nargs='*',
help='strftime-compatiable format'),
]
def execute(self, args):
import datetime
print datetime.date.today().strftime(' '.join(args.format))
class Echo(Command):
"""echo input to stdout
"""
args = [
arg('-n', dest='newline', default=True,
action='store_false',
help='do not output the trailing newline'),
arg('strings', metavar='string', nargs='*'),
]
def execute(self, args):
sys.stdout.write(' '.join(args.strings))
if args.newline:
sys.stdout.write('\n')
Finally, glue it together with argparse.
if __name__ == '__main__':
import argparse
arg_parser = argparse.ArgumentParser()
# really just to demonstrate a global option
arg_parser.add_argument('-v', '--verbose',
action='store_true', help='example global option')
# register commands
register_commands(arg_parser)
# parse args including which command to run
args = arg_parser.parse_args(sys.argv[1:])
# create instance of command
command = args.command_class()
if args.verbose:
print 'running', command
# run it
command.execute(args)
Let's try it! First, let's get a listing of commands:
orutherfurd@laptop:~/tmp$ ./argparse_commands.py --help
usage: argparse_commands.py [-h] {Date,Echo} ...
positional arguments:
{Date,Echo}
Date show current date
Echo echo input to stdout
optional arguments:
-h, --help show this help message and exit
-v, --verbose example global option
then help for a specific one:
orutherfurd@laptop:~/tmp$ ./argparse_commands.py Echo --help
usage: argparse_commands.py Echo [-h] [-n] [string [string ...]]
positional arguments:
string
optional arguments:
-h, --help show this help message and exit
-n do not output the trailing newline
lastly, let's run one:
orutherfurd@laptop:~/tmp$ ./argparse_commands.py Date
2010-04-29
orutherfurd@laptop:~/tmp$ ./argparse_commands.py Date %b %d, %Y
Apr 29, 2010
While cursory, this demonstrates how easy it is to create flexible command line apps using argparse. We supplied the Command boilerplate, but argparse took care of all argument parsing and made command dispatching a piece of cake.