Home > Back-end >  Unable to catch exception/ error for argparse
Unable to catch exception/ error for argparse

Time:09-18

I have the following script, wherein I define a logger and argparse instantiating function, which I then call in the main function.

Scenario 1: For the below code the error handling works as expected if a non-integer value is passed to the argument parser.

import argparse
import logging

def set_logger() -> logging.Logger:
    dtfmt = '%d-%m-%y %H:%M:%S'
    logging.basicConfig(level=logging.DEBUG, 
                        format='%(asctime)s :: [%(processName)s] :: %(levelname)s: %(message)s',
                        datefmt=dtfmt,
                        handlers=[logging.StreamHandler()])
    return logging.getLogger()

def argg() -> argparse.Namespace:
    parser = argparse.ArgumentParser(prog='ProgramName', 
                                     description="A program that does mircales when it runs. LOL!",
                                     exit_on_error=False)
    parser.add_argument('--integers', type=int)
    return parser.parse_args()

def main():
    logger = set_logger()

    try:
        print(f'Integer value: {argg().integers} supplied.')
    except (AttributeError, argparse.ArgumentError)as e:
        logger.error(e)

if __name__ == '__main__':
    main()

The logger generated error message looks like the following when the value 'a' is passed (expected behavior):

09-09-21 00:58:42 :: [MainProcess] :: ERROR: argument --integers: invalid int value: 'a'


Scenario 2: However, if the argg() function is re-defined as below, i.e. to include subparsers, the error handling fails to work when an invalid value is passed to the argument parser.

def argg():
    parser = argparse.ArgumentParser(prog='ProgramName', 
                                     description="A program that does mircales when it runs. LOL!",
                                     exit_on_error=False)
    subparsers = parser.add_subparsers(dest='mode')
    subparser1 = subparsers.add_parser('regular', help="run program in regular mode")
    subparser1.add_argument('-int', '--integers', type=int, required=True)
    return parser.parse_args()

Passing a value 'a' in this scenario, leads to the default argparse error that looks like this:

ProgramName regular: error: argument -int/--integers: invalid int value: 'a'

My question is, why would error handling fail (logger not get invoked) in scenario 2 (i.e. argparse with subparers)?

CodePudding user response:

Without the logger stuff, and in an ipython session. Your first case

In [11]: def argg0(argv) -> argparse.Namespace:
    ...:     parser = argparse.ArgumentParser(prog='ProgramName',
    ...:                                      description="A program that does mircales when it runs. LOL!",
    ...:                                      exit_on_error=False)
    ...:     parser.add_argument('--integers', type=int)
    ...:     return parser.parse_args(argv)
    ...: 
In [12]: argg0(['--integers','xxx'])
Traceback (most recent call last):
  File "/home/paul/mypy/argparse310.py", line 2476, in _get_value
    result = type_func(arg_string)
ValueError: invalid literal for int() with base 10: 'xxx'

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "<ipython-input-12-f7f84a36f9f8>", line 1, in <module>
    argg0(['--integers','xxx'])
  File "<ipython-input-11-0f7c85331b7b>", line 6, in argg0
    return parser.parse_args(argv)
  File "/home/paul/mypy/argparse310.py", line 1818, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/home/paul/mypy/argparse310.py", line 1856, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/home/paul/mypy/argparse310.py", line 2060, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/home/paul/mypy/argparse310.py", line 2000, in consume_optional
    take_action(action, args, option_string)
  File "/home/paul/mypy/argparse310.py", line 1912, in take_action
    argument_values = self._get_values(action, argument_strings)
  File "/home/paul/mypy/argparse310.py", line 2443, in _get_values
    value = self._get_value(action, arg_string)
  File "/home/paul/mypy/argparse310.py", line 2489, in _get_value
    raise ArgumentError(action, msg % args)
ArgumentError: argument --integers: invalid int value: 'xxx'

and the subparser case:

In [13]: def argg1(argv):
    ...:     parser = argparse.ArgumentParser(prog='ProgramName',
    ...:                                      description="A program that does mircales when it runs. LOL!",
    ...:                                      exit_on_error=False)
    ...:     subparsers = parser.add_subparsers(dest='mode')
    ...:     subparser1 = subparsers.add_parser('regular', help="run program in regular mode")
    ...:     subparser1.add_argument('-int', '--integers', type=int, required=True)
    ...:     return parser.parse_args(argv)
    ...: 
In [15]: argg1(['regular','--integers','xxx'])
usage: ProgramName regular [-h] -int INTEGERS
ProgramName regular: error: argument -int/--integers: invalid int value: 'xxx'

ipython error traceback adds:

An exception has occurred, use %tb to see the full traceback.
Traceback (most recent call last):
  File "/home/paul/mypy/argparse310.py", line 2476, in _get_value
    result = type_func(arg_string)
ValueError: invalid literal for int() with base 10: 'xxx'
...
ArgumentError: argument -int/--integers: invalid int value: 'xxx'

So exit_on_error=False is not functional in the case of subparsers.

edit

I just realized that exit_on_error does work - but it has to be part of the subparser definition:

subparser1 = subparsers.add_parser('regular', help="run program in regular mode",exit_on_error=False)

A subparser is created with the same class as a regular parser, and thus takes most of the same optional parameters. It does not inherit them; they have to be included explicitly. I've seen this in other cases, such as for the help formatter class.

  • Related