Can I pass on options from optparse to argparse? - python

I am wrapping a class that exposes its OptionParser through a property named options_parser. I am wrapping this class in a 'runner' that I've written to use argparse. I use the ArgumentParser's parse_known_args() method to parse the wrapper's argument, and any of the remaining arguments I pass on to the instance of the wrapped class.
Running ./wrapper.py --help does not list the options from the wrapped class. Is there a convenient way to add the optparse options to the wrapper's argparse argument?

If it's solely for displaying the options, one way I can think of is use the format_help of optparse and put it in the epilog of argparse, for example:
In [303]: foo = OptionParser()
In [304]: foo.add_option("-f", "--file", dest="filename",help="read data from FILENAME")
In [305]: foo.add_option("-v", "--verbose",action="store_true", dest="verbose")
In [311]: bar = ArgumentParser(epilog = foo.format_help(), formatter_class = RawTextHelpFormatter)
In [312]: bar.add_argument('integers', metavar='N', type=int, nargs='+',help='an integer for the accumulator')
In [313]: bar.add_argument('--sum', dest='accumulate', action='store_const',const=sum, default=max,help='sum the integers (default: find the max)')
In [314]: bar.print_help()
usage: ipython [-h] [--sum] N [N ...]
positional arguments:
N an integer for the accumulator
optional arguments:
-h, --help show this help message and exit
--sum sum the integers (default: find the max)
Usage: ipython [options]
Options:
-h, --help show this help message and exit
-f FILENAME, --file=FILENAME
read data from FILENAME
-v, --verbose
You can of course format the epilog as you want, including some explanation about the two lists of options. You might also experiment with a different Formatter, though the default one doesn't work well in this case because it strips newlines from the epilog. If desperate about the layout you might also try to create your own formatter by subclassing argparse.HelpFormatter, though I'd not recommend this based on class docs:
"""Formatter for generating usage messages and argument help strings.
Only the name of this class is considered a public API. All the methods
provided by the class are considered an implementation detail.
"""

Related

Overwrite help string

Inside my project I'm mostly using docopt, but to overcome a limitation I'm switching to argparse for one function. However, for consistency I want to still print my own doc-string when I type -h or --help. Surprisingly I cannot find how to do that.
This doesn't work:
parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS, help=__doc__)
as it gives
argparse.ArgumentError: argument -h/--help: conflicting option strings: -h, --help
But what do I have to put?
I found that one solution is to overwrite the default print_help function, as follows:
import argparse
class Parser(argparse.ArgumentParser):
def print_help(self):
print(__doc__)
parser = Parser()
parser.add_argument('-f', '--foo', required=False)

How to indicate that at least one parameter is needed?

My script is accepting --full, --last and --check using ArgParse. If no option is provided, it just show the help message. But in that message, the parameters appear as optional.
usage: script.py [-h] [--full] [--last] [--check log_file]
If I use the keyword required, then the script will always expect the parameter, which is not correct.
usage: script.py [-h] --full --last --check log_file
So, how can I show something like:
usage: script.py [-h] (--full |--last |--check log_file)
Indicating that the help is optional but that at least one of those parameters is required.
On the question of customizing the usage:
The parser constructor takes a usage parameter. The immediate effect is to set an attribute:
parser = argparse.ArgumentParser( ... usage=custom_usage...)
print(parser.usage)
# should show None or the custom_usage string
Being a normal Python object attribute, you can change it after the parser was created.
usage_str = parser.format_usage()
The format_usage method ask the parser for create the usage that will be shown in the help (and error messages). If the parser.usage value is None, it formats it from the arguments. If a string, it is used as is (I think it fills in values like %(prog)s).
So you could write a usage string from scratch. Or you could set up the parser, get the current usage string, and edit that to suit your needs. Editing is most likely something you'd do during development, while testing the parser in an IDE. But it could be done on the fly.
A crude example:
In [441]: parser=argparse.ArgumentParser()
In [442]: g=parser.add_mutually_exclusive_group()
In [443]: g.add_argument('--foo')
In [444]: g.add_argument('--bar')
In [445]: ustr = parser.format_usage()
# 'usage: ipython3 [-h] [--foo FOO | --bar BAR]\n'
In [450]: parser.usage = ustr.replace('[','(').replace(']',')')
In [451]: parser.format_usage()
# 'usage: usage: ipython3 (-h) (--foo FOO | --bar BAR)\n'
I've replaced the [] with () (even on the -h :( ).
For now testing logical combinations of the args attributes is the best choice. Inside the parse_args functions the parser maintains a list (set actually) of arguments that it has seen. That is used to test for required arguments, and for mutually_exclusive_arguments, but it is not available outside that code.
For store_true (or false) arguments, just check their truth value. For others I like to test for the default None. If you use other default values, test accordingly. A nice thing about None is that the user cannot give you that value.
Perhaps the most general way to test for arguments is to count the number of attributes which are not None:
In [461]: args=argparse.Namespace(one=None, tow=2, three=None)
In [462]: ll = ['one','tow','three']
In [463]: sum([getattr(args,l,None) is not None for l in ll])
Out[463]: 1
0 means none are found; >0 at least one present; ==len(ll) all found; >1 violates mutually exclusivity; '==1' for required mutually exclusive.
As #doublep explained in his answer, if you want to use more than one option at a time:
Change the usage message manually to the one you want.
Add the following code from Python argparse: Make at least one argument required:
if not (args.full or args.last or args.check):
parse.error('[-] Error: DISPLAY_ERROR_MESSAGE')
You can use add_mutually_exclusive_group():
parser = argparse.ArgumentParser ()
group = parser.add_mutually_exclusive_group (required = True)
group.add_argument ('--foo')
group.add_argument ('--bar')
However, the main effect is that you won't be able to use more than one option at a time.

