3

I know that argparse describes in its documentation that parameters starting with - or -- are considered optional:

In general, the argparse module assumes that flags like -f and --bar indicate optional arguments, which can always be omitted at the command line.

No matter if you define parameters as required or not, they will show up under "optional arguments" in argparse's help output:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--required_argument', action='store_true',  required=True)
parser.add_argument('--optional_argument', action='store_false', required=False)
parser.add_argument('positional_argument')
args = parser.parse_args()

This results in the following help output:

usage: demo.py [-h] --required_argument [--optional_argument] positional_argument

positional arguments:
  positional_argument

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

As you might notice, no matter if the arguments starting with - or -- are defined to be required or not, they are listed under "optional arguments". Only those square brackets in the "usage"-line indicate if it can be omitted or not.

I already know there is a workaround for that by using add_argument_group() like this:

import argparse

parser = argparse.ArgumentParser()
group1 = parser.add_argument_group(title='required arguments')
group1.add_argument('--required_argument', action='store_true',  required=True)
parser.add_argument('--optional_argument', action='store_false', required=False)
parser.add_argument('positional_argument')
args = parser.parse_args()
usage: demo.py [-h] --required_argument [--optional_argument] positional_argument

positional arguments:
  positional_argument

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

required arguments:
  --required_argument

But how to achieve a similar thing in case you have mutually exclusive arguments? Let's assume you need to provide one of two arguments, but not both at the same time. I know, there are better options like in this example, but for the sake of it: When you need the user to use either --enable or --disable: One needs to be provided, but both do not make sense.

If this is implemented like this, I do not know of a way to make it not end up under "optional arguments", since add_mutually_exclusive_group() is lacking support for the "title" and "description" arguments of add_argument_group():

Note that currently mutually exclusive argument groups do not support the title and description arguments of add_argument_group().

import argparse

parser = argparse.ArgumentParser()
group1 = parser.add_mutually_exclusive_group(required=True)
group1.add_argument('--enable',  action='store_true',  help='Enable something')
group1.add_argument('--disable', action='store_false', help='Disable something')
args = parser.parse_args()
usage: demo.py [-h] (--enable | --disable)

optional arguments:
  -h, --help  show this help message and exit
  --enable    Enable something
  --disable   Disable something

So, anybody knows a solution with argparse or is this a showstopper request and one must switch to something else like click?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Judge
  • 644
  • 8
  • 11
  • 1
    You can define a mutually exclusive group inside an argument group. In general groups aren't designed for nesting, but this particular combination does work, and is useful. – hpaulj Jan 16 '20 at 17:14
  • There are two default argument groups, one labeled `positional` the other `optional`. The names reflect historical usage, and how they are processed (not the value of the `required` parameter). The use of 'options' or 'optionals' goes back to `unix` `getopt` parsers. I prefer the term `flagged`. – hpaulj Jan 16 '20 at 17:28

2 Answers2

3

You can create group and rearrange the optional arguments and required arguments for clean look

import argparse

parser = argparse.ArgumentParser()
optional = parser._action_groups.pop() #removes the optional arguments section
group1 = parser.add_argument_group("Required arguments")
group1 = group1.add_mutually_exclusive_group(required=True)
group1.add_argument('--enable',  action='store_true',  help='Enable something')
group1.add_argument('--disable', action='store_false', help='Disable something')
parser._action_groups.append(optional) # add optional arguments section again
args = parser.parse_args()

Output:

usage: argparse test.py [-h] (--enable | --disable)

Required arguments:
  --enable    Enable something
  --disable   Disable something

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

neez_duts
  • 31
  • 3
2

Heed the note on the required parameter:

Note: Required options are generally considered bad form because users expect options to be optional, and thus they should be avoided when possible.

So don't use required options; instead use positional arguments with choices or a sub-parser. Here's an example with positionals, which you'll notice is much simpler:

import argparse

parser = argparse.ArgumentParser(prog='demo.py')
parser.add_argument('action', choices=['enable', 'disable'], help='Action to take')

parser.print_help()

for arg in ['enable', 'disable', 'rotate']:
    print()
    print(parser.parse_args([arg]))

Output:

usage: demo.py [-h] {enable,disable}

positional arguments:
  {enable,disable}  Action to take

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

Namespace(action='enable')

Namespace(action='disable')

usage: demo.py [-h] {enable,disable}
demo.py: error: argument action: invalid choice: 'rotate' (choose from 'enable', 'disable')
wjandrea
  • 28,235
  • 9
  • 60
  • 81