Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python argparse, provide different arguments based on parent argument value

here is what i would like to do : A command that looks like git command behavior. You don't get the same options whether you typed git commit or git checkout. But in my case i want to provide different arguments based on an argument value (a file name) like this :

>cmd file.a -h
usage: cmd filename [-opt1] [-opt2]
positional arguments:
filename         file to process
optional arguments:
-opt1            do something on files of type 'a'
-opt2            do something else on files of type 'a'

>cmd file.b -h
usage: cmd filename [-opt3] [-opt4] 
positional arguments:
filename         file to process
optional arguments:
-opt3            do something on files of type 'b'
-opt4            do something else on files of type 'b'

Is it possible to do this kind of thing using python and argparse ?
What i've tried so far is :

parser = argparse.Argument_parser(prog='cmd')
subparsers = parser.add_subparsers()

parser.add_argument('filename', 
                    help="file or sequence to process")

args = parser.parse_args(args=argv[1:])

sub_parser = subparsers.add_parser(args.filename, help="job type")
base, ext = os.path.splitext(args.filename)
if ext == 'a':
    sub_parser.add_argument("-opt1", action='store_true')
    sub_parser.add_argument("-opt2", action='store_true')
elif ext == 'b':
    sub_parser.add_argument("-opt3", action='store_true')
    sub_parser.add_argument("-opt4", action='store_true')

args = parser.parse_args(args=argv[1:])

I don't know if i should use subparsers or child parsers or groups, i'm kind of lost in all the possibilities provided by argparse

like image 303
Narthe Avatar asked May 13 '15 13:05

Narthe


People also ask

How do you add an optional argument in Argparse?

Optional Arguments To add an optional argument, simply omit the required parameter in add_argument() . args = parser. parse_args()if args.

What is action Store_true in Argparse?

The store_true option automatically creates a default value of False. Likewise, store_false will default to True when the command-line argument is not present. The source for this behavior is succinct and clear: http://hg.python.org/cpython/file/2.7/Lib/argparse.py#l861.

What is Argparse ArgumentParser ()?

The argparse module provides a convenient interface to handle command-line arguments. It displays the generic usage of the program, help, and errors. The parse_args() function of the ArgumentParser class parses arguments and adds value as an attribute dest of the object.


1 Answers

When you take a look at parse_args() implementation you'll notice that it parses all arguments at once (it doesn't use yield to continuously generate state) so you have to prepare your structure before and not after half of the arguments would be parsed.

Taking from official example in the docs you should add subparser(s) before starting parsing like this:

import argparse

parser = argparse.ArgumentParser(prog='PROG')
subparsers = parser.add_subparsers(help='sub-command help')

# create the parser for the "a" command
parser_a = subparsers.add_parser('a', help='a help')
parser_a.add_argument("--opt1", action='store_true')
parser_a.add_argument("--opt2", action='store_true')

# create the parser for the "b" command
parser_b = subparsers.add_parser('b', help='b help')
parser_b.add_argument("--opt3", action='store_true')
parser_b.add_argument("--opt4", action='store_true')

# parse some argument lists
print(parser.parse_args())

And the output (in command line), help is nicely printed:

D:\tmp>s.py -h
usage: PROG [-h] {a,b} ...

positional arguments:
  {a,b}       sub-command help
    a         a help
    b         b help

optional arguments:
  -h, --help  show this help message and exit

A arguments are parsed

D:\tmp>s.py a --opt1
Namespace(opt1=True, opt2=False)

B arguments are parsed

D:\tmp>s.py b
Namespace(opt3=False, opt4=False)

Also with args:

D:\tmp>s.py b --opt3
Namespace(opt3=True, opt4=False)

Running A arguments in B causes error:

D:\tmp>s.py b --opt2
usage: PROG [-h] {a,b} ...
PROG: error: unrecognized arguments: --opt2

Also if you need to identify which subparser was used you may add dest=name to parser.add_subparsers() call (which I think isn't properly stressed in the docs):

subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')

With the result of:

D:\tmp>s.py b --opt3
Namespace(opt3=True, opt4=False, subparser_name='b')

If you needed to really create arguments dynamically (for example load some argument options from expensive resource) you could use parse_known_args():

Sometimes a script may only parse a few of the command-line arguments, passing the remaining arguments on to another script or program. In these cases, the parse_known_args() method can be useful. It works much like parse_args()except that it does not produce an error when extra arguments are present. Instead, it returns a two item tuple containing the populated namespace and the list of remaining argument strings.

After all, parse_args() just checks trailing aruments:

def parse_args(self, args=None, namespace=None):
    args, argv = self.parse_known_args(args, namespace)
    if argv:
        msg = _('unrecognized arguments: %s')
        self.error(msg % ' '.join(argv))
    return args

And then you can re-execute another parser on argv, but I can imagine few issues that can go with this and I wouldn't recommend it until really necessary.

like image 91
Vyktor Avatar answered Oct 19 '22 04:10

Vyktor