Handle invalid arguments with argparse in Python - 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!

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.

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.

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))

Since the error code 2 is reserved for internal docker usage, I'm using the following to parse arguments in scripts inside docker containers:
ERROR_CODE = 1
class DockerArgumentParser(argparse.ArgumentParser):
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.
Overrides error method of the parent class to exit with error code 1 since default value is
reserved for internal docker usage
"""
self.print_usage(sys.stderr)
args = {'prog': self.prog, 'message': message}
self.exit(ERROR_CODE, '%(prog)s: error: %(message)s\n' % args)

Related

SystemExit: 2 error when calling parse_args() within ipython

I'm learning basics of Python and got already stuck at the beginning of argparse tutorial. I'm getting the following error:
import argparse
parser = argparse.ArgumentParser()
args = parser.parse_args()
usage: __main__.py [-h] echo
__main__.py: error: unrecognized arguments: -f
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2
a %tb command gives the following output:
SystemExit Traceback (most recent call last)
<ipython-input-16-843cc484f12f> in <module>()
----> 1 args = parser.parse_args()
C:\Users\Haik\Anaconda2\lib\argparse.pyc in parse_args(self, args, namespace)
1702 if argv:
1703 msg = _('unrecognized arguments: %s')
-> 1704 self.error(msg % ' '.join(argv))
1705 return args
1706
C:\Users\Haik\Anaconda2\lib\argparse.pyc in error(self, message)
2372 """
2373 self.print_usage(_sys.stderr)
-> 2374 self.exit(2, _('%s: error: %s\n') % (self.prog, message))
C:\Users\Haik\Anaconda2\lib\argparse.pyc in exit(self, status, message)
2360 if message:
2361 self._print_message(message, _sys.stderr)
-> 2362 _sys.exit(status)
2363
2364 def error(self, message):
SystemExit: 2
How could I fix this problem?
argparse is a module designed to parse the arguments passed from the command line, so for example if you type the following at a command prompt:
$ python my_programme.py --arg1=5 --arg2=7
You can use argparse to interpret the --arg1=5 --arg2=7 part. If argparse thinks the arguments are invalid, it exits, which in general is done in python by calling sys.exit() which raises the SystemExit error, which is what you're seeing.
So the problem is you're trying to use argparse from an interactive interpreter (looks like ipython), and at this point the programme has already started, so the args should have already been parsed.
To try it properly create a separate python file such as my_programme.py and run it using python from a command line, as I illustrated.
[quick solution] Add an dummy parser argument in the code
parser.add_argument('-f')
had run into a similar issue. adding these lines fixed the issue for me.
import sys
sys.argv=['']
del sys
parse_args method, when it's called without arguments, attempts to parse content of sys.argv. Your interpreter process had filled sys.argv with values that does not match with arguments supported by your parser instance, that's why parsing fails.
Try printing sys.argv to check what arguments was passed to your interpreter process.
Try calling parser.parse_args(['my', 'list', 'of', 'strings']) to see how parser will work for interpreter launched with different cmdline arguments.
I'm surprised nobody mentioned this answer here How to fix ipykernel_launcher.py: error: unrecognized arguments in jupyter?
There is no need for the -f argument. Also, the -f tricks works for Jupyter but not in VS code.
tl;dr
Use
args, unknown = parser.parse_known_args()
INSTEAD of
args = parser.parse_args()
I know this question is nearly three years old but as dumb as it can sound, this exit error is also produced when you don't have argparse installed instead of the default "This module can't be found" error message. Just helping people that may have this error aswell.
Add an argument and assign some value works. I was passing args (ArgumentParser type object) from one function to another (not in a typical case, like, getting user args from terminal).
from argparse import ArgumentParser
parser = ArgumentParser()
# create and assign a dummy args
parser.add_argument('--myarg1')
args = parser.parse_args(['--myarg1', ''])
args.myarg2 = True # actuals args assignment
myTargetFunction(args) # passing args to another function
I found without any actual args in parser, parse_args() gives error.
There are two ways of solving this:
Use get_ipython().__class__.__name__ to determine whether we're running on ipython or terminal, and then use parser.parse_args(args = []) when we're running on ipython
try:
get_ipython().__class__.__name__
# No error means we're running on ipython
args = parser.parse_args(args = []) # Reset args
except NameError:
# NameError means that we're running on terminal
args = parser.parse_args()
Use parser.parse_known_args() to store existing args separately. We would get a return of a tuple with two values (first is args that are added by add_argument and second is existing args)
args = parser.parse_known_args()[0] # Allow unrecognized arguments
The difference of these two approaches is that the second one will allow unrecognized arguments. It will be stored in the second value of the returned tuple.

Python3 override argparse error

