I am using argparse to build a command with subcommands:
mycommand [GLOBAL FLAGS] subcommand [FLAGS]
I would like the global flags to work whether they are befor
This is a perfect use case for parents argparse feature:
Sometimes, several parsers share a common set of arguments. Rather than repeating the definitions of these arguments, a single parser with all the shared arguments and passed to parents= argument to ArgumentParser can be used.
Define a base parent ArgumentParser
, add arguments that will be shared across subparsers. Then, add subparsers and set your base parser as a parent by providing parents
keyword argument:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser_name')
base_subparser = argparse.ArgumentParser(add_help=False)
# define common shared arguments
base_subparser.add_argument('--disable')
sp = subparsers.add_parser('compile', parents=[base_subparser])
# define custom arguments
sp = subparsers.add_parser('launch', parents=[base_subparser])
# define custom arguments
Note that add_help=False
here helps to avoid problems with conflicting help argument.
Also see: Python argparse - Add argument to multiple subparsers.
I see two issues in your example:
1) The use of '--disable' in both the parser and subparsers. Nested ArgumentParser
deals with that overlapping dest
.
2) The repeated set of arguments in the subparsers. parents
is certainly one way to simplify that. But you could easily write your own code:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser_name')
parser.add_argument('--disable', dest='main_disable') # This flag...
for name in ['compile', 'launch']:
sp = subparsers.add_parser(name)
sp.add_argument('zones', nargs='*')
sp.add_argument('--disable', dest=name+'_disable') # Is repeated...
You are asking for argparse solution, but as you also call for a Pythonic solution, I will deliberately propose an alternative using package docopt
:
get it:
$ pip install docopt
Write your code into mycommand
file:
"""
Usage:
mycommand compile [--disable] <zone>...
mycommand launch [--disable] <zone>...
Arguments:
<zone> zone name
Options:
-h --help
--disable disable
"""
from docopt import docopt
if __name__ == "__main__":
args = docopt(__doc__)
print args
Then call it from command line:
basic help (no arguments):
$ python mycommand
Usage:
mycommand compile [--disable] <zone>...
mycommand launch [--disable] <zone>...
Asking for a help:
$ python mycommand -h
Usage:
mycommand compile [--disable] <zone>...
mycommand launch [--disable] <zone>...
Arguments:
<zone> zone name
Options:
-h --help
--disable disable
Using compile subcommand:
$ python mycommand compile zoneAlfa zoneBeta
{'--disable': False,
'<zone>': ['zoneAlfa', 'zoneBeta'],
'compile': True,
'launch': False}
add flag --disable:
$ python mycommand compile --disable zoneAlfa zoneBeta
{'--disable': True,
'<zone>': ['zoneAlfa', 'zoneBeta'],
'compile': True,
'launch': False}
launch subcommand works too:
$ python mycommand launch --disable zoneAlfa zoneBeta
{'--disable': True,
'<zone>': ['zoneAlfa', 'zoneBeta'],
'compile': False,
'launch': True}
My usage of argument parsers started with argparse too, and I hated the complexity of code, which was needed for doing something.
Later I turned into plac
, which is very efficient way for turning a python function into very usable command for console.
Still, I was limited by set of rules to follow and understand, which were not very clear to me in many cases. With docopt
I appreciate, that I can write the docstring first, following standard rules of POSIX, and then use it in my code. If you would care about validation of arguments, I will direct you to samples of this great package.