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
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 likeparse_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.