I'm creating a program as a assignment in my school, I'm all done with it except one thing.
We have to make the program exit with different codes depending on how the execution went. In my program I'm processing options using "argparse" and when I use built-in functions like "version" I've managed to override the exit-code, but if I write a option that doesn't exist, then it won't work. It gives me the "unrecognized error"-message, and exits with code "0", I need it to exit with code 1. Is there anyway to do this? Been driving me nuts, have struggled with it for days now...
Thanks in advance!
/feeloor
Use sys.exit(returnCode) to exit with particular codes. Obviously on linux machines you need to no 8 bit right shift inorder to get the right return code.
To achieve something like this, inherit from argparse.ArgumentParser and reimplement the exit method (or perhaps the error method if you like).
For example:
class Parser(argparse.ArgumentParser):
# the default status on the parent class is 0, we're
# changing it to be 1 here ...
def exit(self, status=1, message=None):
return super().exit(status, message)
From the Python argparse documentation
https://docs.python.org/3/library/argparse.html#exiting-methods
16.4.5.9. Exiting methods
ArgumentParser.exit(status=0, message=None)
This method terminates the program, exiting with the specified status and, if given, it prints a message before that.
ArgumentParser.error(message)
This method prints a usage message including the message to the standard error and terminates the program with a status code of 2.
They both get a message, and pass it on. error adds usage and passes it on to exit. You can customize both in a subclassed Parser.
There are also examples of error catching and redirection the unittest file, test/test_argparse.py.
A problem with using a try/except wrapper is that the error information is written to sys.stderr, and not incorporated in the sys.exc_info.
In [117]: try:
parser.parse_args(['ug'])
except:
print('execinfo:',sys.exc_info())
.....:
usage: ipython3 [-h] [--test TEST] [--bar TEST] test test
ipython3: error: the following arguments are required: test
execinfo: (<class 'SystemExit'>, SystemExit(2,), <traceback object at 0xb31fb34c>)
The exit number is available in the exc_info, but not the message.
One option is to redirect sys.stderr at the same time as I do that try/except block.
Here's an example of changing the exit method and wrapping the call in a try block:
In [155]:
def altexit(status, msg):
print(status, msg)
raise ValueError(msg)
.....:
In [156]: parser.exit=altexit
In [157]:
try:
parser.parse_args(['--ug','ug','ug'])
except ValueError:
msg = sys.exc_info()[1]
.....:
usage: ipython3 [-h] [--test TEST] [--bar TEST] test test
2 ipython3: error: unrecognized arguments: --ug
In [158]: msg
Out[158]: ValueError('ipython3: error: unrecognized arguments: --ug\n')
Python lets me replace methods of existing objects. I don't recommend this in production code, but it is convenient when trying ideas. I capture the Error (my choice of ValueError is arbitrary), and save the message for later display or testing.
Generally the type of error (e.g. TypeError, ValueError, etc) is part of the public API, but the text of error is not. It can be refined from one Python release to the next without much notification. So you test for message details at your own risk.
I solved the problem catching SystemExit and determining what error by simply testing and comparing.
Thanks for all the help guys!

Python 2.x optionnal subparsers - Error too few arguments

I have been trying to set up a main parser with two subs parser so that when called alone, the main parser would display a help message.
def help_message():
print "help message"
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='sp')
parser_a = subparsers.add_parser('a')
parser_a.required = False
#some options...
parser_b = subparsers.add_parser('b')
parser_b.required = False
#some options....
args = parser.parse_args([])
if args.sp is None:
help_message()
elif args.sp == 'a':
print "a"
elif args.sp == 'b':
print "b"
This code works well on Python 3 and I would like it to work aswell on Python 2.x
I am getting this when running 'python myprogram.py'
myprogram.py: error: too few arguments
Here is my question : How can i manage to write 'python myprogram.py' in shell and get the help message instead of the error.
I think you are dealing the bug discussed in http://bugs.python.org/issue9253
Your subparsers is a positional argument. That kind of argument is always required, unless nargs='?' (or *). I think that is why you are getting the error message in 2.7.
But in the latest py 3 release, the method of testing for required arguments was changed, and subparsers fell through the cracks. Now they are optional (not-required). There's a suggested patch/fudge to make argparse behave as it did before (require a subparser entry). I expect that eventually py3 argparse will revert to the py2 practice (with a possible option of accepting a required=False parameter).
So instead of testing args.sp is None, you may want to test sys.argv[1:] before calling parse_args. Ipython does this to produce it's own help message.
For others - I ended up on this page while trying to figure out why I couldn't just call my script with no arguments while using argparse in general.
The tutorial demonstrates that the difference between an optional argument and a required argument is adding "--" to the name:
parser.add_argument("--show") <--- Optional arg
parser.add_argument("show") <--- Not optional arg

