How can I get optparse's OptionParser to ignore invalid options? - python

In python's OptionParser, how can I instruct it to ignore undefined options supplied to method parse_args?
e.g.
I've only defined option --foo for my OptionParser instance, but I call parse_args with list: [ '--foo', '--bar' ]
I don't care if it filters them out of the original list. I just want undefined options ignored.
The reason I'm doing this is because I'm using SCons' AddOption interface to add custom build options. However, some of those options guide the declaration of the targets. Thus I need to parse them out of sys.argv at different points in the script without having access to all the options. In the end, the top level Scons OptionParser will catch all the undefined options in the command line.

Here's one way to have unknown arguments added to the result args of OptionParser.parse_args, with a simple subclass.
from optparse import (OptionParser,BadOptionError,AmbiguousOptionError)
class PassThroughOptionParser(OptionParser):
"""
An unknown option pass-through implementation of OptionParser.
When unknown arguments are encountered, bundle with largs and try again,
until rargs is depleted.
sys.exit(status) will still be called if a known argument is passed
incorrectly (e.g. missing arguments or bad argument types, etc.)
"""
def _process_args(self, largs, rargs, values):
while rargs:
try:
OptionParser._process_args(self,largs,rargs,values)
except (BadOptionError,AmbiguousOptionError), e:
largs.append(e.opt_str)
And here's a snippet to show that it works:
# Show that the pass-through option parser works.
if __name__ == "__main__": #pragma: no cover
parser = PassThroughOptionParser()
parser.add_option('-k', '--known-arg',dest='known_arg',nargs=1, type='int')
(options,args) = parser.parse_args(['--shazbot','--known-arg=1'])
assert args[0] == '--shazbot'
assert options.known_arg == 1
(options,args) = parser.parse_args(['--k','4','--batman-and-robin'])
assert args[0] == '--batman-and-robin'
assert options.known_arg == 4

By default there is no way to modify the behavior of the call to error() that is raised when an undefined option is passed. From the documentation at the bottom of the section on how optparse handles errors:
If optparse‘s default error-handling behaviour does not suit your needs, you’ll need to
subclass OptionParser and override its exit() and/or error() methods.
The simplest example of this would be:
class MyOptionParser(OptionParser):
def error(self, msg):
pass
This would simply make all calls to error() do nothing. Of course this isn't ideal, but I believe that this illustrates what you'd need to do. Keep in mind the docstring from error() and you should be good to go as you proceed:
Print a usage message incorporating 'msg' to stderr and
exit.
If you override this in a subclass, it should not return -- it
should either exit or raise an exception.

