Python argparse provide different arguments based on the value of the parent argument

Here is what I would like to do: A command that looks like the behavior of a git command. You are not getting the same options whether you typed git commit or git checkout. But in my case, I want to provide different arguments based on the value of the argument (file name) as follows:

>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 such things using python and argparse?
I have tried so far:

 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 whether to use subparameters or child parsers or groups, as if I had lost all the possibilities provided by argparse

+6
source share
1 answer

When you look at the parse_args() implementation , you will notice that it parses all the arguments at once (it does not use yield to continuously generate state), so you need to prepare your structure before and after half of the arguments have been parsed.

Taking from an official example in the docs , you should add subparser (s) before starting the parsing as follows:

 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 (on the command line), the help is beautifully printed:

 D:\tmp>s.py -h usage: PROG [-h] {a,b} ... positional arguments: {a,b} sub-command help aa help bb help optional arguments: -h, --help show this help message and exit 

Arguments are analyzed

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

Parsed arguments B

 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 arguments in B result in an error:

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

Also, if you need to determine which subparameter was used, you can add the dest=name call to parser.add_subparsers() (which, it seems to me, is not properly underlined in the documents):

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

As a result:

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

If you needed to dynamically create arguments (for example, load some argument parameters from an expensive resource), you can use parse_known_args() :

Sometimes a script can parse only a few command line arguments, passing the remaining arguments to another script or program. In these cases, the parse_known_args() method may be useful. It works the same as parse_args() , except that it does not throw an error if there are additional arguments. Instead, it returns a binary tuple containing the filled namespace and a list of the remaining argument strings .

In the end, parse_args() just checks for 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 a few problems that could go with this, and I would not recommend it until it really is needed.

+15
source

Source: https://habr.com/ru/post/987135/


All Articles