Skip to content Skip to sidebar Skip to footer

Python Argparse: Make At Least One Argument Required

I've been using argparse for a Python program that can -process, -upload or both: parser = argparse.ArgumentParser(description='Log archiver arguments.') parser.add_argument('-proc

Solution 1:

if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')

Solution 2:

args = vars(parser.parse_args())
ifnotany(args.values()):
    parser.error('No arguments provided.')

Solution 3:

I know this is old as dirt, but the way to require one option but forbid more than one (XOR) is like this:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()
print args

Output:

>opt.py  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: one of the arguments -process -upload is required  

>opt.py -upload  
Namespace(process=False, upload=True)  

>opt.py -process  
Namespace(process=True, upload=False)  

>opt.py -upload -process  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: argument -process: not allowed with argument -upload  

Solution 4:

If not the 'or both' part (I have initially missed this) you could use something like this:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
    parser.error("One of --process or --upload must be given")

Though, probably it would be a better idea to use subcommands instead.

Solution 5:

Requirements Review

  • use argparse (I will ignore this one)
  • allow one or two actions to be called (at least one required).
  • try to by Pythonic (I would rather call it "POSIX"-like)

There are also some implicit requirements when living on command line:

  • explain the usage to the user in a way which is easy to understand
  • options shall be optional
  • allow specifying flags and options
  • allow combining with other parameters (like file name or names).

Sample solution using docopt (file managelog.py):

"""Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Try to run it:

$ python managelog.py
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Show the help:

$ python managelog.py -h
Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>

And use it:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
 '--pswd': 'secret',
 '--user': 'user',
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': False,
 'upload': True}

Short alternative short.py

There can be even shorter variant:

"""Manage logfiles
Usage:
    short.py [options] (process|upload)... -- <logfile>...
    short.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Usage looks like this:

$ python short.py -V process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 1,
 'upload': 1}

Note, that instead of boolean values for "process" and "upload" keys there are counters.

It turns out, we cannot prevent duplication of these words:

$ python short.py -V process process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 2,
 'upload': 1}

Conclusions

Designing good command line interface can be challenging sometime.

There are multiple aspects of command line based program:

  • good design of command line
  • selecting/using proper parser

argparse offers a lot, but restricts possible scenarios and can become very complex.

With docopt things go much shorter while preserving readability and offering high degree of flexibility. If you manage getting parsed arguments from dictionary and do some of conversions (to integer, opening files..) manually (or by other library called schema), you may find docopt good fit for command line parsing.

Post a Comment for "Python Argparse: Make At Least One Argument Required"