Python 2.7 (which didn't exist when this question was asked) now provides the argparse module. You may be able to use ArgumentParser.parse_known_args() to accomplish the goal of this question.

This is pass_through.py example from Optik distribution.
#!/usr/bin/env python
# "Pass-through" option parsing -- an OptionParser that ignores
# unknown options and lets them pile up in the leftover argument
# list. Useful for programs that pass unknown options through
# to a sub-program.
from optparse import OptionParser, BadOptionError
class PassThroughOptionParser(OptionParser):
def _process_long_opt(self, rargs, values):
try:
OptionParser._process_long_opt(self, rargs, values)
except BadOptionError, err:
self.largs.append(err.opt_str)
def _process_short_opts(self, rargs, values):
try:
OptionParser._process_short_opts(self, rargs, values)
except BadOptionError, err:
self.largs.append(err.opt_str)
def main():
parser = PassThroughOptionParser()
parser.add_option("-a", help="some option")
parser.add_option("-b", help="some other option")
parser.add_option("--other", action='store_true',
help="long option that takes no arg")
parser.add_option("--value",
help="long option that takes an arg")
(options, args) = parser.parse_args()
print "options:", options
print "args:", args
main()

Per synack's request in a different answer's comments, I'm posting my hack of a solution which sanitizes the inputs before passing them to the parent OptionParser:
import optparse
import re
import copy
import SCons
class NoErrOptionParser(optparse.OptionParser):
def __init__(self,*args,**kwargs):
self.valid_args_cre_list = []
optparse.OptionParser.__init__(self, *args, **kwargs)
def error(self,msg):
pass
def add_option(self,*args,**kwargs):
self.valid_args_cre_list.append(re.compile('^'+args[0]+'='))
optparse.OptionParser.add_option(self, *args, **kwargs)
def parse_args(self,*args,**kwargs):
# filter out invalid options
args_to_parse = args[0]
new_args_to_parse = []
for a in args_to_parse:
for cre in self.valid_args_cre_list:
if cre.match(a):
new_args_to_parse.append(a)
# nuke old values and insert the new
while len(args_to_parse) > 0:
args_to_parse.pop()
for a in new_args_to_parse:
args_to_parse.append(a)
return optparse.OptionParser.parse_args(self,*args,**kwargs)
def AddOption_and_get_NoErrOptionParser( *args, **kwargs):
apply( SCons.Script.AddOption, args, kwargs)
no_err_optparser = NoErrOptionParser(optparse.SUPPRESS_USAGE)
apply(no_err_optparser.add_option, args, kwargs)
return no_err_optpars

Related

How to write a Python script which tests the program and start from command line this way: python unit_test.py program_to_test.py input_data.txt [duplicate]

I would like to set
sys.argv
so I can unit test passing in different combinations. The following doesn't work:
#!/usr/bin/env python
import argparse, sys
def test_parse_args():
global sys.argv
sys.argv = ["prog", "-f", "/home/fenton/project/setup.py"]
setup = get_setup_file()
assert setup == "/home/fenton/project/setup.py"
def get_setup_file():
parser = argparse.ArgumentParser()
parser.add_argument('-f')
args = parser.parse_args()
return args.file
if __name__ == '__main__':
test_parse_args()
Then running the file:
pscripts % ./test.py
File "./test.py", line 4
global sys.argv
^
SyntaxError: invalid syntax
pscripts %
Changing sys.argv at runtime is a pretty fragile way of testing. You should use mock's patch functionality, which can be used as a context manager to substitute one object (or attribute, method, function, etc.) with another, within a given block of code.
The following example uses patch() to effectively "replace" sys.argv with the specified return value (testargs).
try:
# python 3.4+ should use builtin unittest.mock not mock package
from unittest.mock import patch
except ImportError:
from mock import patch
def test_parse_args():
testargs = ["prog", "-f", "/home/fenton/project/setup.py"]
with patch.object(sys, 'argv', testargs):
setup = get_setup_file()
assert setup == "/home/fenton/project/setup.py"
test_argparse.py, the official argparse unittest file, uses several means of setting/using argv:
parser.parse_args(args)
where args is a list of 'words', e.g. ['--foo','test'] or --foo test'.split().
old_sys_argv = sys.argv
sys.argv = [old_sys_argv[0]] + args
try:
return parser.parse_args()
finally:
sys.argv = old_sys_argv
This pushes the args onto sys.argv.
I just came across a case (using mutually_exclusive_groups) where ['--foo','test'] produces different behavior than '--foo test'.split(). It's a subtle point involving the id of strings like test.
global only exposes global variables within your module, and sys.argv is in sys, not your module. Rather than using global sys.argv, use import sys.
You can avoid having to change sys.argv at all, though, quite simply: just let get_setup_file optionally take a list of arguments (defaulting to None) and pass that to parse_args. When get_setup_file is called with no arguments, that argument will be None, and parse_args will fall back to sys.argv. When it is called with a list, it will be used as the program arguments.
I like to use unittest.mock.patch(). The difference to patch.object() is that you don't need a direct reference to the object you want to patch but use a string.
from unittest.mock import patch
with patch("sys.argv", ["file.py", "-h"]):
print(sys.argv)
It doesn't work because you're not actually calling get_setup_file. Your code should read:
import argparse
def test_parse_args():
sys.argv = ["prog", "-f", "/home/fenton/project/setup.py"]
setup = get_setup_file() # << You need the parentheses
assert setup == "/home/fenton/project/setup.py"
I achieved this by creating an execution manager that would set the args of my choice and remove them upon exit:
import sys
class add_resume_flag(object):
def __enter__(self):
sys.argv.append('--resume')
def __exit__(self, typ, value, traceback):
sys.argv = [arg for arg in sys.argv if arg != '--resume']
class MyTestClass(unittest.TestCase):
def test_something(self):
with add_resume_flag():
...
Very good question.
The trick to setting up unit tests is all about making them repeatable. This means that you have to eliminate the variables, so that the tests are repeatable. For example, if you are testing a function that must perform correctly given the current date, then force it to work for specific dates, where the date chosen does not matter, but the chosen dates match in type and range to the real ones.
Here sys.argv will be an list of length at least one. So create a "fakemain" that gets called with a list. Then test for the various likely list lengths, and contents. You can then call your fake main from the real one passing sys.argv, knowing that fakemain works, or alter the "if name..." part to do perform the normal function under non-unit testing conditions.
You'll normally have command arguments. You need to test them. Here is how to unit test them.
Assume program may be run like: % myprogram -f setup.py
We create a list to mimic this behaviour. See line (4)
Then our method that parses args, takes an array as an argument that is defaulted to None. See line (7)
Then on line (11) we pass this into parse_args, which uses the array if it isn't None. If it is None then it defaults to using sys.argv.
1: #!/usr/bin/env python
2: import argparse
3: def test_parse_args():
4: my_argv = ["-f", "setup.py"]
5: setup = get_setup_file(my_argv)
6: assert setup == "setup.py"
7: def get_setup_file(argv=None):
8: parser = argparse.ArgumentParser()
9: parser.add_argument('-f')
10: # if argv is 'None' then it will default to looking at 'sys.argv'
11: args = parser.parse_args(argv)
12: return args.f
13: if __name__ == '__main__':
14: test_parse_args()
You can attach a wrapper around your function, which prepares sys.argv before calling and restores it when leaving:
def run_with_sysargv(func, sys_argv):
""" prepare the call with given sys_argv and cleanup afterwards. """
def patched_func(*args, **kwargs):
old_sys_argv = list(sys.argv)
sys.argv = list(sys_argv)
try:
return func(*args, **kwargs)
except Exception, err:
sys.argv = old_sys_argv
raise err
return patched_func
Then you can simply do
def test_parse_args():
_get_setup_file = run_with_sysargv(get_setup_file,
["prog", "-f", "/home/fenton/project/setup.py"])
setup = _get_setup_file()
assert setup == "/home/fenton/project/setup.py"
Because the errors are passed correctly, it should not interfere with external instances using the testing code, like pytest.

Simple command line application in python - parse user input?

New to python here - I want to make a command line application where the user will type input I will parse it and execute some command - something in the lines of:
try:
while True:
input = raw_input('> ')
# parse here
except KeyboardInterrupt:
pass
The user is supposed to type commands like init /path/to/dir. Can I use argparse to parse those ? Is my way too crude ?
You can take a look at the cmd lib: http://docs.python.org/library/cmd.html
If you want to parse by yourself, you can use split to tokenize the user input, and execute your commands based on the tokens, sort of like this:
try:
while True:
input = raw_input('> ')
tokens = input.split()
command = tokens[0]
args = tokens[1:]
if command == 'init':
# perform init command
elif command == 'blah':
# perform other command
except KeyboardInterrupt:
pass
arparse is a perfect solution for what you propose. The docs are well written and show dozens of example of how to invoke it simply. Keep in mind, it wants to read sys.argv by default, so when you invoke parse_args, you want to give it args (https://docs.python.org/2.7/library/argparse.html?highlight=argparse#the-parse-args-method).
The only downsize is argparse expects the items to be in "parameter" format, which means prefixed with dashes.
>>> import argparse
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('-init', nargs=1)
>>> parser.parse_args('-init /path/to/something'.split())
Namespace(init="/path/to/something")
It depends on what you want to do, but you could have your script use ipython (interactive python). For instance:
#!/bin/ipython -i
def init(path_to_dir):
print(path_to_dir)
Usage: after staring the script,
init("pathToFile.txt")
You are running in an interactive python session, so you get features like tab completion that would be difficult to implement manually. On the other hand, you are stuck with python syntax. It depends on your application.
What I did was:
# main
parser = Parser('blah')
try:
while True:
# http://stackoverflow.com/a/17352877/281545
cmd = shlex.split(raw_input('> ').strip())
logging.debug('command line: %s', cmd)
try:
parser.parse(cmd)
except SystemExit: # DUH http://stackoverflow.com/q/16004901/281545
pass
except KeyboardInterrupt:
pass
Where the parser:
class Parser(argparse.ArgumentParser):
def __init__(self, desc, add_h=True):
super(Parser, self).__init__(description=desc, add_help=add_h,
formatter_class=argparse.
ArgumentDefaultsHelpFormatter)
# https://docs.python.org/dev/library/argparse.html#sub-commands
self.subparsers = subparsers = self.add_subparsers(
help='sub-command help')
# http://stackoverflow.com/a/8757447/281545
subparsers._parser_class = argparse.ArgumentParser
from watcher.commands import CMDS
for cmd in CMDS: cmd()(subparsers)
def parse(self, args):
return self.parse_args(args)
And a command (CMDS=[watch.Watch]):
class Watch(Command):
class _WatchAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
# here is the actual logic of the command
logging.debug('%r %r %r' % (namespace, values, option_string))
setattr(namespace, self.dest, values)
Sync.addObserver(path=values)
CMD_NAME = 'watch'
CMD_HELP = 'Watch a directory tree for changes'
ARGUMENTS = {'path': Arg(hlp='Path to a directory to watch. May be '
'relative or absolute', action=_WatchAction)}
where:
class Command(object):
"""A command given by the users - subclasses must define the CMD_NAME,
CMD_HELP and ARGUMENTS class fields"""
def __call__(self, subparsers):
parser_a = subparsers.add_parser(self.__class__.CMD_NAME,
help=self.__class__.CMD_HELP)
for dest, arg in self.__class__.ARGUMENTS.iteritems():
parser_a.add_argument(dest=dest, help=arg.help, action=arg.action)
return parser_a
class Arg(object):
"""Wrapper around cli arguments for a command"""
def __init__(self, hlp=None, action='store'):
self.help = hlp
self.action = action
Only tried with one command so far so this is rather untested. I used the shlex and subparsers tips from comments. I had a look at the cmd module suggested by #jh314 but did not quite grok it - however I think it is the tool for the job - I am interested in an answer with code doing what I do but using the cmd module.

Use argparse with Setuptools entry_points

I'm writing a script which I want to distribute using Setuptools. I have added this script to the entry_points section in my setup.py.
From the setuptools docs:
The functions you specify are called with no arguments, and their return value is passed to sys.exit(), so you can return an errorlevel or message to print to stderr.
Since the method will return instead of exit it becomes more testable. For testability purposes I accept arguments in the method defaulting to sys.argv. So far so good.
The problem arises when argparse is added to the mix. When argparse fails to parse args it calls sys.exit. Now I would really prefer that argparse doesn't do this as this is handled by the setuptools wrapper. The first thing I could think of to fix this is to override the argparse.ArgumentParser but then I saw this:
# ===============
# Exiting methods
# ===============
def exit(self, status=0, message=None):
if message:
self._print_message(message, _sys.stderr)
_sys.exit(status)
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))
So the docstring states I should not return and stick with raising an exception. How should I solve this?
The main method if I didn't explain it thoroughly enough:
def main(args=sys.argv):
parser = ArgumentParser(prog='spam')
# parser is configured here
parsed = parser.parse_args(args)
# Parsed args are used here
The reason you don't want to return from error is that the parser will continue parsing. Some errors are raised near the end (e.g. about unparsed strings), but others can occur early (e.g. bad type for the first argument string). The behavior of parse_args is unpredictable if you return from the error method. Normally you want the parser to quit and return control your code.
What you want to do is wrap the parse_args() call in a try: except SystemExit: block. I often use test scripts like this:
for test in ['-o FILE',
...
]:
print(test)
try:
print(parser.parse_args(test.split()))
except SystemExit:
pass
You could use error and/or exit to return other kinds of Exceptions. They could also bypass the usage message. But in one way or other you need to trap the exception in your wrapper.
If you're starting on a fresh project or have time for some refactoring, then you might consider using the Click library. Click has both setuptools integration and 'testability' as features, among other considerations.
Here's an example / test-snippet from the docs that both creates a mini command-line interface, and then tests it immediately:
import click
from click.testing import CliRunner
def test_hello_world():
#click.command()
#click.argument('name')
def hello(name):
click.echo('Hello %s!' % name)
runner = CliRunner()
result = runner.invoke(hello, ['Peter'])
assert result.exit_code == 0
assert result.output == 'Hello Peter!\n'

