1

Is there a way to specify two required arguments in argparse, one that corresponds to a subcommand, and another that is required by all subcommands.

The closest I can manage seems to be with

import argparse

parser = argparse.ArgumentParser()

subparsers = parser.add_subparsers(help='', dest='command',  metavar='COMMAND', title='required arguments',
                                   description='two arguments are required')
parser.add_argument('config', metavar='CONFIG', action='store', help='the config to use')

cmda_parser = subparsers.add_parser('cmdA',  help='a first command')
cmdb_parser = subparsers.add_parser('cmdB',  help='the second operation')
cmdc_parser = subparsers.add_parser('cmdC',  help='yet another thing')

print(parser.parse_args())

which gives

usage: enigma.py [-h] COMMAND ... CONFIG

positional arguments:
  CONFIG      the config to use

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

required arguments:
  two arguments are required

  COMMAND
    cmdA      a first command
    cmdB      the second operation
    cmdC      yet another thing

and help for subcommands that does not show CONFIG; but what I want is

usage: enigma.py [-h] COMMAND CONFIG

required arguments:
  two arguments are required

  COMMAND
    cmdA      a first command
    cmdB      the second operation
    cmdC      yet another thing

  CONFIG      the config to use

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

and help for each subcommand that does show CONFIG, eg.

usage: enigma.py cmdA CONFIG [-h] 

    required arguments:

      CONFIG      the config to use

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

is there any way to accomplish this?

How to I specific two required arguments, one of which is a subcommand, with the second "propagated" to each subcommand as a required argument?

orome
  • 45,163
  • 57
  • 202
  • 418

2 Answers2

1

Parsers can "inherit" arguments from another parser, using the parents attribute.

import argparse

parser = argparse.ArgumentParser()

# Put common subparser arguments here. Each sub parser will have
# its own -h option, so disable it on the shared base.
subbase = argparse.ArgumentParser(add_help=False)
subbase.add_argument('config', metavar='CONFIG', action='store', help='the config to use')

subparsers = parser.add_subparsers(help='', dest='command',  metavar='COMMAND', title='required arguments',
                                   description='two arguments are required')

# Add subbase to the parent list for each subparser.
cmda_parser = subparsers.add_parser('cmdA', parents=[subbase],  help='a first command')
cmdb_parser = subparsers.add_parser('cmdB', parents=[subbase], help='the second operation')
cmdc_parser = subparsers.add_parser('cmdC', parents=[subbase], help='yet another thing')

print(parser.parse_args())
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Getting close, I think. But this doesn't list `CONFIG` as one of the two required arguments when help is asked for without specifying an argument. – orome Nov 01 '15 at 19:01
  • You have to use `script.py cmdA -h` to see `CONFIG` listed as an argument. It's an argument of the subparser, not the main parser. – chepner Nov 01 '15 at 19:37
  • That gets into a different issue - that none of the subparser arguments are displayed in the main parser help. Each parser (and that includes the main one) only displays its own arguments. – hpaulj Nov 01 '15 at 19:41
  • http://stackoverflow.com/questions/20094215/argparse-subparser-monolithic-help-output is one of several SO questions about displaying the help for both a main parser and subparsers. – hpaulj Nov 01 '15 at 19:53
  • Not quite what I'm looking for, but it illustrates why argparse prevents me from doing it. This is one of the least thought-through Python APIs I've used. – orome Nov 04 '15 at 23:24
0

With the parser that you defined, COMMAND and CONFIG are both required. Try for example

python myprog cmdA

You should get a missed argument error. On newer version it will be explicit

0826:~/mypy$ python3.5 stack33463052.py cmdA 
usage: stack33463052.py [-h] COMMAND ... CONFIG
stack33463052.py: error: the following arguments are required: CONFIG

But, yes, COMMAND does not appear in the subparser's help. That's because the subparse knows nothing about it. It was defined for the main parser.

If I move CONFIG definition to before the subparsers creation, I get:

0833:~/mypy$ python stack33463052.py tst cmdA -h
usage: stack33463052.py CONFIG cmdA [-h]

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

Now CONFIG appears in the usage. That's because the code knows about this argument when the subparser is defined. But - I had to include a value for CONFIG when seeking this help.


(edit) CONFIG appears in the cmdA usage because it was added to it's prog attribute when the parser was created:

 print(cmda_parser.prog)
 'stack33463052.py CONFIG cmdA'

But that prog is not modified if arguments are later added to parser.


In the following, cmdA is taken as an argument to CONFIG, not COMMAND, and thus I get the default main parser help.

0833:~/mypy$ python stack33463052.py cmdA -h
usage: stack33463052.py [-h] CONFIG COMMAND ...

positional arguments:
  CONFIG      the config to use

optional arguments:
...

To the main parser both CONFIG and COMMAND are required positionals. There's nothing special about COMMAND, except that it has 3 defined choices.

Defining CONFIG for each of the subparsers might be the best choice. It requires more typing, but it will be displayed in the right help. If conceptually it is linked more closely with the commands than the main parser, then it needs to defined with them. That fact that all commands need it isn't that important.

In general, when using subparsers, it is best to use optionals for main parser values like vebosity and logging, and define the rest in the subparers. It keeps the division of effort between the parsers cleaner. I can elaborate on that if needed.

There's no propagation mechanism. But I could certainly write a function that would add a common argument to each subparser.

def foo(subparsers, *args, **kwargs):
   sub = subparsers.add_parser(*args, **kwargs)
   sub.add_argument('config')
   return sub

It can be instructive to run a parser setup in an interactive shell, and examine the objects that each command creates - parser, subparsers, cmda_parser, etc. The list of arguments (Action objects) that parser knows about is in parser._actions.

Program clarity and conciseness in the argparse module means that parser and cmda_parser are distinct ArgumentParser objects. One isn't just a mode or method of the other. The subparsers object (an Action subclass) is the only formal link between parser and cmda_parser.

Also try:

parser.print_help() # or parser.print_usage()
cmda_parser.print_help()

This helps show that the help for the 2 parsers is independent.

hpaulj
  • 221,503
  • 14
  • 230
  • 353