neo4k
neo4k

Reputation: 169

Show subparser help and not main parser help when non-existent arguments used?

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?

Upvotes: 0

Views: 158

Answers (1)

hpaulj
hpaulj

Reputation: 231355

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.

Upvotes: 1

Related Questions