39

With python's argparse, how do I make a subcommand a required argument? I want to do this because I want argparse to error out if a subcommand is not specified. I override the error method to print help instead. I have 3-deep nested subcommands, so it's not a matter of simply handling zero arguments at the top level.

In the following example, if this is called like so, I get:

$./simple.py
$

What I want it to do instead is for argparse to complain that the required subcommand was not specified:

import argparse

class MyArgumentParser(argparse.ArgumentParser):
    def error(self, message):
        self.print_help(sys.stderr)
        self.exit(0, '%s: error: %s\n' % (self.prog, message))

def main():
    parser = MyArgumentParser(description='Simple example')
    subs = parser.add_subparsers()
    sub_one = subs.add_parser('one', help='does something')
    sub_two = subs.add_parser('two', help='does something else')

    parser.parse_args()

if __name__ == '__main__':
    main()
PonyEars
  • 2,144
  • 4
  • 25
  • 30

4 Answers4

65

There was a change in 3.3 in the error message for required arguments, and subcommands got lost in the dust.

http://bugs.python.org/issue9253#msg186387

There I suggest this work around, setting the required attribute after the subparsers is defined.

parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()

update

A related pull-request: https://github.com/python/cpython/pull/3027

hpaulj
  • 221,503
  • 14
  • 230
  • 353
18

In addition to hpaulj's answer: you can also use the required keyword argument with ArgumentParser.add_subparsers() since Python 3.7. You also need to pass dest as argument. Otherwise you will get an error: TypeError: sequence item 0: expected str instance, NoneType found.

Example file example.py:

import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', required=True)
foo_parser = subparsers.add_parser("foo", help="command foo")
args = parser.parse_args()

Output of the call without an argument:

$ python example.py
usage: example.py [-h] {foo} ...
example.py: error: the following arguments are required: command
Henrik
  • 2,771
  • 1
  • 23
  • 33
0

How about using required=True? More info here.

Joseph Dunn
  • 1,298
  • 9
  • 9
  • 7
    It isn't supported by the `add_subparsers()` method: `TypeError: __init__() got an unexpected keyword argument 'required'` – Bakuriu Aug 16 '13 at 21:37
  • 3
    We are adding this to Python 3.7, and making it the default behaviour. Better late than never ¯\\_(ツ)_/¯ – merwok Sep 20 '17 at 17:33
  • 2
    Did this make it into Python 3.7? It seems to be mentioned in the [docs](https://docs.python.org/dev/library/argparse.html#argparse.ArgumentParser.add_subparsers) however when I use it with 3.7.1 I get a `TypeError: sequence item 0: expected str instance, NoneType found` so maybe not. – davidA Nov 29 '18 at 04:40
  • 2
    Turns out you also need to provide a `dest` so that the subparser selection has a name: `parser.add_subparsers(dest="command", required=True)`. – davidA Nov 29 '18 at 04:47
0

You can use the dest argument, which is documented in the last example in the documentation for add_subparsers():

# required_subparser.py
import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser_name')
one = subparsers.add_parser('one')
two = subparsers.add_parser('two')

args = parser.parse_args()

Running with Python 2.7:

$python required_subparser.py 
usage: required_subparser.py [-h] {one,two} ...
required_subparser.py: error: too few arguments
$python required_subparser.py one
$# no error
Henrik
  • 2,771
  • 1
  • 23
  • 33
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • Not sure if I'm missing something here, but I don't get the "usage:" message when running your example. I get back to the prompt with no output with or without an argument. I'm using python 3.3.2 and the argparse that came along with it. – PonyEars Aug 16 '13 at 22:10
  • 1
    @redstreet You didn't mention you were using python3.3 and I tested it using the system `python` executable, which is python 2.7. Indeed with python3.3 you don't get the messages. – Bakuriu Aug 17 '13 at 07:58
  • Yes, because I didn't expect this to be due to a bug (which made it dependent on the version!). Thank you for the answer and for trying it on 2.7! – PonyEars Aug 17 '13 at 08:18
  • using metavar in addition to dest helps make output make more sense – Ryan Hope Sep 14 '18 at 08:06