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)
Related
I have a legacy Python application which uses some options in its CLI, using argparse, like:
parser = argparse.ArgumentParser()
parser.add_argument('-f', default='foo')
Now I need to remove this option, since now its value cannot be overwritten by users but it has to assume its default value (say 'foo' in the example). Is there a way to keep the option but prevent it to show up and be overwritten by users (so that I can keep the rest of the code as it is)?
It's not entirely clear what you can do, not with the parser. Can you edit the setup? Or just modify results of parsing?
If you can edit the setup, you could replace the add_argument line with a
parser.setdefaults(f='foo')
https://docs.python.org/3/library/argparse.html#parser-defaults
The -f won't appear in the usage or help, but it will appear in the args
Or you could leave it in, but suppress the help display
parser.add_argument('-f', default='foo', help=argparse.SUPPRESS)
https://docs.python.org/3/library/argparse.html#help
Setting the value after parsing is also fine.
Yes you can do that. After the parser is parsed (args = parser.parse_args()) it is a NameSpace so you can do this:
parser = argparse.ArgumentParser()
args = parser.parse_args()
args.foo = 'foo value'
print(args)
>>> Namespace(OTHER_OPTIONS, foo='foo value')
I assumed that you wanted to add test to your parser, so your original code will still work, but you do not want it as an option for the user.
I think it doesn't make sense for the argparse module to provide this as a standard option, but there are several easy ways to achieve what you want.
The most obvious way is to just overwrite the value after having called parse_args() (as already mentioned in comments and in another answer):
args.f = 'foo'
However, the user may not be aware that the option is not supported anymore and that the application is now assuming the value "foo". Depending on the use case, it might be better to warn the user about this. The argparse module has several options to do this.
Another possibility is to use an Action class to do a little magic. For example, you could print a warning if the user provided an option that is not supported anymore, or even use the built-in error handling.
import argparse
class FooAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if values != 'foo':
print('Warning: option `-f` has been removed, assuming `-f foo` now')
# Or use the built-in error handling like this:
# parser.error('Option "-f" is not supported anymore.')
# You could override the argument value like this:
# setattr(namespace, self.dest, 'foo')
parser = argparse.ArgumentParser()
parser.add_argument('-f', default='foo', action=FooAction)
args = parser.parse_args()
print('option=%s' % args.f)
You could also just limit the choices to only "foo" and let argparse create an error for other values:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-f', default='foo', choices=['foo'])
args = parser.parse_args()
print('option=%s' % args.f)
Calling python test.py -f bar would then result in:
usage: test.py [-h] [-f {foo}]
test.py: error: argument -f: invalid choice: 'bar' (choose from 'foo')
I have a file structure like this:
project/
main_prog.py
tools/
script.py
md_script/
__init__.py
md_script.py
I search in tools for local python modules. In this example it's md_script. And i want to use it as positional argument like install in my code, but when i use it, I'v got an error:
./jsh.py md_script
usage: jsh.py [-h] {install,call,list,log,restore} ... [md_script]
jsh.py: error: invalid choice: 'md_script' (choose from 'install', 'call', 'list', 'log', 'restore')
python3.4 on ubuntu14.10
Here is my code:
parser = argparse.ArgumentParser(prog='jsh.py',
description='Some help.', epilog='Example of usage: some help')
subparsers = parser.add_subparsers()
parser_install = subparsers.add_parser('install', help = 'Install new project.')
parser_install.add_argument('install', nargs='?', help = 'Name of project to be installed')
if os.path.isdir(full/path/to/tools/):
name_arg = next(os.walk(full/path/to/tools))[1]
tools_arg = parser.add_argument_group('Tools', 'Modules from tools')
for element in name_arg:
tools_arg.add_argument(element, nargs='?', help='md_script description')
args = parser.parse_args()
try:
if not len(sys.argv) > 1:
parser.print_help()
elif 'install' in args:
do_some_stuff
elif element in args:
do_some_md_script_stuff
else:
parser.print_help()
The usage line shows what's wrong:
usage: jsh.py [-h] {install,call,list,log,restore} ... [md_script]
You need to use something like
jsh.py install md_script
You specified subparsers, so you have to give it a subparser name.
From the usage it also looks like you created other subparsers, call, list, etc that you don't show in the code.
You also define positional arguments after creating subparser. That's where the [md_script] comes from. Be careful about making a lot of nargs='?' positionals (including the argument for the install subparser). This could make things confusing for your users. In fact it seems to confusing you. Remember that subparser is in effect a positional argument (one that requires 1 string).
I'd suggest experimenting with a simplier parser before creating one this complicated.
So from your comments and examples I see that you goal is let the user name a module, so your script can invoke it in some way or other. For that populating the subparsers with these names makes sense.
I wonder why you also create an optional positional argument with the same name:
module_pars = subparsers.add_parser(element, help = 'Modules from tools')
module_pars.add_argument(element, nargs='?', help=element+' description')
Is that because you are using the presence of the attribute as evidence that this subparser was invoked?
elif element in args:
do_some_md_script_stuff
The argparse documentation has a couple of other ideas.
One particularly effective way of handling sub-commands is to combine the use of the add_subparsers() method with calls to set_defaults() so that each subparser knows which Python function it should execute.
and
However, if it is necessary to check the name of the subparser that was invoked, the dest keyword argument to the add_subparsers() call will work:
These avoid the messiness of a '?' positional argument, freeing you to use subparser arguments for real information.
subparsers = parser.add_subparsers(dest='module')
....
for element in name_arg:
# module_pars = 'parser_'+element # this does nothing
module_pars = subparsers.add_parser(element, help = 'Modules from tools')
module_pars.set_defaults(func = do_some_md_script_stuff)
# or module_pars.set_default(element='I am here')
module_pars.add_argument('real_argument')
Now you can check:
if args.module='md_script':
do_some_md_script_stuff(args)
or
if hasattr(args, 'func'):
func(args)
With the alternative set_defaults, your original test should still work:
if element in args:
do_some_md_script_stuff
I did it like this. It's exactly what I want to.
if os.path.isdir(TOOLS_PATH):
name_arg = next(os.walk(TOOLS_PATH))[1]
for element in name_arg:
module_pars = 'parser_'+element
module_pars = subparsers.add_parser(element, help = 'Modules from tools')
module_pars.add_argument(element, nargs='?', help=element+' description')
I didn't test it, because i dont have a test module, but ./jsh.py md_script goes into elif element in args: print('md_script') and print string. So it looks like it works.
Thanks for all replies.
Edit: I tested it. In add_argument i must change nargs='?' for nargs='*' to catch more than one argument.
And to catch arguments from command line I used this:
elif args:
for element in name_arg:
if element in args:
element_arg = sys.argv[2:]
done_cmd,msg = opt_exec_module(element,*element_arg)
my_logger(done_cmd,msg)
Not very elegant but it works.
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.
"""
I have a problem with giving arguments to a function when using callback. I am new to the command line argument scripting in python so go easy on me. Here's my code:
from optparse import OptionParser
import urllib
def google(option, opt_str, value, parser):
print options.f_name
parser = OptionParser()
parser.add_option("-f", "--first", type="string", dest="f_name", help="Supply a first name to search", action="store")
parser.add_option("-l", "--last", type="string", dest="l_name", help="Supply a last name to search", action="store")
parser.add_option("-g", "--google", action="callback", callback=google)
(options, args) = parser.parse_args()
And can't seem to figure out why it wouldn't print out the user supplied input. I've looked at the doc for python on optparse and it just gets fuzzy. Anyways any possibly way I can use options.f_name in that function. I have been using something as such to put into the functions arguments to use.
first_name = options.f_name
Then would supply the function with one of the arguments that didn't work either.
Old post, but for posterity:
You need to specify the type of the argument in order to have optparse inject it into the value parameter:
def google(option, opt_str, value, parser):
print value
parser.add_option("-g", "--google", action="callback", callback=google, type="string")
Full example showing how to return the value to the parser for inclusion into options:
from optparse import OptionParser
def doSomethingWithValue(value):
return "baked beans and {}".format(value)
def google(option, opt_str, value, parser):
setattr(parser.values, option.dest, doSomethingWithValue(value))
parser = OptionParser()
parser.add_option("-g", "--google", action="callback", callback=google, type="string", dest="googleOption")
(options, args) = parser.parse_args()
print(options)
# ./script.py ==> {'googleOption': None}
# ./script.py -g spam ==> {'googleOption': 'baked beans and spam'}
optparse is hard to use. You should try docopt. http://docopt.org/
As a side note, urllib is hard to use too. Check out the requests module. http://docs.python-requests.org/en/latest/
The order of evaluation of the arguments is not specified, so the callback for -g may be called before optparse handles the -f option. The only way to do this during the parse is to make them both callbacks that are aware of each other, and only when the second argument is handled does it perform the behaviour you are looking for.
Is there any reason you can't just set a flag and handle it after the parse_args() is complete, then you will be sure all the arguments have been handled.
BTW: optparse is deprecated in favour of argparse.
If you check the documentation, you'll find out that you actually need to use:
parser.values.f_name
Of course you should make sure that you take the precautions for cases where it hasn't been defined yet.
How can I pass options without any argument and without passing any default argument?
For example:
./log.py --ipv4
parser.add_option("--ipv4", action="store_true", dest="ipv4")
See http://docs.python.org/2/library/optparse.html#handling-boolean-flag-options
While lajarre's answer is correct, it's important to note outparse is considered deprecated.
I suggest using the newer argparse module instead.
So your code would look like:
import argparse
parser = argparse.ArgumentParser(description='This is my description')
parser.add_argument('--ipv4', action='store_true', dest='ipv4')
Using -foo or --foo flags makes the arguement optional. See this documentation for more about optional arguments.
Edit: And here's the specific documentation for the add_argument method.
Edit 2: Additionally, if you wanted to accept either -foo or --foo you could do
parser.add_argument('-ipv4', '--ipv4', action='store_true', dest='ipv4')