argparse with required subcommands
Asked Answered
H

4

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()
Hornbeam answered 16/8, 2013 at 21:29 Comment(0)
A
66

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

Accurate answered 16/8, 2013 at 23:58 Comment(1)
Excellent, that's what I needed. Works great now, and I'll watch out for the bug fix.Hornbeam
F
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
Fluidize answered 24/4, 2019 at 16:9 Comment(0)
I
0

How about using required=True? More info here.

Imperial answered 16/8, 2013 at 21:32 Comment(4)
It isn't supported by the add_subparsers() method: TypeError: __init__() got an unexpected keyword argument 'required'Aerobatics
We are adding this to Python 3.7, and making it the default behaviour. Better late than never ¯\_(ツ)_/¯Heavyhanded
Did this make it into Python 3.7? It seems to be mentioned in the docs however when I use it with 3.7.1 I get a TypeError: sequence item 0: expected str instance, NoneType found so maybe not.Yardmaster
Turns out you also need to provide a dest so that the subparser selection has a name: parser.add_subparsers(dest="command", required=True).Yardmaster
A
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
Aerobatics answered 16/8, 2013 at 21:37 Comment(4)
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.Hornbeam
@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.Aerobatics
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!Hornbeam
using metavar in addition to dest helps make output make more senseShontashoo

© 2022 - 2024 — McMap. All rights reserved.