Python argparse - creating subcommands with no labels - python

I want to create a command parser mycommand, using argparse, with two subcommands read and write: read should have just one argument which is some path, and write should have two arguments one of which is a path and the other a value. It should be possible to execute the command in the following way:
mycommand read <path>
mycommand write <path> <value>
without using labels for the <path>, <value> arguments, i.e. without requiring --path. How can I do this?

This is pretty straight forward following the docs:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
read = subparsers.add_parser('read')
read.add_argument('path')
write = subparsers.add_parser('write')
write.add_argument('path')
write.add_argument('value')
print(parser.parse_args(['read', 'foo']))
print(parser.parse_args(['write', 'foo', 'bar']))
Note that this doesn't tell you what parser parsed the arguments. If you want that, you can simply add a dest to the add_subparsers command:
subparsers = parser.add_subparsers(dest='subparser')
Finally, you can add a default attribute for each subparser that you can use to perform the actions specific to that subparser. This is spelled out in the docs too, but for completeness, in our example, it might look something like this1:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser')
def handle_read(args):
print('Handling read')
print(args)
read = subparsers.add_parser('read')
read.add_argument('path')
read.set_defaults(handler=handle_read)
def handle_write(args):
print('Handling write')
print(args)
write = subparsers.add_parser('write')
write.add_argument('path')
write.add_argument('value')
write.set_defaults(handler=handle_write)
args = parser.parse_args()
args.handler(args)
1I added the dest to subparsers in this example too for illustrative purposes -- Using argparse with handler functions probably makes that attribute on args obsolete.

Related

Common positional arguments in different subparsers