python: strange error behaviour in argparse

argparse is giving me different results when combining flags (-x -y > -xy). It's hard to explain in words, so I have reduced the problem to the following minimal setup:
# test.py
def invalid_argument_type(x):
raise Exception("can't parse this") # in my code, it doesn't *always* fail
parser = argparse.ArgumentParser()
parser.add_argument('args', type = invalid_argument_type)
parser.add_argument('-x')
print parser.parse_args()
Now, erroneously invoking this program yields unexpected results. The first command is correct, the second has an invalid flag, and the third should be the same as the second:
$ python test.py -x foo
Namespace(args=[], x='foo')
$ python test.py -A -x foo
test.py: error: unrecognized arguments: -A
$ python test.py -Ax foo
Exception: can't parse this
It seems that when the flags are combined, the "unknown flag" error swallows -x and foo is treated as a regular argument. Note that if the -A flag existed, both -A and -x would work as expected in every scenario.
This results in highly confusing error messages.
Am I using argparse wrong? Is there a way to fix this, or should I move the error handling into my own hands?
You are seeing this because exceptions happening during the argument parsing are of higher importance than simple argument errors. The argument parsing does recognize that -Ax is an invalid flag and simply keeps note of that to be displayed later; but because the parsing of args fails with an exception, that exception is displayed immediately and the other invalid arguments are simply no longer mentioned.
You can also confirm this behavior from the source. parse_args will delegate the parsing step to parse_known_args which returns the parsed namespace, and a list of invalid arguments. parse_known_args however will internally try to parse it and in case of an exception immediately display that. So exceptions will abort the process before parse_args can display the invalid arguments.
Now, your three examples all work differently, which is why you only see the exception for the last one. So let’s check them in detail:
-x foo: Here, -x is a valid argument name, and foo is the value for that. So all is well.
-A -x foo: -A is an unrecognized argument, so that’s noted down. The remaining part is -x foo which is again a valid sequence for the argument x.
-Ax foo: -Ax is an unrecognized argument, so that’s again noted down. The remaining part is foo. As there’s no argument flag, the parser will try to match that to the args parameter—which then raises an exception.
Note that argparse does only support combined flags (-Ax) if it can correctly parse those from left-to-right. Because single-dash flags can also be longer than one character (e.g. -foo would be fine), it cannot safely say that -Ax would be -A -x because you could have actually defined a -Ax argument. To be able to make this statement, it would actually start to parse -Ax where it will first try to find an argument that matches it. As there is no -A argument, it assumes that it should be -Ax. But that also doesn’t exist, so it fails there.

Python getopt taking second option as argument of first option

I am trying to use getoptsfor command line parsing. However, if I set the options to have mandatory arguments via :or = and no argument is given in the command line, the following option is taken as an argument of the first option. I would like this to raise an error instead. How can this be resolved?
Working example:
#!/usr/bin/env python
import sys, getopt, warnings
argv = sys.argv
try:
opts, args = getopt.getopt(argv[1:], "c:", ["config-file=", "sample-list="])
print >> sys.stderr, opts
except getopt.GetoptError as msg:
print msg
Running this script on the command line like this:
python getopt_test.py --config-file --sample-list
results in the following output (opts):
[('--config-file', '--sample-list')]
There is nothing wrong while running :
python getopt_test.py --config-file --sample-list
In your snippet:
opts, args = getopt.getopt(argv[1:], "c:", ["config-file=", "sample-list="])
# config-file requires argument i.e what ever argument come next in sys.argv list
# sample-list requires argument i.e what ever argument come next in sys.argv list
So, when you run as python getopt_test.py --config-file --sample-list,
--sample-list is just an argument to --config-file.
Lets confirm it by printing opts, which is list of tuple element containing first element inside tuple as the option name and second element as the arguments.
tmp/: python get.py --config-file --sample-list
[('--config-file', '--sample-list')] []
tmp/:
# lets put a proper argument for config-file
tmp/: python get.py --config-file 'argument_for_config_file'
[('--config-file', 'argument_for_config_file')] []
tmp/:
# Lets run with proper sample-list
tmp/: python get.py --config-file 'argument_for_config_file' --sample-list 'here is the
list'
[('--config-file', 'argument_for_config_file'), ('--sample-list', 'here is the list')]
[]
tmp/:
So, you need to write your own proper parse to make sure the user is providing the right options and argument. IF you are using optparse.
About the Exception: exception getopt.GetoptError
This is raised when an unrecognized option is found in the argument list or when an option
requiring an argument is given none. But in your case nothing of the rules was violate, thats why it was running silently without any error.
To prevent from all this optparse pitfall: highly recommend to use argparse which has tons of new and good feature to solve all your problem.

Categories