python argparse: How can I display help automatically on error? - python

Currently when I enter invalid options or omit positional arguments, argparse kicks me back to the prompt and displays the usage for my app. This is ok, but I would rather automatically display the full help listing (that explains the options, etc) than require the user to type
./myscript.py -h
Thanks!
Jamie

To print help you might want to use: print_help function on ArgumentParser instance
parser = argparse.ArgumentParser()
(...)
parser.print_help()
To print help message on error you need to create own subclass of ArgumentParser instance, that overrides error() method. For example like that:
class MyParser(argparse.ArgumentParser):
def error(self, message):
sys.stderr.write('error: %s\n' % message)
self.print_help()
sys.exit(2)
When this parser encounters unparseable argument line it will print help.

This thread over at Google groups has the following code snippet which seems to do the trick (modified slightly).
class DefaultHelpParser(argparse.ArgumentParser):
def error(self, message):
sys.stderr.write('error: %s\n' % message)
self.print_help()
sys.exit(2)

I just fixed this same problem myself using the following syntax:
parser = ArgumentParser()
... add arguments ...
parser.usage = parser.format_help()
args = parser.parse_args()

Suppress printing of usage with usage=argparse.SUPPRESS. Then catch the SystemExit exception that ArgumentParser raises on error, print the help, and exit by raising the exception again.
parser = argparse.ArgumentParser(usage=argparse.SUPPRESS)
parser.add_argument(...)
try:
args = parser.parse_args()
except SystemExit:
parser.print_help()
raise

You, also, can print help without using a class or exception:
def _error(parser):
def wrapper(interceptor):
parser.print_help()
sys.exit(-1)
return wrapper
def _args_get(args=sys.argv[1:]):
parser = argparser.ArgumentParser()
parser.error = _error(parser)
parser.add_argument(...)
...
. Just wrap ArgumentParser.error function in your and intercept message argument. I answered, there, earlier:
https://stackoverflow.com/a/60714163/10152015

Related

Python ArgParse add help message when subparsers are omitted [duplicate]

With python's argparse, how do I make a subcommand a required argument? I want to do this because I want argparse to error out if a subcommand is not specified. I override the error method to print help instead. I have 3-deep nested subcommands, so it's not a matter of simply handling zero arguments at the top level.
In the following example, if this is called like so, I get:
$./simple.py
$
What I want it to do instead is for argparse to complain that the required subcommand was not specified:
import argparse
class MyArgumentParser(argparse.ArgumentParser):
def error(self, message):
self.print_help(sys.stderr)
self.exit(0, '%s: error: %s\n' % (self.prog, message))
def main():
parser = MyArgumentParser(description='Simple example')
subs = parser.add_subparsers()
sub_one = subs.add_parser('one', help='does something')
sub_two = subs.add_parser('two', help='does something else')
parser.parse_args()
if __name__ == '__main__':
main()
There was a change in 3.3 in the error message for required arguments, and subcommands got lost in the dust.
http://bugs.python.org/issue9253#msg186387
There I suggest this work around, setting the required attribute after the subparsers is defined.
parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()
update
A related pull-request: https://github.com/python/cpython/pull/3027
In addition to hpaulj's answer: you can also use the required keyword argument with ArgumentParser.add_subparsers() since Python 3.7. You also need to pass dest as argument. Otherwise you will get an error: TypeError: sequence item 0: expected str instance, NoneType found.
Example file example.py:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', required=True)
foo_parser = subparsers.add_parser("foo", help="command foo")
args = parser.parse_args()
Output of the call without an argument:
$ python example.py
usage: example.py [-h] {foo} ...
example.py: error: the following arguments are required: command
How about using required=True? More info here.
You can use the dest argument, which is documented in the last example in the documentation for add_subparsers():
# required_subparser.py
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser_name')
one = subparsers.add_parser('one')
two = subparsers.add_parser('two')
args = parser.parse_args()
Running with Python 2.7:
$python required_subparser.py
usage: required_subparser.py [-h] {one,two} ...
required_subparser.py: error: too few arguments
$python required_subparser.py one
$# no error

Python using argparse with cmd

