-1

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?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Jason T.
  • 113
  • 1
  • 2
  • 7
  • Does this answer your question? [Conditional command line arguments in Python using argparse](https://stackoverflow.com/questions/9505898/conditional-command-line-arguments-in-python-using-argparse) – Maurice Meyer Sep 13 '22 at 18:13
  • Thank you! Not exactly the same but close. – Jason T. Sep 13 '22 at 18:23
  • 3
    In general, you should make `diff` and `format` subcommands, with subparsers, instead of being options themselves. That is, `foo diff` and `foo format`, instead of `foo -diff` and `foo -format`. – Charles Duffy Sep 13 '22 at 18:51
  • ...the standard way to parse `foo -diff` has it be identical to `foo -f -f -i -d`; that is to say, a collection of separate one-character flags, not a single world (hence the GNU extension `foo --diff` to make "diff" a word) – Charles Duffy Sep 13 '22 at 18:53
  • (see section 12.2 of https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/basedefs/V1_chap12.html re: above assertion; when I say "standard", I _do_ mean formally-ratified-by-a-standards-body type "standard"). – Charles Duffy Sep 13 '22 at 19:03
  • @CharlesDuffy Thank you very much for the helps. I am looking into subparsers too but at the same time I found the last proposed one from this post https://stackoverflow.com/questions/9505898/conditional-command-line-arguments-in-python-using-argparse is a pretty good alternative apart from subparsers. It applies conditions on the top level parser instead of using subparser. It also provides a lot of flexibility. – Jason T. Sep 13 '22 at 19:13

2 Answers2

1

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)
Alexander
  • 16,091
  • 5
  • 13
  • 29
-1

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()
Jason T.
  • 113
  • 1
  • 2
  • 7