Conditional command line arguments in Python using argparse

Question:

I’d like to have a program that takes a --action= flag, where the valid choices are dump and upload, with upload being the default. If (and only if) dump is selected, I’d like there to also be a --dump-format= option. Is there a way to express this using argparse, or do I need to just accept all the arguments and do the logic myself.

Asked By: Alex Gaynor

||

Answers:

You could use parser.error:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--action', choices=['upload', 'dump'], default='dump')
parser.add_argument('--dump-format')
args = parser.parse_args()
if args.action != 'dump' and args.dump_format:
    parser.error('--dump-format can only be set when --action=dump.')
Answered By: Jakub Roztocil

Another way to approach the problem is by using subcommands (a’la git) with “action” as the first argument:

script dump --dump-format="foo"
script upload
Answered By: codysoyland

The argparse module offers a way to do this without implementing your own
requiredness checks. The example below uses "subparsers" or "sub commands".
I’ve implemented a subparser for "dump" and one for "format".

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('file', help='The file you want to act on.')
subparsers = parser.add_subparsers(dest='subcommand')
subparsers.required = True  # required since 3.7

#  subparser for dump
parser_dump = subparsers.add_parser('dump')
# add a required argument
parser_dump.add_argument(
    'format',
    choices=['csv', 'json'],
    help='Dump the file in this format.')

#  subparser for upload
parser_upload = subparsers.add_parser('upload')
# add a required argument
parser_upload.add_argument(
    'server',
    choices=['amazon', 'imgur'],
    help='Upload the file to this service.')

args = parser.parse_args()
print args
if args.subcommand == 'dump':
    print 'I will now dump "%s" in the %s format' % (args.file, args.format)
if args.subcommand == 'upload':
    print 'I will now upload "%s" to %s' % (args.file, args.server)

That looks like this on the command line:

$ python ap.py 
usage: ap.py [-h] file {upload,dump} ...
ap.py: error: too few arguments
$ python ap.py tmp.txt 
usage: ap.py [-h] file {upload,dump} ...
ap.py: error: too few arguments
$ python ap.py tmp.txt upload
usage: ap.py file upload [-h] {amazon,imgur}
ap.py file upload: error: too few arguments
$ python ap.py tmp.txt upload amazo
usage: ap.py file upload [-h] {amazon,imgur}
ap.py file upload: error: argument server: invalid choice: 'amazo' (choose from 'amazon', 'imgur')
$ python ap.py tmp.txt upload amazon
Namespace(file='tmp.txt', server='amazon', subcommand='upload')
I will now upload "tmp.txt" to amazon
$ python ap.py tmp.txt upload imgur
Namespace(file='tmp.txt', server='imgur', subcommand='upload')
I will now upload "tmp.txt" to imgur
$ python ap.py tmp.txt dump
usage: ap.py file dump [-h] {csv,json}
ap.py file dump: error: too few arguments
$ python ap.py tmp.txt dump csv
Namespace(file='tmp.txt', format='csv', subcommand='dump')
I will now dump "tmp.txt" in the csv format
$ python ap.py tmp.txt dump json
Namespace(file='tmp.txt', format='json', subcommand='dump')
I will now dump "tmp.txt" in the json format

More info:
http://docs.python.org/dev/library/argparse.html#argparse.ArgumentParser.add_subparsers

Answered By: Niels Bom

It depends what you consider “doing all the logic yourself”. You can still use argparse and add the dump option as follows with minimal effort without resorting to sub-commands:

from argparse import ArgumentParser
from sys import argv

parser = ArgumentParser()
action_choices = ['upload', 'dump']
parser.add_argument('--action', choices=action_choices, default=action_choices[1])
parser.add_argument('--dump-format', required=(action_choices[1] in argv))

This way argparse won’t care about the dump format if the dump action wasn’t selected

Answered By: Dmytro Bugayev

Try this.

Suppose you have a tool that lists, adds and deletes records in a table with the following structure:

id sitekey response status
1 123456 valid a
2 234567 invalid
3 345678 invalid c
4 456789 valid d

And you want to have the following operations and arguments:

  • list
    • from: optional
    • to: optional
    • short-response: optional
  • add
    • sitekey: required
    • response: required
    • status: optional
  • remove
    • id: required

Then, you can have a code similar to the following:

import argparse
import sys

operation_choices = ['list', 'add', 'remove']
parser = argparse.ArgumentParser()
parser.add_argument("--operation",
                    choices = operation_choices,
                    default = operation_choices[0],
                    help = "Your help!",
                    required = True)

# list operation subarguments
if True in list(map(lambda x: operation_choices[0] in x, sys.argv)):
    parser.add_argument("--from",
                        type = int,
                        default = 1,
                        help = "Your help!",
                        required = False)
    parser.add_argument("--to",
                        type = int,
                        help = "Your help!",
                        required = False)
    parser.add_argument("--short-response",
                        type = bool,
                        default = True,
                        help = "Your help!",
                        required = False)

# add operation subarguments
if True in list(map(lambda x: operation_choices[1] in x, sys.argv)):
    parser.add_argument("--sitekey",
                        type = str,
                        help = "Your help!",
                        required = True)
    parser.add_argument("--response",
                        type = str,
                        help = "Your help!",
                        required = True)
    parser.add_argument("--status",
                        type = str,
                        help = "Your help!",
                        required = False)

# remove operation subarguments
if True in list(map(lambda x: operation_choices[2] in x, sys.argv)):
    parser.add_argument("--id",
                        type = int,
                        help = "Your help!",
                        required = True)

args = parser.parse_args()

# Your operations...

So when you run:

$ python tool.py --operation=list

This run, no required arguments

$ python tool.py --operation=add

usage: tool.py [-h] --operation {list,add,remove} --sitekey SITEKEY --response RESPONSE [--status STATUS]
tool.py: error: the following arguments are required: --sitekey, --response

$ python tool.py --operation=remove

usage: tool.py [-h] --operation {list,add,remove} --id ID
tool.py: error: the following arguments are required: --id

$ python tool.py --help

usage: tool.py [-h] --operation {list,add,remove}

options:
  -h, --help            show this help message and exit
  --operation {list,add,remove}
                        Your help!

$ python tool.py --operation=list --help

usage: tool.py [-h] --operation {list,add,remove} [--from FROM] [--to TO] [--short-response SHORT_RESPONSE]

options:
  -h, --help            show this help message and exit
  --operation {list,add,remove}
                        Your help!
  --from FROM           Your help!
  --to TO               Your help!
  --short-response SHORT_RESPONSE
                        Your help!

$ python tool.py --operation=add --help

usage: tool.py [-h] --operation {list,add,remove} --sitekey SITEKEY --response RESPONSE [--status STATUS]

options:
  -h, --help            show this help message and exit
  --operation {list,add,remove}
                        Your help!
  --sitekey SITEKEY     Your help!
  --response RESPONSE   Your help!
  --status STATUS       Your help!

$ python tool.py --operation=remove --help

usage: tool.py [-h] --operation {list,add,remove} --id ID

options:
  -h, --help            show this help message and exit
  --operation {list,add,remove}
                        Your help!
  --id ID               Your help!
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.