Argparse with required subparser
You need to give subparsers
a dest
.
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='cmd')
subparsers.required = True
Now:
1909:~/mypy$ argdev/python3 stack23349349.py
usage: stack23349349.py [-h] {foo} ...
stack23349349.py: error: the following arguments are required: cmd
In order to issue this 'missing arguments' error message, the code needs to give that argument a name. For a positional argument (like subparses), that name is (by default) the 'dest'. There's a (minor) note about this in the SO answer you linked.
One of the few 'patches' to argparse
in the last Python release changed how it tests for 'required' arguments. Unfortunately it introduced this bug regarding subparsers. This needs to be fixed in the next release (if not sooner).
update
If you want this optional subparsers behavior in Py2, it looks like the best option is to use a two stage parser as described in
How to Set a Default Subparser using Argparse Module with Python 2.7
There has been some recent activity in the related bug/issue
https://bugs.python.org/issue9253
update
A fix to this is in the works: https://github.com/python/cpython/pull/3027
argparse with required subcommands
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
how can I make subparsers be required?
Instead of printing help, I would prefer to raise an error:
if which_subcmd not in parsedargs:
msg = "Subcommands needed: subcmd1, subcmd2"
raise argparse.ArgumentTypeError(msg)
This way is more consistent with other argparse errors. But just a matter of taste. I don't see anything wrong on your approach as long as you exit from your script after that print_help()
statement.
Is it possible to inherit required options in argparse subparsers?
Separate the main parser and parent parser functionality:
import argparse
parent_parser = argparse.ArgumentParser(description="The parent parser", add_help=False)
parent_parser.add_argument("-p", type=int, required=True,
help="set the parent required parameter")
main_parser = argparse.ArgumentParser()
subparsers = main_parser.add_subparsers(title="actions", required=True, dest='command')
parser_command1 = subparsers.add_parser("command1", parents=[parent_parser],
description="The command1 parser",
help="Do command1")
parser_command1.add_argument("--option1", help="Run with option1")
parser_command2 = subparsers.add_parser("command2", parents=[parent_parser],
description="The command2 parser",
help="Do command2")
args = main_parser.parse_args()
The main parser and subparser both write to the same args
namespace. If both define a 'p' argument, the subparser's value (or default) will overwrite any value set by the main. So it's possible to use the same 'dest' in both, but it is generally not a good idea. And each parser has to meet its own 'required' specifications. There's no 'sharing'.
The parents
mechanism copies arguments from the parent to the child. So it saves typing or copy-n-paste, but little else. It's most useful when the parent is defined elsewhere and imported. Actually it copies by reference, which sometimes raises problems. In general it isn't a good idea to run both the parent and child. Use a 'dummy' parent.
Put subparser command at the beginning of arguments
Creating another parser helped me getting what I wanted.
The root parser should add all the optional arguments - and also have add_help=False
, to avoid an help message conflict -, then another parser - parser2
, with a lot of fantasy - will be created.
The second parser will have subparsers, and they all needs to specify as parents
the root parser.
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument() ...
parser2 = argparse.ArgumentParser()
subparsers = parser2.add_subparsers(title="Commands", dest="command")
subparsers.required = True
summary_parser = subparsers.add_parser("summary", parents=[parser])
summary_parser.add_argument("v", "--verbose", action="store_true")
# parse args
args = parser2.parse_args()
Now the output will be this:
usage: script.py [-h] {summary} ...
optional arguments:
-h, --help show this help message and exit
Commands:
{summary}
summary For each report print its summary, then exit
usage: script.py summary [-h] -x X -y Y -f FILES [FILES ...] [-v]
optional arguments:
-h, --help show this help message and exit
-x X
-y Y
-f FILES [FILES ...], --files FILES [FILES ...]
-v, --verbose
Optional subparsers?
If you go the subparsers
route, you could define two parsers, 'ls' and 'extract'. 'ls' wouldn't have any arguments; 'extract' would take one positional, 'profile'.
subparsers are optional, (Argparse with required subparser), but 'profile', as currently defined, is required.
An alternative is to define two optionals, and omit the positional.
'-ls', True/False, if True to the list
'-e profile', if not None, do the extract.
Or you could leave the positional profile
, but make it optional (nargs='?').
Another possibility is to look at the profile
value after parsing. If it is a string like 'ls', then list instead of extract. This feels like the cleanest choice, however, the usage will not document this.
parser.add_argument('-l','--ls', action='store_true', help='list')
parser.add_argument('profile', nargs='?', help='The profile')
or
sp = parser.add_subparsers(dest='cmd')
sp.add_parser('ls')
sp1 = sp.add_parser('extract')
sp1.add_argument('profile', help='The profile')
A required mutually exclusive group
gp = parser.add_mutually_exclusive_group(required=True)
gp.add_argument('--ls', action='store_true', help='list')
gp.add_argument('profile', nargs='?', default='adefault', help='The profile')
produces:
usage: aws-env [-h] [-n] (--ls | profile)
How to Set a Default Subparser using Argparse Module with Python 2.7
Another idea is to use a 2 stage parsing. One handles 'globals', returning strings it can't handle. Then conditionally handle the extras with subparsers.
import argparse
def cmd1(args):
print('cmd1', args)
def cmd2(args):
print('cmd2', args)
parser1 = argparse.ArgumentParser()
parser1.add_argument("-i", "--info", help="Display more information")
parser2 = argparse.ArgumentParser()
subparsers = parser2.add_subparsers(dest='cmd')
parserCmd1 = subparsers.add_parser("cmd1", help="First Command")
parserCmd1.set_defaults(func=cmd1)
parserCmd2 = subparsers.add_parser("cmd2", help="Second Command")
parserCmd2.add_argument("-o", "--output", help="Redirect Output")
parserCmd2.set_defaults(func=cmd2)
args, extras = parser1.parse_known_args()
if len(extras)>0 and extras[0] in ['cmd1','cmd2']:
args = parser2.parse_args(extras, namespace=args)
args.func(args)
else:
print('doing system with', args, extras)
sample runs:
0901:~/mypy$ python stack46667843.py -i info
('doing system with', Namespace(info='info'), [])
0901:~/mypy$ python stack46667843.py -i info extras for sys
('doing system with', Namespace(info='info'), ['extras', 'for', 'sys'])
0901:~/mypy$ python stack46667843.py -i info cmd1
('cmd1', Namespace(cmd='cmd1', func=<function cmd1 at 0xb74b025c>, info='info'))
0901:~/mypy$ python stack46667843.py -i info cmd2 -o out
('cmd2', Namespace(cmd='cmd2', func=<function cmd2 at 0xb719ebc4>, info='info', output='out'))
0901:~/mypy$
Related Topics
Multiprocessing - Pipe VS Queue
How to Convert Escaped Characters
Initialize List to a Variable in a Dictionary Inside a Loop
Generalise Slicing Operation in a Numpy Array
How to Get Md5 Sum of a String Using Python
How to Share Numpy Random State of a Parent Process with Child Processes
How to Crop to Largest Interior Bounding Box in Opencv
How to Make the Width of the Title Box Span the Entire Plot
How to Sort a Pandas Dataframe by Index
Django Template System, Calling a Function Inside a Model
Differencebetween Join and Merge in Pandas
How to Get the Executable's Current Directory in Py2Exe
How to Clone a Python Generator Object
Adding a Particle Effect to My Clicker Game
How to Force a Python Wheel to Be Platform Specific When Building It
Why Does CSVwriter.Writerow() Put a Comma After Each Character