Is there a way to use the argparse module hooked in as the interpreter for every prompt in an interface inheriting from cmd?
I'd like for my cmd interface to interpret the typical line parameter in the same way one would interpret the options and arguments passed in at runtime on the bash shell, using optional arguments with - as well as positional arguments.
Well, one way to do that is to override cmd's default method and use it to parse the line with argparse, because all commands without do_ method in your cmd.Cmd subclass will fall through to use the default method. Note the additional _ before do_test to avoid it being used as cmd's command.
import argparse
import cmd
import shlex
class TestCLI(cmd.Cmd):
def __init__(self, **kwargs):
cmd.Cmd.__init__(self, **kwargs)
self.parser = argparse.ArgumentParser()
subparsers = self.parser.add_subparsers()
test_parser = subparsers.add_parser("test")
test_parser.add_argument("--foo", default="Hello")
test_parser.add_argument("--bar", default="World")
test_parser.set_defaults(func=self._do_test)
def _do_test(self, args):
print args.foo, args.bar
def default(self, line):
args = self.parser.parse_args(shlex.split(line))
if hasattr(args, 'func'):
args.func(args)
else:
cmd.Cmd.default(self, line)
test = TestCLI()
test.cmdloop()
argparse does a sys.exit if it encounters unknown commands, so you would need to override or monkey patch your ArgumentParser's error method to raise an exception instead of exiting and handle that in the default method, in order to stay in cmd's command loop.
I would suggest you look into cliff which allows you to write commands that can automatically be used both as argparse and cmd commands, which is pretty neat. It also supports loading commands from setuptools entry points, which allows you to distribute commands as plugins to your app. Note however, that cliff uses cmd2, which is cmd's more powerful cousin, but you can replace it cmd as cmd2 was developed as a drop-in replacement for cmd.
The straight forward way would be to create an argparse parser, and parse line.split() within your function, expecting SystemExit in case invalid arguments are supplied (parse_args() calls sys.exit() when it finds invalid arguments).
class TestInterface(cmd.Cmd):
__test1_parser = argparse.ArgumentParser(prog="test1")
__test1_parser.add_argument('--bar', help="bar help")
def help_test1(self): self.__test1_parser.print_help()
def do_test1(self, line):
try:
parsed = self.__test1_parser.parse_args(line.split())
except SystemExit:
return
print("Test1...")
print(parsed)
If invalid arguments are passed, parse_args() will print errors, and the program will return to the interface without exiting.
(Cmd) test1 --unk
usage: test1 [-h] [--bar BAR]
test1: error: unrecognized arguments: --unk
(Cmd)
Everything else should work the same as a regular argparse use case, also maintaining all of cmd's functionality (help messages, function listing, etc.)
Source: https://groups.google.com/forum/#!topic/argparse-users/7QRPlG97cak
Another way, which simplifies the setup above, is using the decorator below:
class ArgparseCmdWrapper:
def __init__(self, parser):
"""Init decorator with an argparse parser to be used in parsing cmd-line options"""
self.parser = parser
self.help_msg = ""
def __call__(self, f):
"""Decorate 'f' to parse 'line' and pass options to decorated function"""
if not self.parser: # If no parser was passed to the decorator, get it from 'f'
self.parser = f(None, None, None, True)
def wrapped_f(*args):
line = args[1].split()
try:
parsed = self.parser.parse_args(line)
except SystemExit:
return
f(*args, parsed=parsed)
wrapped_f.__doc__ = self.__get_help(self.parser)
return wrapped_f
#staticmethod
def __get_help(parser):
"""Get and return help message from 'parser.print_help()'"""
f = tempfile.SpooledTemporaryFile(max_size=2048)
parser.print_help(file=f)
f.seek(0)
return f.read().rstrip()
It makes defining additional commands simpler, where they take an extra parsed parameter that contains the result of a successful parse_args(). If there are any invalid arguments the function is never entered, everything being handled by the decorator.
__test2_parser = argparse.ArgumentParser(prog="test2")
__test2_parser.add_argument('--foo', help="foo help")
#WrapperCmdLineArgParser(parser=__test2_parser)
def do_test2(self, line, parsed):
print("Test2...")
print(parsed)
Everything works as the original example, including argparse generated help messages - without the need to define a help_command() function.
Source: https://codereview.stackexchange.com/questions/134333/using-argparse-module-within-cmd-interface

Don't parse options after the last positional argument

