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
.