Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add common arguments to argparse subcommands? [duplicate]

When using argparse, some subcommands need the same options and I'm using parents to avoid repeatedly defining them in every sub-command.

script filename: testarg.py

import argparse                                                                  

parser = argparse.ArgumentParser(add_help=False)                                 
parser.add_argument('-H', '--host', default='192.168.122.1')                     
parser.add_argument('-P', '--port', default='12345')                             
subparsers = parser.add_subparsers()                                             

# subcommand a                                                                   
parser_a = subparsers.add_parser('a', parents=[parser])                          
parser_a.add_argument('-D', '--daemon', action='store_true')                     

parser_a.add_argument('-L', '--log', default='/tmp/test.log')                    

# subcommand b                                                                   
parser_b = subparsers.add_parser('b', parents=[parser])                          
parser_b.add_argument('-D', '--daemon', action='store_true')                     

# subcommand c                                                                   
parser_c = subparsers.add_parser('c', parents=[parser])                          
args = parser.parse_args()                                                       

print args   

But when I run command:

>>>./testarg.py a
usage: testarg.py a [-h] [-H HOST] [-P PORT] [-D] [-L LOG] {a,b,c} ...
testarg.py a: error: too few arguments

expecting output:

>>>./testarg.py a
Namespace(daemon=False, host='192.168.122.1', log='/tmp/test.log', port='12345')

>>>./testarg.py b -H 127.0.0.1 -P 11111
Namespace(daemon=False, host='127.0.0.1', port='11111')

>>>./testarg.py c
Namespace(host='192.168.122.1', port='12345')

also, 

>>>./testarg.py c -H 127.0.0.1 -P 12222
Namespace(host='127.0.0.1', port='12222')

What am I missing?

like image 997
Fujiao Liu Avatar asked Nov 11 '15 07:11

Fujiao Liu


People also ask

How do you add arguments in Argparse?

After importing the library, argparse. ArgumentParser() initializes the parser so that you can start to add custom arguments. To add your arguments, use parser. add_argument() .

What is Subparser in Argparse?

Subparsers are invoked based on the value of the first positional argument, so your call would look like python test01.py A a1 -v 61. The "A" triggers the appropriate subparser, which would be defined to allow a positional argument and the -v option.

What does Nargs do in Argparse?

The add_argument() method action - The basic type of action to be taken when this argument is encountered at the command line. nargs - The number of command-line arguments that should be consumed. const - A constant value required by some action and nargs selections.


2 Answers

Make a separate parent parser and pass it to subparsers

import argparse                                                                  

parent_parser = argparse.ArgumentParser(add_help=False)                                 
parent_parser.add_argument('-H', '--host', default='192.168.122.1')                     
parent_parser.add_argument('-P', '--port', default='12345')                             

parser = argparse.ArgumentParser(add_help=False) 
subparsers = parser.add_subparsers()                                             

# subcommand a                                                                   
parser_a = subparsers.add_parser('a', parents = [parent_parser])                          
parser_a.add_argument('-D', '--daemon', action='store_true')                     

parser_a.add_argument('-L', '--log', default='/tmp/test.log')                    

# subcommand b                                                                   
parser_b = subparsers.add_parser('b', parents = [parent_parser])                          
parser_b.add_argument('-D', '--daemon', action='store_true')                     

# subcommand c                                                                   
parser_c = subparsers.add_parser('c', parents = [parent_parser])                          
args = parser.parse_args()                                                       

print args   

This gives desired result

$ python arg.py a
Namespace(daemon=False, host='192.168.122.1', log='/tmp/test.log', port='12345')
$ python arg.py b -H 127.0.0.1 -P 11111
Namespace(daemon=False, host='127.0.0.1', port='11111')
$ python arg.py c
Namespace(host='192.168.122.1', port='12345')
like image 94
Alik Avatar answered Oct 19 '22 12:10

Alik


When you use parser itself as a parents of the subparsers, you recursively add subparsers to each subparser. The add_subparsers command actually defines a positional argument, one that gets choices, {'a','b','c'}. It ends up expecting prog.py a a a ..., each subparser expects another subparser command etc.

I've never seen anyone try this kind of definition, and it took a bit of thinking to realize what was happening.

@Alik's approach is a correct one. Define the parent parser separately, and don't use it directly. It is just a source for those -H and -P Actions that you want added to each subparser. That's all you want to add to the subparsers.

Another approach is to simply define -H and -P in the main parser.

parser = argparse.ArgumentParser()
parser.add_argument('-H', '--host', default='192.168.122.1')
parser.add_argument('-P', '--port', default='12345')
subparsers = parser.add_subparsers()

# subcommand a
parser_a = subparsers.add_parser('a')
parser_a.add_argument('-D', '--daemon', action='store_true')
....

It will function in the same way, except that -H and -P will have to be specified before the subparser command.

0015:~/mypy$ python stack33645859.py -H 127.0.0.1 -P 1111 b
Namespace(daemon=False, host='127.0.0.1', port='1111')

They still appear in the namespace in the same way, it's just that order in the commandline is different. help will also be different.

A third option is to add the common arguments programmatically, with a loop or function. A crude example is:

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
splist = []
for cmd in ['a','b','c']:
    p = subparsers.add_parser(cmd)
    p.add_argument('-H', '--host', default='192.168.122.1')
    p.add_argument('-P', '--port', default='12345')
    splist.append(p)
splist[0].add_argument('-D', '--daemon', action='store_true')

Functionally it will be similar to @Alik's approach, with a subtle difference. With the parent, only one pair of H and P Action objects is created. References are added to each subparser.

With mine, each subparser gets its own H and P Action object. Each subparser could define different defaults for those arguments. I remember this being an issue in one other SO question.

Coding work is similar in all cases.

like image 31
hpaulj Avatar answered Oct 19 '22 14:10

hpaulj