I am trying to make a parser using argparse than can parse the following commands:
python prog.py update <DOMAIN> <ENVIRONMENT>
python prog.py pull <DOMAIN> <ENVIRONMENT>
python prog.py release <DOMAIN> <ENVIRONMENT>
As you can see, both update, pull and release take the same arguments <DOMAIN> and <ENVIRONMENT>.
All three of them are subparsers of the main parser.
I wrote the following:
import argparse
# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG', add_help=False)
parser.add_argument('domain', type=str, help='domain help')
parser.add_argument('environment', type=str, help='environment help')
#subparsers
subparsers = parser.add_subparsers(help='sub-command help', parents=[parser])
parser_pull = subparsers.add_parser('pull', help='pull help')
parser_update = subparsers.add_parser('update', help='update help')
print parser_pull.parse_args(['pull', 'WEBAPPS', 'DEV'])
print parser.parse_args(['update', 'WEBAPPS', 'DEV'])
but it seems that domain and environment are expected BEFORE the subcommands update, pull and release, so it throws an error.
How can I make it required to accept those arguments after the subcommands, without duplicating code inside each subcommand ?
For the record, I use Python 2.7.
Go ahead and duplicate the code. A little cut and paste is not that much work.
Positional arguments have to be given in a certain order. And .add_subparsers creates one of those positionals (one that expects values like 'pull','update'. So the order of the subparse command, positionals defined for the main parser, and positionals for the subparsers matters.
There is a parents mechanism, which can save some typing. But I hesitate to recommend it because it can cause problems (previous SO questions demonstrate this). Simply biting the bullet and entering the positional arguments where they are expected is the surest approach.
Don't forget that you can create subparsers in a loop or with helper functions - saving one kind of typing for another.
For example, after creating the subparsers:
for p in parser_pull, parser_update:
p.add_argument('domain', type=str, help='domain help')
p.add_argument('environment', type=str, help='environment help')

Python's argparse choose one of several optional parameter

I have a program which can be used in the following way:
program install -a arg -b arg
program list
program update
There can only ever be one of the positional arguments specified (install, list or update). And there can only be other arguments in the install scenario.
The argparse documentation is a little dense and I'm having a hard time figuring out how to do this correctly. What should my add_arguments look like?
This seems like you want to use subparsers.
from argparse import ArgumentParser
parser = ArgumentParser()
subparsers = parser.add_subparsers()
install = subparsers.add_parser('install')
install.add_argument('-b')
install.add_argument('-a')
install.set_defaults(subparser='install')
lst = subparsers.add_parser('list')
lst.set_defaults(subparser='list')
update = subparsers.add_parser('update')
update.set_defaults(subparser='update')
print parser.parse_args()
As stated in the docs, I have combined with set_defaults so that you can know which subparser was invoked.

Python argparse conditional arguments

I am creating a program that requires conditional arguments using argparse. I would like to generate new arguments in my code depending on if a previous argument has been entered or not. Here is a basic example of how I would like my code to look
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-bowtie",action = "store_true",help="use to run bowtie")
args = parser.parse_args()
if args.bowtie:
parser.add_argument( add some new argument here )
args = parser.parse_args()
I've been doing a lot of stuff recently that's really similar to this. Look into subparsers:
parser = argparser.ArgumentParser
subparsers = parser.add_subparsers('-bowtie')
subparser = subparsers.add_parser()
subparser.add_argument('new argument')

Is there a way to add an already created parser as a subparser in argparse?

Normally, to add a subparser in argparse you have to do:
parser = ArgumentParser()
subparsers = parser.add_subparser()
subparser = subparsers.add_parser()
The problem I'm having is I'm trying to add another command line script, with its own parser, as a subcommand of my main script. Is there an easy way to do this?
EDIT: To clarify, I have a file script.py that looks something like this:
def initparser():
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
parser.add_argument('--bar')
return parser
def func(args):
#args is a Namespace, this function does stuff with it
if __name__ == '__main__':
initparser().parse_args()
So I can run this like:
python script.py --foo --bar
I'm trying to write a module app.py that's a command line interface with several subcommands, so i can run something like:
python app.py script --foo --bar
Rather than copy and pasting all of the initparser() logic over to app.py, I'd like to be able to directly use the parser i create from initparser() as a sub-parser. Is this possible?
You could use the parents parameter
p=argparse.ArgumentParser()
s=p.add_subparsers()
ss=s.add_parser('script',parents=[initparser()],add_help=False)
p.parse_args('script --foo sst'.split())
ss is a parser that shares all the arguments defined for initparser. The add_help=False is needed on either ss or initparser so -h is not defined twice.
You might want to take a look at the shlex module as it sounds to me like you're trying to hack the ArgumentParser to do something that it wasn't actually intended to do.
Having said that, it's a little difficult to figure out a good answer without examples of what it is, exactly, that you're trying to parse.
I think your problem can be addressed by a declarative wrapper for argparse. The one I wrote is called Argh. It helps with separating definition of commands (with all arguments-related stuff) from assembling (including subparsers) and dispatching.
This is a way old question, but I wanted to throw out another alternative. And that is to think in terms of inversion of control. By this I mean the root ArgumentParser would manage the creation of the subparsers:
# root_argparser.py
from argparse import ArgumentParser, Namespace
__ARG_PARSER = ArgumentParser('My Script')
__SUBPARSERS = __ARG_PARSER.add_subparsers(dest='subcommand')
__SUBPARSERS.required = True
def get_subparser(name: str, **kwargs) -> ArgumentParser:
return __SUBPARSERS.add_parser(name, **kwargs)
def parse_args(**kwargs) -> Namespace:
return __ARG_PARSER.parse_args(**kwargs)
# my_script.py
from argparse import ArgumentParser
from root_argparse import get_subparser
__ARG_PARSER = get_subparser('script')
__ARG_PARSER.add_argument('--foo')
__ARG_PARSER.add_argument('--bar')
def do_stuff(...):
...
# main.py
from root_argparse import parse_args
import my_script
if __name__ == '__main__':
args = parse_args()
# do stuff with args
Seems to work okay from some quick testing I did.

Python: argument parser that handles global options to sub-commands properly

argparse fails at dealing with sub-commands receiving global options:
import argparse
p = argparse.ArgumentParser()
p.add_argument('--arg', action='store_true')
s = p.add_subparsers()
s.add_parser('test')
will have p.parse_args('--arg test'.split()) work,
but fails on p.parse_args('test --arg'.split()).
Anyone aware of a python argument parser that handles global options to sub-commands properly?
You can easily add this argument to both parsers (main parser and subcommand parser):
import argparse
main = argparse.ArgumentParser()
subparser = main.add_subparsers().add_parser('test')
for p in [main,subparser]:
p.add_argument('--arg', action='store_true')
print main.parse_args('--arg test'.split()).arg
print main.parse_args('test --arg'.split()).arg
Edit: As #hpaulj pointed in comment, there is also parents argument which you can pass to ArgumentParser constructor or to add_parser method. You can list in this value parsers which are bases for new one.
import argparse
base = argparse.ArgumentParser(add_help=False)
base.add_argument('--arg', action='store_true')
main = argparse.ArgumentParser(parents=[base])
subparser = main.add_subparsers().add_parser('test', parents=[base])
print main.parse_args('--arg test'.split()).arg
print main.parse_args('test --arg'.split()).arg
More examples/docs:
looking for best way of giving command line arguments in python, where some params are req for some option and some params are req for other options
Python argparse - Add argument to multiple subparsers (I'm not sure if this question is not overlaping with this one too much)
http://docs.python.org/dev/library/argparse.html#parents
Give docopt a try:
>>> from docopt import docopt
>>> usage = """
... usage: prog.py command [--test]
... prog.py another [--test]
...
... --test Perform the test."""
>>> docopt(usage, argv='command --test')
{'--test': True,
'another': False,
'command': True}
>>> docopt(usage, argv='--test command')
{'--test': True,
'another': False,
'command': True}
There's a ton of argument-parsing libs in the Python world. Here are a few that I've seen, all of which should be able to handle address the problem you're trying to solve (based on my fuzzy recollection of them when I played with them last):
opster—I think this is what mercurial uses, IIRC
docopt—This one is new, but uses an interesting approach
cliff—This is a relatively new project by Doug Hellmann (PSF member, virtualenvwrapper author, general hacker extraordinaire) is a bit more than just an argument parser, but is designed from the ground up to handle multi-level commands
clint—Another project that aims to be "argument parsing and more", this one by Kenneth Reitz (of Requests fame).
Here's a dirty workaround --
import argparse
p = argparse.ArgumentParser()
p.add_argument('--arg', action='store_true')
s = p.add_subparsers()
s.add_parser('test')
def my_parse_args(ss):
#parse the info the subparser knows about; don't issue an error on unknown stuff
namespace,leftover=p.parse_known_args(ss)
#reparse the unknown as global options and add it to the namespace.
if(leftover):
s.add_parser('null',add_help=False)
p.parse_args(leftover+['null'],namespace=namespace)
return namespace
#print my_parse_args('-h'.split()) #This works too, but causes the script to stop.
print my_parse_args('--arg test'.split())
print my_parse_args('test --arg'.split())
This works -- And you could modify it pretty easily to work with sys.argv (just remove the split string "ss"). You could even subclass argparse.ArgumentParser and replace the parse_args method with my_parse_args and then you'd never know the difference -- Although subclassing to replace a single method seems overkill to me.
I think however, that this is a lit bit of a non-standard way to use subparsers. In general, global options are expected to come before subparser options, not after.
The parser has a specific syntax: command <global options> subcommand <subcommand ptions>, you are trying to feed the subcommand with an option and but you didn't define one.

Categories