I'm writing a script that takes one required parameter, and then can change the interpretation of the following arguments based on that. Most of the combinations are going well, but there's one that is giving me trouble. It's a repeating group of three parameters, all strings. For example:
$ python script.py p1 p2_1 p2_2 p2_3 p3_1 p3_2 p3_3
or as pseudo-regex:
$ python script.py p1 (px_1 px_2 px_3)+
I have no control over the format of the input. There is the option to receive this via stdin versus on the command line though. It's probably just easier to deal with this as a string using a regex, which would also allow me to handle both, by joining argv.
There are several other SO answers that sort of address doing something similar with argparse.
hpaulj has two helpful responses here: Argparse, handle repeatable set of items and here: Python argparser repeat subparse
After now several hours, I have yet to figure out how to make this work with argparse without some hackery. First, stripping off the first param, then iterating until the remaining params are gone. I'd like to keep it in the same namespace object, like it would be if I could figure out how to do this properly. Some demo code based on one of the answers above:
#!/usr/bin/env python
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('param1', type=str)
param1, remaining = parser.parse_known_args()
parser = argparse.ArgumentParser()
parser.add_argument('first', type=str, action='append')
parser.add_argument('second', type=str, action='append')
parser.add_argument('third', type=str, action='append')
repeating_args, remaining = parser.parse_known_args(remaining)
while remaining:
another_set, remaining = parser.parse_known_args(remaining)
repeating_args.first.append(another_set.first[0])
repeating_args.second.append(another_set.second[0])
repeating_args.third.append(another_set.third[0])
But this just feels kludgy to me and it forces me to modify the code in a way that impacts the other parameter combinations. Is there a better way to do this with argparse? Or if I'm not happy with this, should I just not be using argparse? Unfortunately, that would mean I get to rewrite a lot of my code...
Thanks.
UPDATED CODE:
Based on hpaulj's answers, this is a compacted and working version of the code I'm using. It's much better than the above code, since it's relatively generic with regard to the parser configuration. I hope it helps.
#!/usr/bin/env python
import sys
import argparse
def parse_args():
# Create the first parser object and get just the first parameter
parser = argparse.ArgumentParser('Argument format parser')
parser.add_argument('arg_format', type=str, help='The first argument.' +
'It tells us what input to expect next.')
args_ns, remaining = parser.parse_known_args()
# Generate a new parser based on the first parameter
parser = formatSpecificParser(args_ns.arg_format)
# There will always be at least one set of input (in this case at least)
args_ns, remaining = parser.parse_known_args(args=remaining, namespace=args_ns)
# Iterate over the remaining input, if any, adding to the namespace
while remaining:
args_ns, remaining = parser.parse_known_args(args=remaining,
namespace=args_ns)
return args_ns
def formatSpecificParser(arg_format):
parser = argparse.ArgumentParser("Command line parser for %s" % arg_format)
if (arg_format == "format_1"):
addArgsFormat1(parser)
# elif (...):
# other format function calls
return parser
def addArgsFormat1(parser):
parser.add_argument('arg1', type=str, action='append', help='helpful text')
parser.add_argument('arg2', type=str, action='append', help='helpful text')
parser.add_argument('arg3', type=str, action='append', help='helpful text')
def main(argv):
args = parse_args()
print (args)
if __name__ == "__main__":
main(sys.argv[1:])
Command line output:
$ ./example.py format_1 foo bar baz meh meh meh e pluribus unum
Namespace(arg1=['foo', 'meh', 'e'], arg2=['bar', 'meh', 'pluribus'], arg3=['baz', 'meh', 'unum'], arg_format='format_1')