I have a small CLI app (myscript.py
) that is defined like so.
import sys
import argparse
class MyParser(argparse.ArgumentParser):
'''
Overriden to show help on default.
'''
def error(self, message):
print(f'error: {message}')
self.print_help()
sys.exit(2)
def myfunc(args):
'''
Some function.
'''
print(args.input_int**2)
def main():
# Define Main Parser
main_par = MyParser(
prog='myapp',
description='main help')
# Define Command Parser
cmd_par = main_par.add_subparsers(
dest='command',
required=True)
# Add Subcommand Parser
subcmd_par = cmd_par.add_parser(
'subcmd',
description='subcmd help')
# Add Subcommand Argument
subcmd_par.add_argument(
'-i', '--input-int',
type=int,
help='some integer',
required=True)
# Add FromName Dispatcher
subcmd_par.set_defaults(
func=myfunc)
# Parse Arguments
args = main_par.parse_args()
# Call Method
args.func(args)
if __name__ == '__main__':
main()
The MyParser
class simply overrides the error()
method in argparse.ArgumentParser
class to print help on error.
When I execute
$ python myscript.py
I see the default / main help. Expected.
When I execute
$ python myscript.py subcmd
I see the subcmd
help. Expected.
When I execute
$ python myscript.py subcmd -i ClearlyWrongValue
I also see the subcmd
help. Expected.
However, very annoyingly if I do the following
$ python myscript.py subcmd -i 2 --non-existent-argument WhateverValue
I see the default / main help and not subcmd
help.
What can I do, to ensure that this last case shows me the subcmd
help and not the main help? I thought the subparser structure would automatically procure the help from subcmd
as found in the third case, but it is not so? Why?
The unrecognized args
error is raised by parse_args
def parse_args(self, args=None, namespace=None):
args, argv = self.parse_known_args(args, namespace)
if argv:
msg = _('unrecognized arguments: %s')
self.error(msg % ' '.join(argv))
return args
The subparser is called via the cmd_par.__call__
with:
subnamespace, arg_strings = parser.parse_known_args(arg_strings, None)
for key, value in vars(subnamespace).items():
setattr(namespace, key, value)
if arg_strings:
vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
That is it is called with parse_known_args
, and it's extras
are returned to the main as UNRECOGNIZED
. So it's the main
than handles these, not the subparser.
In the $ python myscript.py subcmd -i ClearlyWrongValue
case, the subparser raises a ArgumentError
which is caught and converted into a self.error
call.
Similarly, the newish exit_on_error
parameter handles this kind of ArgumentError
, but does not handle the urecognized
error. There was some discussion of this in the bug/issues.
If you used parse_known_args
, the extras
would be ['--non-existent-argument', 'WhateverValue']
, without distinguishing which parser initially classified them as such.