I'm writing a wrapper around the ssh command line client. After the first positional argument that's part of command, all further options should also be treated as positional arguments.
Under optparse, I believe this would be done with disable_interspersed_args.
Presently I have something like this:
parser = argparse.ArgumentParser()
parser.add_argument('--parallel', default=False, action='store_true')
# maybe allow no command? this would ssh interactively into each machine...
parser.add_argument('command', nargs='+')
args = parser.parse_args()
But if options are passed as part of the command (such as my_wrapper ls -l), they're instead interpreted by ArgumentParser as unknown options. error: unrecognized arguments: -l
If I use parse_known_args(), the options may be taken out of order.
p = argparse.ArgumentParser()
p.add_argument('-a', action='store_true')
p.add_argument('command', nargs='+')
print(p.parse_known_args())
$ python3 bah.py -b ls -l -a
(Namespace(a=True, command=['ls']), ['-b', '-l'])
Here you can see that -b's position before ls has been lost, and -a has been parsed out from the command, which is not desired.
How can I:
Prevent arguments from being parsed after a certain point?
Disable parsing of interspersed arguments?
Allow arguments with a prefix to be consumed as positional arguments?
I had the same problem. I found the solution on the argparse bug tracker: http://code.google.com/p/argparse/issues/detail?id=52
The solution is simple: replace nargs='+' (or '*') with nargs=argparse.REMAINDER. This special value is not documented, but it does what you want.
I think your best bet to start solving these issues is to try out -- after all your optional args. -- is a pseudo-arg that tells ArgumentParser that everything after is a positional argument. Docs are here
As for prevent arguments from being parsed after a certain point, you can pass part of argv to parse_args. That combined with some introspection can be used to limit what is parsed.
What #dcolish suggested is the universal approach. Here is a sample implementation which also supports the standard -- separator, but its usage is not required for correct parsing.
Result:
# ./parse-pos.py -h
usage: parse-pos.py [-h] [-qa] [-qb] COMMAND [ARGS...]
# ./parse-pos.py -qa ls -q -h aa /bb
try_argv = ['-qa', 'ls']
cmd_rest_argv = ['-q', '-h', 'aa', '/bb']
parsed_args = Namespace(command='ls', qa=True, qb=False)
The code:
#!/usr/bin/python3
import argparse
import sys
from pprint import pprint
class CustomParserError(Exception):
pass
class CustomArgumentParser(argparse.ArgumentParser):
def error(self, message):
raise CustomParserError(message)
def original_error(self, message):
super().error(message)
def parse_argv():
parser = CustomArgumentParser(description='Example')
parser.add_argument('command', metavar='COMMAND [ARGS...]', help='the command to be executed')
parser.add_argument('-qa', action='store_true') # "ambiguous option" if you specify just "-q"
parser.add_argument('-qb', action='store_true') # "ambiguous option" if you specify just "-q"
def parse_until_positional(parser, _sys_argv = None):
if _sys_argv is None:
_sys_argv = sys.argv[1:] # skip the program name
for i in range(0, len(_sys_argv) + 1):
try_argv = _sys_argv[0:i]
try:
parsed_args = parser.parse_args(try_argv)
except CustomParserError as ex:
if len(try_argv) == len(_sys_argv):
# this is our last try and we still couldn't parse anything
parser.original_error(str(ex)) # sys.exit()
continue
# if we are here, we parsed our known optional & dash-prefixed parameters and the COMMAND
cmd_rest_argv = _sys_argv[i:]
break
return (parsed_args, cmd_rest_argv, try_argv)
(parsed_args, cmd_rest_argv, try_argv) = parse_until_positional(parser)
# debug
pprint(try_argv)
pprint(cmd_rest_argv)
pprint(parsed_args)
return (parsed_args, cmd_rest_argv)
def main():
parse_argv()
main()
Another option is to use parse_known_args, which stops parsing when an unknown argument is encountered.

Python argpase: Handling unknown amount of parameters/options/etc