How do I get the user specified list of optional command line args not containing the defaults using argparse in python?

I want to know which options were explicitly passed through command-line.
Consider the following argparse setup in test.py:
parser.add_argument("--foo", default=True, action="store_true")
parser.add_argument("--bar", default=False, action="store_true")
When I execute ./test.py --foo --bar, I shall get foo=True, bar=True in the Namespace.
In this case, --foo and --bar were passed explicitly through command-line.
When I execute ./test.py --bar, I shall still get foo=True, bar=True in the Namespace.
So, I need to find which args were actually passed while executing through command-line (in the 2nd case : --bar), without sacrificing the defaults functionality.
One approach is to search in argv, but it's not efficient and doesn't look elegant.
I want to know, if there is any argparse api or any other better approach which shall allow me to do this?
Simply set no default. If the variable is not set, the user did not pass it. After checking that, you can handle the default yourself.
With a 'store_true' action, the builtin default is 'False'
With
parser.add_argument('--foo',action='store_true')
no input produces
Namespace(foo=False)
while '--foo' produces
Namespace(foo=True)
with
parser.add_argument('--foo',action='store_true', default=True)
it is always foo=True. That argument is useless. DO NOT set your own default when using 'store_true' or 'store_false'.
If you want to know whether the user gave you a --foo or not, use the first form, and check whether the namespace value is true or not. If in later code you need foo to be True regardless of what the user gave you, set it explicitly, after you have used argparse.
The answers and comments here recommended to not set defaults, and then handle the defaults on my own within the code. However, the add_argument calls aren't completely under my control, so this wasn't really an option.
Initially I went with checking the presence of the options in sys.argv. This approach quickly proved inefficient, bug-prone and not at all scalable.
Finally, I ended up with this which seems to be working just fine:
class _Reflection(object):
def __init__(self, source, reflection, name=None):
self.source = source
self.reflection = reflection
self.name = name
def __getattr__(self, attribute):
self.attribute = attribute
return _Reflection(self.source.__getattribute__(attribute), self.reflection.__getattribute__(attribute), name=attribute)
def __call__(self, *args, **kwargs):
source_output = self.source(*args, **kwargs)
if self.name == 'add_argument':
# if the method being called is 'add_argument',
# over-ride the 'default' argument's value to 'None' in our secondary argparser.
kwargs['default'] = None
reflection_output = self.reflection(*args, **kwargs)
return _Reflection(source_output, reflection_output)
class ReflectionArgumentParser(object):
def create(self, *args, **kwargs):
self.parser = argparse.ArgumentParser(*args, **kwargs)
self._mirror = argparse.ArgumentParser(*args, **kwargs)
return _Reflection(self.parser, self._mirror)
def parse_args(self, *args, **kwargs):
return self.parser.parse_args(*args, **kwargs)
def filter_defaults(self, *args, **kwargs):
return self._mirror.parse_args(*args, **kwargs)
mirrorParser = ReflectionArgumentParser()
parser = mirrorParser.create()
parser.add_argument('-f', '--foo', default=False, action="store_true")
parser.add_argument('-b', '--baz', default=0, action="store_const", const=10)
parser.add_argument('bar', nargs='*', default='bar')
print mirrorParser.parse_args([])
# Outputs: Namespace(bar='bar', baz=0, foo=False)
print mirrorParser.filter_defaults([])
# Outputs: Namespace(bar=[], baz=None, foo=None)
print mirrorParser.filter_defaults('--foo -b lorem ipsum'.split())
# Outputs: Namespace(bar=['lorem', 'ipsum'], baz=10, foo=True)
I have tried this implementation with argument-groups and subparsers.
This doesn't deal with set_defaults method, however the additions required are trivial.
This is possible using the ArgumentParser.get_default(dest) method.
Basically, you iterate over all of the parsed arguments and collect which ones are not equal to the default value:
args = parser.parse_args()
non_default = {arg: value for (arg, value) in vars(args).iteritems() if value != parser.get_default(arg)}
Although this doesn't work in your specific example because --foo is a do-nothing argument (it sets the variable to the default value).

Python argparse and controlling/overriding the exit status code

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

Categories