pythonargparse

ignore argparse generated help and usage messages and issues errors when users give the program invalid arguments


Let s assume I have a parser:

import argparse
def int_main():
    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument('integers', metavar='N', type=int, nargs='+',
                    help='an integer for the accumulator')
    parser.add_argument('--sum', dest='accumulate', action='store_const',
                    const=sum, default=max,
                    help='sum the integers (default: find the max)')

    args = parser.parse_args()
    print(args.accumulate(args.integers))
    return 1

if __name__ == '__main__':
    int_main()

Assuming the Python code above is saved into a file called prog.py. If I give it wrong arguments in the command line:

python prog.py a b c

It gives this error message:

usage: prog.py [-h] [--sum] N [N ...]
prog.py: error: argument N: invalid int value: 'a'

In a context of mine, I don't want that error message to be generated, I want for example int_main() simply to return False if any error raised indicating parsing errors.

How to do that?

I tried :

import argparse
def int_main():
    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument('integers', metavar='N', type=int, nargs='+',
                    help='an integer for the accumulator')
    parser.add_argument('--sum', dest='accumulate', action='store_const',
                    const=sum, default=max,
                    help='sum the integers (default: find the max)')
    try:
        args = parser.parse_args()
        # print(args.accumulate(args.integers))
    except:
        return False
    return True

if __name__ == '__main__':
    res = int_main()
    print(res)

but didn't work.

Update

There is a bug it seems:

import argparse
parser = argparse.ArgumentParser(exit_on_error=False)
try:
    parser.parse_args('invalid arguments'.split())
except argparse.ArgumentError:
    print('ArgumentError caught.')

still exists on error even when set to False, how to fix this? I really want a solution to cover all possible exceptions and never outputs errors, this is because I am doing some unit testing and this can break things

update2

import argparse

def copy_(name1, name2):
    return True

def int_main():

    parser = argparse.ArgumentParser(prog='blabla',
                    exit_on_error=False,
                    argument_default=argparse.SUPPRESS,
                    add_help=False)

    subparsers = parser.add_subparsers(dest="subparser_name", help="")
    one_parser = subparsers.add_parser("copy_", help="")
    one_parser.add_argument(
        dest = "name1",
        type = str,
        metavar = "NAME1",
    )
    one_parser.add_argument(
        dest = "name2",
        type = str,
        metavar = "NAME2",
    )
    one_parser.set_defaults(func=copy_)

    try:
        args = parser.parse_args()
        print(args.func(args.name1, args.name2))
    except (SystemExit, SystemError, argparse.ArgumentError): 
        return False
    
    return True

if __name__ == '__main__':
    int_main()

This one still outputs:

usage: blabla copy_ [-h] NAME1 NAME2
blabla copy_: error: the following arguments are required: NAME2

when submitting this command python arg.py copy_ a. How can we see only False and not the error message?


Solution

  • Modifying your int_main to take an argv parameter which is then passed to parser.parse_args(argv), and redefining the error method as

    In [32]: def myerror(self, message):
        ...:     print('message:', message)
        ...:     print('no usage') # self.print_usage(_sys.stderr)
        ...:     self.exit(2)
        ...:     #args = {'prog': self.prog, 'message': message}
        ...:     #self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
        ...:     
    
    In [33]: argparse.ArgumentParser.error = myerror
    

    Your test case produces:

    In [34]: int_main('copy_ a'.split())
    message: the following arguments are required: NAME2
    no usage
    Out[34]: False
    

    and other errors caught by ArgumentError:

    In [35]: int_main(None)
    Out[35]: False
    
    In [36]: int_main('copy_ a b'.split())
    True
    Out[36]: True
    
    In [37]: int_main('foobar a b'.split())
    Out[37]: False
    

    This a crude error replacement, but gives an idea of what might be done.

    For unit testing I suspect redirecting stderr would better than customizing the argparse method. Be careful about blindly capturing errors. It could hide some serious or unexpected flaws.

    Removing the exit_on_error=False, produces the following 'error' displays:

    In [40]: int_main('copy_ a b'.split())
    True
    Out[40]: True
    
    In [41]: int_main(None)
    message: argument subparser_name: invalid choice: 'C:\\Users\\14256\\AppData\\Roaming\\jupyter\\runtime\\kernel-1783dfcfeed2.json' (choose from 'copy_')
    no usage
    Out[41]: False
    
    In [42]: int_main('foobar a b'.split())
    message: argument subparser_name: invalid choice: 'foobar' (choose from 'copy_')
    no usage
    Out[42]: False
    
    In [43]: int_main('copy_ a b c'.split())
    message: unrecognized arguments: c
    no usage
    Out[43]: False