I am having some trouble with argparse. My goal is to have the user select one and only one option (-a, -b, -c, etc.) and then the arguments for that option. I'm using subparsers to do this:
parser_iq = subparsers.add_parser('iq', help='iq help')
parser_iq.add_argument("-iq", "--index_query", nargs="+", action=required_length(1,6),type=organize_args, help="Choose an index to query for. Start-date, end-date, "\
"csv, json, stdout are all optional")
This is just one of the subparsers I plan to have.
Problem: When running this in the command line:
python3.6 main.py iq "index_name_here"
I get the error that "index_name_here" is unrecognized. I am parsing it like this:
args = parser.parse_args()
I found some problems similar to mine, but they were passing in sys.argv into parse_args(), which was their issue.
How can I make it so that argparse will recognize the arguments passed? Also, is there a way to have only one option passed in at a time? For example:
Correct:
main.py option1 arg1 arg2
Wrong:
main.py option1 option2 arg1 arg2
Thank you!
You have to pass the value like python3.6 main.py -iq "index_name_here" (i.e., use -iq, not iq).
As far as making mutually exclusive arguments, subparsers is, from what I understand, the way to go, but I can't give much in the way of guidance on how to proceed on that.
Edit:
In response to your comment, does the following work:
python3.6 main.py iq -iq "index_name_here"
?
Related
Using argparse, is there a simple way to specify arguments which are mutually exclusive so that the application asks for one of these arguments have to be provided but only one of them?
Example of fictive use-case:
> myapp.py foo --bar
"Foo(bar) - DONE"
> myapp.py read truc.txt
"Read: truc.txt - DONE"
>myapp.py foo read
Error: use "myapp.py foo [options]" or "myapp.py read [options]" (or something similar).
> myapp.py foo truc.txt
Error: "foo" action don't need additional info.
> myapp.py read --bar
Error: "read" action don't have a "--bar" option.
My goal is to have a "driver" application(1) that would internally apply one action depending on the first command line argument and have arguments depending on the action.
So far I see no obvious ways to do this with argparse without manually processing the arguments myself, but maybe I missed something Pythonic? (I'm not a Python3 expert...yet)
I call it "driver" because it might be implemented by calling another application, like gcc does with different compilers.
What you're trying to do is actually supported quite well in Python.
See Mutual Exclusion
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('foo', dest='foo', nargs=1)
group.add_argument('read', dest='read', nargs=1)
args = parser.parse_args()
return args
I'm trying to pass a list of arguments with argparse but the only way that I've found involves rewriting the option for each argument that I want to pass:
What I currently use:
main.py -t arg1 -a arg2
and I would like:
main.py -t arg1 arg2 ...
Here is my code:
parser.add_argument("-t", action='append', dest='table', default=[], help="")
Use nargs:
ArgumentParser objects usually associate a single command-line
argument with a single action to be taken. The nargs keyword argument
associates a different number of command-line arguments with a single
action.
For example, if nargs is set to '+'
Just like '*', all command-line args present are gathered into a list.
Additionally, an error message will be generated if there wasn’t at
least one command-line argument present.
So, your code would look like
parser.add_argument('-t', dest='table', help='', nargs='+')
That way -t arguments will be gathered into list automatically (you don't have to explicitly specify the action).
Being aware, you asked for argparse solution, I would like to present alternative solution using package docopt
Install it first:
$ pip install docopt
Write the code:
"""Usage:
main.py -a <arg>...
"""
if __name__ == "__main__":
from docopt import docopt
resargs = docopt(__doc__)
print resargs
Run it to show usage instrucitons:
$ python main.py
Usage:
main.py -a <arg>...
Call it with your parameters:
$ python main.py -a AA BB CC
{'-a': True,
'<arg>': ['AA', 'BB', 'CC']}
Btw. if you do not need the -a option, you shall directly allow passing the arguments. It makes usage simpler to the user.
Is there a Python module for doing gem/git-style command line arguments? What I mean by gem/git style is:
$ ./MyApp.py
The most commonly used MyApp commands are:
add Add file contents to the index
bisect Find by binary search the change that introduced a bug
branch List, create, or delete branches
checkout Checkout a branch or paths to the working tree
...
$ ./MyApp.py branch
* current-branch
master
With no arguments, the output tells you how you can proceed. And there is a special "help" command:
$ ./MyApp.py help branch
Which gets you deeper tips about the "branch" command.
Edit:
And by doing I mean it does the usage printing for you, exits with invalid input, runs your functions according to your CLI specification. Sort of a "URL mapper" for the command line.
Yes, argparse with add_subparsers().
It's all well explained in the Sub-commands section.
Copying one of the examples from there:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers()
>>> checkout = subparsers.add_parser('checkout', aliases=['co'])
>>> checkout.add_argument('foo')
>>> parser.parse_args(['checkout', 'bar'])
Namespace(foo='bar')
Edit: Unfortunately there's no self generated special help command, but you can get the verbose help message (that you seem to want) with -h or --help like one normally would after the command:
$ ./MyApp.py branch --help
By verbose I don't mean that is like a man page, it's like every other --help kind of help: listing all the arguments, etc...
Example:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(description='Sub description')
>>> checkout = subparsers.add_parser('checkout', description='Checkout description')
>>> checkout.add_argument('foo', help='This is the foo help')
>>> parser.parse_args(['checkout', '--help'])
usage: checkout [-h] foo
Checkout description
positional arguments:
foo This is the foo help
optional arguments:
-h, --help show this help message and exit
If you need to, it should be easy to implement an help command that redirects to --help.
A reasonable hack to get the gem/git style "help" behavior (I just wrote this for what I'm working on anyway):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='sub_commands')
parser_branch = subparsers.add_parser('branch', description='list of branches')
parser_help = subparsers.add_parser('help')
parser_help.add_argument('command', nargs="?", default=None)
# I can't find a legitimate way to set a default subparser in the docs
# If you know of one, please let me know!
if len(sys.argv) < 2:
sys.argv.append('--help')
parsed = parser.parse_args()
if parsed.sub_commands == "help":
if not parsed.command:
parser.parse_args(['--help'])
else:
parser.parse_args([parsed.command, '--help'])
argparse is definitely a step up from optparse and other python solutions I've come across. But IMO the gem/git style of handling args is just a more logical and safer way to do things so it's annoying that it's not supported.
I wanted to do something similar to git commands, where I would load a second script based off of one of the command line options, and have that script populate more command line options, and also have the help work.
I was able to do this by disabling the help option, parse known args, add more arguments, re-enable the help option, and then parse the rest of the arguments.
This is what I came up with.
import argparse
#Note add_help=False
arg_parser = argparse.ArgumentParser(description='Add more arguments after parsing.',add_help=False)
arg_parser.add_argument('MODE', default='default',type=str, help='What commands to use')
args = arg_parser.parse_known_args()[0]
if args.MODE == 'branch':
arg_parser.add_argument('-d', '--delete', default='Delete a branch')
arg_parser.add_argument('-m', '--move', default='move a branch')
elif args.MODE == 'clone' :
arg_parser.add_argument('--local', '-l')
arg_parser.add_argument('--shared')
#Finally re-enable the help option, and reparse the arguments
arg_parser.add_argument(
'-h', '--help',
action='help', default=argparse.SUPPRESS,
help=argparse._('show this help message and exit'))
args = arg_parser.parse_args()
I want the help option act the same as --help.
sidenote: I have created a program with the same command line behavior as svn or hg. I managed to do this with subparsers. However I want to make things consistent. That's why I want help to work.
You can do the following to create an alias and make program help act the same as program --help
import argparse
def help(args):
args.parser.print_help()
parser = argparse.ArgumentParser(description='my program')
subparsers = parser.add_subparsers()
p_help = subparsers.add_parser('help')
p_help.set_defaults(func=help, parser=parser)
args = parser.parse_args()
args.func(args)
It seems to me that you want to define help as another subparser. I would naively say that you could then link it to a print_help() function that would copy the output of your standard --help, but I wonder if there is a way to call the native help() function.
I've a Python app like this:
$ app.py -a arg1 -a arg2 -b file1.b -b file2.b
Depending on the option, in app.py, I use two different functions to process the input files. As each option can have several file arguments, I would like something like this:
$ app.py -a arg1 arg2 -b *.b
After searching on the net, I find these three modules: getopt, argparse, optparse
And I wrote a simple one for my app:
optionDict= {'-a':aFunction, '-b':bFunction}
for arg in sys.argv[1:]:
if arg in optionDict: # arg is an option here.
funcName = optionDict[arg]
else: # arg is not an option, then it's a fileName. Deal with this file with the function found according to previous arg.
funcName(arg)
My quesion: Are there any other modules or better ways to do this?
Since 2012 Python has an easy, powerful and very cool module for argument parsing called docopt. It works with Python from 2.5 to 3.3 and needs no installation. Here is the code for your particular case:
'''app.py
Usage:
app.py -a ARG ... -b FILE ...
app.py (-h | -v)
'''
from docopt import docopt
args = docopt(__doc__, version='1.0')
# now do something based on the value of args dict
So this is it: one line of code plus your doc string which is essential. I told you it's cool -- didn't I ;-)
If you found argparse to be too hard to get started with because of the complexity, then you would probably like plac which is available on PyPi. It is a wrapper around argparse which hides the complexity, and it takes care of a lot of boilerplate argument checking for you.
It works for both Python 2 and 3, and has a decorator that allows Python 2 users to get access to a nice Python 3 feature for function parameters. And you can even nest commands in one another so that you could have -arguments (beginning with dash) and .arguments (beginning with dot) on one command line.
Here are the docs http://plac.googlecode.com/hg/doc/plac.html
I think that argparse is the way to go. It does a great job, it is easy to use and in the standard library since 2.7. Take a look a the nargs argument. From the docs
Fixed elements to consume
parser = argparse.ArgumentParser()
parser.add_argument('--foo', nargs=2)
parser.add_argument('bar', nargs=1)
parser.parse_args('c --foo a b'.split())
Namespace(bar=['c'], foo=['a', 'b'])
One arg will be consumed from the command line if possible, and produced as a single item. If no command-line arg is present, the value from default will be produced
parser = argparse.ArgumentParser()
parser.add_argument('--foo', nargs='?', const='c', default='d')
parser.add_argument('bar', nargs='?', default='d')
parser.parse_args('XX --foo YY'.split())
Namespace(bar='XX', foo='YY')
parser.parse_args('XX --foo'.split())
Namespace(bar='XX', foo='c')
parser.parse_args(''.split())
Namespace(bar='d', foo='d')
All command-line args present are gathered into a list.
parser = argparse.ArgumentParser()
parser.add_argument('--foo', nargs='*')
parser.add_argument('--bar', nargs='*')
parser.add_argument('baz', nargs='*')
parser.parse_args('a b --foo x y --bar 1 2'.split())
Namespace(bar=['1', '2'], baz=['a', 'b'], foo=['x', 'y'])