Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle invalid arguments with argparse in Python

I am using argparse to parse command line arguments and by default on receiving invalid arguments it prints help message and exit. Is it possible to customize the behavior of argparse when it receives invalid arguments?

Generally I want to catch all invalid arguments and do stuff with them. I am looking for something like:

parser = argparse.ArgumentParser()
# add some arguments here
try:
    parser.parse_args()
except InvalidArgvsError, iae:
    print "handle this invalid argument '{arg}' my way!".format(arg=iae.get_arg())

So that I can have:

>> python example.py --invalid some_text
handle this invalid argument 'invalid' my way!
like image 750
Yuhao Zhang Avatar asked Jul 12 '16 23:07

Yuhao Zhang


3 Answers

You might want to use parse_known_args and then take a look at the second item in the tuple to see what arguments were not understood.

That said, I believe this will only help with extra arguments, not expected arguments that have invalid values.

like image 166
user94559 Avatar answered Oct 26 '22 19:10

user94559


Some previous questions:

Python argparse and controlling/overriding the exit status code

I want Python argparse to throw an exception rather than usage

and probably more.

The argparse documentation talks about using parse_known_args. This returns a list of arguments that it does not recognize. That's a handy way of dealing with one type of error.

It also talks about writing your own error and exit methods. That error that you don't like passes through those 2 methods. The proper way to change those is to subclass ArgumentParser, though you can monkey-patch an existing parser. The default versions are at the end of the argparse.py file, so you can study what they do.

A third option is to try/except the Systemexit.

try:
    parser=argparse.ArgumentParser()
    args=parser.parse_args()
except SystemExit:
    exc = sys.exc_info()[1]
    print(exc)

This way, error/exit still produce the error message (to sys.stderr) but you can block exit and go on and do other things.

1649:~/mypy$ python stack38340252.py -x
usage: stack38340252.py [-h]
stack38340252.py: error: unrecognized arguments: -x
2

One of the earlier question complained that parser.error does not get much information about the error; it just gets a formatted message:

def myerror(message):
    print('error message')
    print(message)

parser=argparse.ArgumentParser()
parser.error = myerror
args=parser.parse_args()

displays

1705:~/mypy$ python stack38340252.py -x
error message
unrecognized arguments: -x

You could parse that message to find out the -x is the unrecognized string. In an improvement over earlier versions it can list multiple arguments

1705:~/mypy$ python stack38340252.py foo -x abc -b
error message
unrecognized arguments: foo -x abc -b

Look up self.error to see all the cases that can trigger an error message. If you need more ideas, focus on a particular type of error.

===============

The unrecognized arguments error is produced by parse_args, which calls parse_known_args, and raises this error if the extras is not empty. So its special information is the list of strings that parse_known_args could not handle.

parse_known_args for its part calls self.error if it traps an ArgumentError. Generally those are produced when a specific argument (Action) has problems. But _parse_known_args calls self.error if required Action is missing, or if there's a mutually-exclusive-group error. choices can produce a different error, as can type.

like image 39
hpaulj Avatar answered Oct 26 '22 18:10

hpaulj


You can try subclassing argparse.ArgumentParser() and overriding the error method.

From the argparse source:

def error(self, message):
        """error(message: string)

        Prints a usage message incorporating the message to stderr and
        exits.

        If you override this in a subclass, it should not return -- it
        should either exit or raise an exception.
        """
        self.print_usage(_sys.stderr)
        self.exit(2, _('%s: error: %s\n') % (self.prog, message))
like image 20
Maciej M Avatar answered Oct 26 '22 19:10

Maciej M