Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

argparse: required choice between 1 argument or an argument group

Tags:

In my python app, my users have a choice between specifying either:

  • a single date
  • a date range (start, end)

I have created a nested argument_group inside a mutually_exclusive_group:

argument_group
+--- mutually_exclusive_group
     +--- date                 (argument)
     +--- argument_group
          +--- start           (argument)
          +--- end             (argument)

I want to indicate to my users that either date or the group (start, end) is required, and that if they choose the group, both (start, end) are required:

This is what I have so far:

ap = argparse.ArgumentParser()
grp = ap.add_argument_group('test dates')
date_group = grp.add_mutually_exclusive_group(required=True)

date_group.add_argument('--date', help='date (YYYYMMDD)')
date_range = date_group.add_argument_group('date range')

date_range.add_argument('--start',help='start date (YYYYMMDD)', required=True)
date_range.add_argument('--end',  help='end date (YYYYMMDD)', required=True)

Problem 1:

Specifying (start, end) doesn't work, as argparse is (I guess) unable to see that I've specified the argument_group part of the mutually_exclusive_group instead of date, and since the mutually_exclusive_group is required, it barfs:

$ ./app.py --start 20180101 --end 20180102
usage: app.py [-h] --date date --start date --end date
app.py: error: one of the arguments --date is required

I can work around this issue by making the mutually_exclusive_group not required, but this is obviously incorrect, and won't catch the case where nothing is specified (as it is, in fact, required)

Problem 2:

Unfortunately the help for the above does not reflect my intention:

There is no (option_1 | option_2) around the options in the mutually_exclusive_group which there would be if I didn't have a nested group.

In addition, the help for start and end isn't even shown.

$ ./app.py --help
usage: app.py [-h] --date date --start date --end date

optional arguments:
  -h, --help         show this help message and exit

test dates:
  --date date        date (YYYYMMDD) (default: None)

What I would like is something such as:

$ ./app.py --help
usage: app.py [-h] (--date date | --start date --end date)

optional arguments:
  -h, --help         show this help message and exit

test dates:
  --date date        date (YYYYMMDD) (default: None)
  --start date       start date (YYYYMMDD) (default: None)
  --end date         end date (YYYYMMDD) (default: None)

How can I express this required choice between an argument and an argument group?

like image 452
Steve Lorimer Avatar asked Nov 28 '18 15:11

Steve Lorimer


2 Answers

Given your comment that start == end if --date is given, I'd keep things simple and stick with just the two arguments:

class DefaultEnd(argparse.Action):
  def __call__(self, parser, namespace, values, option_string=None):
    setattr(namespace, self.dest, values)
    setattr(namespace, 'end', values)

ap = argparse.ArgumentParser()
ap.add_argument('--start', help='date (YYYYMMDD) Mandatory', action=DefaultEnd, required=True)
ap.add_argument('--end', help='date (YYYYMMDD) Optional, defaulted to START if not provided')

Using the custom argparse.Action it also defaults --end to --start if only the latter was provided:

optional arguments:
  -h, --help     show this help message and exit
  --start START  date (YYYYMMDD) Mandatory
  --end END      date (YYYYMMDD) Optional, defaulted to START if not provided

So if the params were given:

args = ap.parse_args(['--start','20100102', '--end', '20121123'])
# args.start = 20100102
# args.end   = 20121123

If not:

args = ap.parse_args(['--start','20100102'])

# args.start = 20100102
# args.end   = 20100102

You can of course, also keep things simple if you just manipulate args after parsing and skip the custom argparse.Action altogether:

ap.add_argument('--end', help='date (YYYYMMDD) Optional, defaulted to START if not provided', default=None)

...

args.end = args.end if args.end else args.start

Of course this might change how your post-args codes are being handled, so these solutions might not apply for you if your code must require args.date.

like image 156
r.ook Avatar answered Oct 16 '22 14:10

r.ook


I don't know how you can express the choice between an argument and an argument group, but a simple solution to your problem would be to use the following:

ap = argparse.ArgumentParser()
grp = ap.add_argument_group('test dates')
date_group = grp.add_mutually_exclusive_group(required=True)

date_group.add_argument('--date', help='date (YYYYMMDD)')
date_range = date_group.add_argument('--date-range',
    nargs=2, help='Start and End')

Just specify date-range as single argument with two parameters.

like image 23
FelixGK Avatar answered Oct 16 '22 16:10

FelixGK