in my script I try to wrap the bazaar executable. When I read certain options meant for bzr my script will react on that. In any case all arguments are then given to the bzr executable. Of course I don't want to specify all arguments that bzr can handle inside
my script.
So, is there a way to handle an unknown amount of arguments with argpase?
My code currently looks like this:
parser = argparse.ArgumentParser(help='vcs')
subparsers = parser.add_subparsers(help='commands')
vcs = subparsers.add_parser('vcs', help='control the vcs',
epilog='all other arguments are directly passed to bzr')
vcs_main = vcs.add_subparsers(help='vcs commands')
vcs_commit = vcs_main.add_parser('commit', help="""Commit changes into a
new revision""")
vcs_commit.add_argument('bzr_cmd', action='store', nargs='+',
help='arugments meant for bzr')
vcs_checkout = vcs_main.add_parser('checkout',
help="""Create a new checkout of an existing branch""")
The nargs option allows as many arguments as I want of course. But not another unknown optional argument (like --fixes or --unchanged).
The simple answer to this question is the usage of the argparse.ArgumentParser.parse_known_args method. This will parse the arguments that your wrapping script knowns about and ignore the others.
Here is something I typed up based on the code that you supplied.
# -*- coding: utf-8 -*-
import argparse
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', help='commands')
vcs = subparsers.add_parser('vcs', help='control the vcs')
vcs_main = vcs.add_subparsers(dest='vcs_command', help='vcs commands')
vcs_commit = vcs_main.add_parser('commit',
help="Commit changes into a new revision")
vcs_checkout = vcs_main.add_parser('checkout',
help="Create a new checkout of an "
"existing branch")
args, other_args = parser.parse_known_args()
if args.command == 'vcs':
if args.vcs_command == 'commit':
print("call the wrapped command here...")
print(" bzr commit %s" % ' '.join(other_args))
elif args.vcs_command == 'checkout':
print("call the wrapped command here...")
print(" bzr checkout %s" % ' '.join(other_args))
return 0
if __name__ == '__main__':
main()

efficient and complete input check for command line argument and option with python

I am developing cli with python version 2.4.3. i want to have the input exception check. The following is part of the code. With this code, I can type
addcd -t 11
and if I type
addcd -t str_not_int
or
addcd -s 3
I will catch the error of wrong type argument and wrong option. However, it is not sufficient. e.g.
addcd s 11
or
addcd s a
then the optparse cannot detect this kind of misinput.
to eliminate the case like
"addcd s a 11 21", I add something by checking number of argument, but I do not know if it is the right way.
So, how can I implement a thorough/efficient input check for CLI?
class OptionParsingError(RuntimeError):
def __init__(self, msg):
self.msg = msg
class OptionParsingExit(Exception):
def __init__(self, status, msg):
self.msg = msg
self.status = status
class ModifiedOptionParser(optparse.OptionParser):
def error(self, msg):
raise OptionParsingError(msg)
def exit(self, status=0, msg=None):
raise OptionParsingExit(status, msg)
class CDContainerCLI(cmd.Cmd):
"""Simple CLI """
def __init__(self):
""" initialization """
cmd.Cmd.__init__(self)
self.cdcontainer=None
def addcd(self, s):
args=s.split()
try:
parser = ModifiedOptionParser()
parser.add_option("-t", "--track", dest="track_number", type="int",
help="track number")
(options, positional_args) = parser.parse_args(args)
except OptionParsingError, e:
print 'There was a parsing error: %s' % e.msg
return
except OptionParsingExit, e:
print 'The option parser exited with message %s and result code %s' % (e.msg, e.status)
return
if len(args) != 4:
print "wrong number of inputs"
return
cd_obj= CD()
cd_obj.addCD(options.track_number, options.cd_name)
First, read this http://docs.python.org/library/optparse.html#terminology.
You're not following the optparse rules for options. If you don't follow the rules, you cannot use this library.
Specifically.
addcd s 11
or
addcd s a
Are not legal options. No "-". No "--". Why no "-"?
"then it cannot detect."
Cannot detect what? Cannot detect that they're present? Of course not. They're not legal options.
Cannot detect that the option parameters are the right type? Of course not. They're not legal options to begin with.
eliminate the case of "addcd s a 11 21"
Since none of that looks like legal options, then optparse can't help you. Those aren't "options".
Those are "arguments". They will be found in the variable positional_args.
However, you're not using that variable. Why not?
Indeed, this code
return
if len(args) != 4:
print "wrong number of inputs"
return
Makes no sense at all. After return no statement can be executed. Why write code after a return?
Why check the len of args after parsing args? It's already parsed, it's too late to care how long it was.

Categories

Resources