How can I set up argparse to parse either 'diff -g file1 -r file2' or 'format file'?

Question:

I want to use argparse to parse command line arguments for my program. The program supports two modes – diff and format.

When -diff is chosen, the arguments -g <file1> and -r <file2> are also needed. When -format is chosen, instead there should be a <file> argument.

How can I set up this logic with argparse? Should I use subparsers?

Asked By: Jason T.

||

Answers:

When you have two "modes" that each needs to enforce different optional and/or positional parameters I think your best bet would be to use subparsers for each of the modes… Then you can either test the output namespace for which command was triggered or you can set default callables to trigger for each of the options:

Here is an example of what it could look like using this strategy.

import argparse
import sys

parser = argparse.ArgumentParser(sys.argv[0], prefix_chars="-")
subparsers = parser.add_subparsers(dest="cmd")
diff_parser = subparsers.add_parser("diff", help='diff help')
diff_parser.add_argument(
    "-g",
    metavar="<file1>",
    action="store",
    dest="gfile",
    help="g help"
)
diff_parser.add_argument(
    "-r",
    metavar="<file2>",
    action="store",
    dest="rfile",
    help="r help"
)
format_parser = subparsers.add_parser("format", help="format help")
format_parser.add_argument(
    "file",
    metavar="<file>",
    help="file help"
)
args = parser.parse_args(sys.argv[1:])

Then if you wanted to use a conditional to check which command was triggered like this:

if args["cmd"] == "diff":
    do something...
elif args["cmd"] == "format":
    do something else....

Or using default callables like this:

diff_parser.set_defaults(func=my_diff_func)

format_parser.set_defaults(func=my_format_func)

args.func(args)
Answered By: Alexander

Just to share and record what I have finalized eventually.
There are two solutions that I tried and eventually I picked up the subparser one although I think both ways work well to suit this task:

  1. Using subparser
import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')
subparsers.required = True

# subparser for diff
parser_multi = subparsers.add_parser('diff')
# add required arguments
parser_diff.add_argument(
    '-f1',
    '--file1',
    type=str,
    required=True
)
parser_diff.add_argument(
    '-f2',
    '--file2',
    type=str,
    required=True
)

# subparser for format
parser_format = subparsers.add_parser('format')
# add a required argument
parser_format.add_argument(
    '-f',
    '--file',
    type=str,
    required=True
)

args = parser.parse_args()
  1. Using conditional args parser
import argparse
import sys

mode_choices = ['diff', 'format']
parser = argparse.ArgumentParser()
parser.add_argument('-m',
                    '--mode',
                    choices=mode_choices,
                    help='Choose mode to start',
                    required=True)

if True in list(map(lambda x: mode_choices[0] in x, sys.argv)):
    parser.add_argument('-f1',
                        '--file1',
                        type=str,
                        required=True)
    parser.add_argument('-f2',
                        '--file2',
                        type=str,
                        required=True)

if True in list(map(lambda x: mode_choices[1] in x, sys.argv)):
    parser.add_argument('-f',
                        '--file',
                        type=str,
                        required=True)

args = parser.parse_args()
Answered By: Jason T.
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.