48

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.

4
  • Is it a viable option (aesthetically speaking) to do something like --action=dump-csv or --action=dump-some-other-format? This would alleviate the problem of having "required options" entirely. Commented Feb 29, 2012 at 20:32
  • @dcrosta it would obviously work, but I prefer not to go that way, I find it unwieldy. Commented Feb 29, 2012 at 20:35
  • Fair enough, just wanted to make sure you've covered the obvious bases. Commented Feb 29, 2012 at 20:37
  • He needed default to be upload -- parser.add_argument('--action', choices=['upload', 'dump'], default='dump') but I did not think of parser.error. Commented Feb 29, 2012 at 20:53

5 Answers 5

83

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

Upload:

$ 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

Dump:

$ 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: ArgumentParser.add_subparsers()

Sign up to request clarification or add additional context in comments.

3 Comments

The design of the command line arguments are different than the one asked.
Doesn't python print require ()? Why is it not used above? It errors when I run the example above because print tells me the syntax should be print('something') rather than print 'something'. Is this an environment specific quirk? (I am running in Windows)
@skeetastax print worked like that in Python 2, this answer is from 2012 when Python 2 was still common.
43

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

3 Comments

One limitation is that argparse does not allow multiple subparsers.
This is wrong. You can add as many subparsers as you want. See subcommands link referenced in answer. See also Niels Bom's answer
... or did you mean nesting of subcommands? Something like dump dumpsubcommand1 , dump subcommand2, upload subcommand3 etc..?
23

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.')

3 Comments

This is only describing the "argparse way" of reporting an error. The logic is implemented manually. Not an "argparse" way of conditionally requiring arguments.
If instead of "dump" and "format" there are 5 more options that all have their own set of required or non-required arguments this way of checking for requiredness can get unclear fast.
Besides, things like argparse.FileType will get problems. For certain selected actions when the FileType argument is not needed, non-existence of the file causes a false error.
7

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

1 Comment

That's clever, elegant solution for specific usecases
1

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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.