Python argparse and controlling/overriding the exit status code

PythonArgparse

Python Problem Overview


Apart from tinkering with the argparse source, is there any way to control the exit status code should there be a problem when parse_args() is called, for example, a missing required switch?

Python Solutions


Solution 1 - Python

I'm not aware of any mechanism to specify an exit code on a per-argument basis. You can catch the SystemExit exception raised on .parse_args() but I'm not sure how you would then ascertain what specifically caused the error.

EDIT: For anyone coming to this looking for a practical solution, the following is the situation:

  • ArgumentError() is raised appropriately when arg parsing fails. It is passed the argument instance and a message
  • ArgumentError() does not store the argument as an instance attribute, despite being passed (which would be convenient)
  • It is possible to re-raise the ArgumentError exception by subclassing ArgumentParser, overriding .error() and getting hold of the exception from sys.exc_info()

All that means the following code - whilst ugly - allows us to catch the ArgumentError exception, get hold of the offending argument and error message, and do as we see fit:

import argparse
import sys

class ArgumentParser(argparse.ArgumentParser):    
    def _get_action_from_name(self, name):
        """Given a name, get the Action instance registered with this parser.
        If only it were made available in the ArgumentError object. It is 
        passed as it's first arg...
        """
        container = self._actions
        if name is None:
            return None
        for action in container:
            if '/'.join(action.option_strings) == name:
                return action
            elif action.metavar == name:
                return action
            elif action.dest == name:
                return action
    
    def error(self, message):
        exc = sys.exc_info()[1]
        if exc:
            exc.argument = self._get_action_from_name(exc.argument_name)
            raise exc
        super(ArgumentParser, self).error(message)

## usage:
parser = ArgumentParser()
parser.add_argument('--foo', type=int)
try:
    parser.parse_args(['--foo=d'])
except argparse.ArgumentError, exc:
    print exc.message, '\n', exc.argument

Not tested in any useful way. The usual don't-blame-me-if-it-breaks indemnity applies.

Solution 2 - Python

All the answers nicely explain the details of argparse implementation.

Indeed, as proposed in PEP (and pointed by Rob Cowie) one should inherit ArgumentParser and override the behavior of error or exit methods.

In my case I just wanted to replace usage print with full help print in case of the error:

class ArgumentParser(argparse.ArgumentParser):
    
    def error(self, message):
        self.print_help(sys.stderr)
        self.exit(2, '%s: error: %s\n' % (self.prog, message))

In case of override main code will continue to contain the minimalistic..

# Parse arguments.
args = parser.parse_args()
# On error this will print help and cause exit with explanation message.

Solution 3 - Python

Perhaps catching the SystemExit exception would be a simple workaround:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('foo')
try:
    args = parser.parse_args()
except SystemExit:
    print("do something else")
    

Works for me, even in an interactive session.

Edit: Looks like @Rob Cowie beat me to the switch. Like he said, this doesn't have very much diagnostic potential, unless you want get silly and try to glean info from the traceback.

Solution 4 - Python

As of Python 3.9, this is no longer so painful. You can now handle this via the new argparse.ArgumentParser exit_on_error instantiation argument. Here is an example (slightly modified from the python docs: argparse#exit_on_error):

parser = argparse.ArgumentParser(exit_on_error=False)
parser.add_argument('--integers', type=int)

try:
    parser.parse_args('--integers a'.split())
except argparse.ArgumentError:
    print('Catching an argumentError')
    exit(-1)

Solution 5 - Python

You can use one of the exiting methods: http://docs.python.org/library/argparse.html#exiting-methods. It should already handle situations where the arguments are invalid, however (assuming you have defined your arguments properly).

Using invalid arguments:

% [ $(./test_argparse.py> /dev/null 2>&1) ] || { echo error } 
error # exited with status code 2

Solution 6 - Python

You'd have to tinker. Look at argparse.ArgumentParser.error, which is what gets called internally. Or you could make the arguments non-mandatory, then check and exit outside argparse.

Solution 7 - Python

I needed a simple method to catch an argparse error at application start and pass the error to a wxPython form. Combining the best answers from above resulted in the following small solution:

import argparse

# sub class ArgumentParser to catch an error message and prevent application closing
class MyArgumentParser(argparse.ArgumentParser):

    def __init__(self, *args, **kwargs):
        super(MyArgumentParser, self).__init__(*args, **kwargs)

        self.error_message = ''

    def error(self, message):
        self.error_message = message

    def parse_args(self, *args, **kwargs):
        # catch SystemExit exception to prevent closing the application
        result = None
        try:
            result = super().parse_args(*args, **kwargs)
        except SystemExit:
            pass
        return result


# testing ------- 
my_parser = MyArgumentParser()
my_parser.add_argument('arg1')

my_parser.parse_args()

# check for an error
if my_parser.error_message:
    print(my_parser.error_message)

running it:

>python test.py
the following arguments are required: arg1

Solution 8 - Python

While argparse.error is a method and not a class its not possible to "try", "except" all "unrecognized arguments" errors. If you want to do so you need to override the error function from argparse:

def print_help(errmsg):
   print(errmsg.split(' ')[0])

parser.error = print_help
args = parser.parse_args()

on an invalid input it will now print:

unrecognised

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevView Question on Stackoverflow
Solution 1 - PythonRob CowieView Answer on Stackoverflow
Solution 2 - PythonYauhen YakimovichView Answer on Stackoverflow
Solution 3 - PythonGreg HaskinsView Answer on Stackoverflow
Solution 4 - PythonshellsterView Answer on Stackoverflow
Solution 5 - PythonzeekayView Answer on Stackoverflow
Solution 6 - PythonTobuView Answer on Stackoverflow
Solution 7 - PythonMaceView Answer on Stackoverflow
Solution 8 - Pythond0nView Answer on Stackoverflow