Python argparse : how to detect duplicated optional argument?

I'm using argparse with optional parameter, but I want to avoid having something like this : script.py -a 1 -b -a 2
Here we have twice the optional parameter 'a', and only the second parameter is returned. I want either to get both values or get an error message.
How should I define the argument ?
[Edit]
This is the code:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', dest='alpha', action='store', nargs='?')
parser.add_argument('-b', dest='beta', action='store', nargs='?')
params, undefParams = self.parser.parse_known_args()
append action will collect the values from repeated use in a list
parser.add_argument('-a', '--alpha', action='append')
producing an args namespace like:
namespace(alpha=['1','3'], b='4')
After parsing you can check args.alpha, and accept or complain about the number of values. parser.error('repeated -a') can be used to issue an argparse style error message.
You could implement similar functionality in a custom Action class, but that requires understanding the basic structure and operation of such a class. I can't think anything that can be done in an Action that can't just as well be done in the appended list after.
https://stackoverflow.com/a/23032953/901925 is an answer with a no-repeats custom Action.
Why are you using nargs='?' with flagged arguments like this? Without a const parameter this is nearly useless (see the nargs=? section in the docs).
Another similar SO: Python argparse with nargs behaviour incorrect

Allowing same option in different argparser group

Is it possible to allow the same argparse option to be in 2 different argparser groups?
This is actually what I want to achieve:-
#!/usr/bin/env python
import argparse
... ... ...
g1 = parser.add_argument_group('g1')
g2 = parser.add_argument_group('g2')
g1.add_argument('--aa')
g1.add_argument('--common')
g2.add_argument('--bb')
g2.add_argument('--common')
... and the printed out help looks like this:-
usage: ...
... ... ...
g1:
--aa [aa]
--common [common]
g2:
-bb [bb]
--common [common]
But that is not possible, as argparse complains 'conflicting option string'
http://bugs.python.org/issue10984 discusses adding an argument to two different mutually_exclusive_groups. Doing the same with argument_groups is similar, though simpler.
import argparse
parser=argparse.ArgumentParser()
g1=parser.add_argument_group('group1')
g1.add_argument('-a')
caction=g1.add_argument('-c')
g2=parser.add_argument_group('group2')
g2.add_argument('-b')
g2._group_actions.append(caction)
parser.print_help()
It is a kludge, in the sense that it is modifying a 'private' attribute of the group.
The result:
usage: ipython [-h] [-a A] [-c C] [-b B]
optional arguments:
-h, --help show this help message and exit
group1:
-a A
-c C
group2:
-b B
-c C
Here's what's going on. add_argument creates an Action, registers it with the parser, and returns it. That's what caction captures. If added to a group, it also registers the action with the group - by adding it to a _group_actions list.
If you do g2.add_argument('-c') you get an error because the parser already has an action with that option string. The fact that you are 'adding' it to a different group is incidental. The kludge gets around that by adding it to the group's list, without creating a new action.
In case it isn't obvious from the documentation, argument_groups are basically a 'help' convenience. They do not affect parsing at all. There are other ways you could customize the help. For example, add --common to the parser, possibly with a SUPPRESS help line. Then include a mention of it in the description for each group.
In this special case, you could make --aa and --bb mutually exclusive.
import argparse
parser = argparse.ArgumentParser()
mutex = parser.add_mutually_exclusive_group()
mutex.add_argument('--aa', action = 'store_true')
mutex.add_argument('--bb', action = 'store_true')
parser.add_argument('--common', action = 'store_true')
args = parser.parse_args()
which results in
usage: a.py [-h] [--aa | --bb] [--common]
Generally, argparse has the problem that you must create an option when calling add_argument.
Here's a somewhat related topic: Does argparse (python) support mutually exclusive groups of arguments?
There's a patch that allows you to have one argument in more than one mutually exclusive group:
http://bugs.python.org/issue10984
Another thing you can do is fiddle around with the argparse subparsers, or use another commandline parser altogether.

Argparse argument generated help, 'metavar' with choices

When using an argument (optional and positional both have this problem) with the keyword choices, the generated help output shows those choices.
If that same argument also includes a metavar keyword, the list of choices is omitted from the generated output.
What I had in mind, was to show the metavar in the usage line, but actually show the available choices when the 'autohelp' lists positional/optional argument details.
Any simple fixes/workarounds?
I have already started an argparse wrapper for custom help functionality. Perhaps this should be another feature on my TODO list.
You can add the choices to the help text.
parser=argparse.ArgumentParser()
parser.add_argument('-f',metavar="TEST",choices=('a','b','c'),
help='choices, {%(choices)s}')
print parser.format_help()
produces:
usage: stack20328931.py [-h] [-f TEST]
optional arguments:
-h, --help show this help message and exit
-f TEST choices, {a, b, c}

Categories