Reputation: 1993
Let's say I have a script, select_libs.py
, which enables you to choose which libraries to include in some build process. When running this script I want to be able to specify the library name, branch and version. Here are some use cases.
select_libs.py
select_libs.py opencv stable 3.4.1
select_libs.py opencv stable 3.4.1 boost development 1.67.0
So I imagine something as simple as this:
import argparse
branches = ["legacy", "stable", "development"]
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='lib configuration')
opencv_parser = subparsers.add_parser("opencv")
opencv_parser.add_argument("opencv_build", choices=branches)
opencv_parser.add_argument("opencv_version", type=str)
boost_parser = subparsers.add_parser("boost")
boost_parser.add_argument("boost_build", choices=branches)
boost_parser.add_argument("boost_version",type=str)
cmd_options = parser.parse_args()
The first two use cases both work, but the third:
select_libs.py opencv stable 3.4.1 boost development 1.67.0
produces this error:
error: unrecognized arguments: boost development 1.67.0
In my head, this should work since each parser have exactly two positional arguments, so it should know that boost
is not an argument to opencv
and trigger the boost
parser accordingly. Clearly I'm wrong, but what have I missed and how can I make it work as intended (if possible)?
My current Python version is 3.5.2
.
Upvotes: 3
Views: 3487
Reputation: 43136
Argparse is not suited for this kind of thing. add_subparsers
assumes that exactly 1 of the sub-commands will be used, so it throws an error if you try to set both opencv
and boost
. And other than that, argparse has no concept of arguments being associated with other arguments.
If you don't mind using keyword options instead of positional options, you can use the solution from this answer:
argv = '-l opencv -b stable -v 3.4.1 -l boost -b development -v 1.67.0'.split()
parser = argparse.ArgumentParser()
parent = parser.add_argument('-l', '--lib', choices=['opencv', 'boost'], action=ParentAction)
parser.add_argument('-b', '--build', action=ChildAction, parent=parent)
parser.add_argument('-v', '--version', action=ChildAction, parent=parent)
args = parser.parse_args(argv)
print(args)
# output:
# Namespace(lib=OrderedDict([('opencv', Namespace(build='stable',
# version='3.4.1')),
# ('boost', Namespace(build='development',
# version='1.67.0'))]))
Use the nargs
argument to make it a mix of positional and named arguments:
argv = '-l opencv stable 3.4.1 -l boost development 1.67.0'.split()
parser = argparse.ArgumentParser()
parent = parser.add_argument('-l', '--lib', nargs=3, action='append')
args = parser.parse_args(argv)
print(args)
# output:
# Namespace(lib=[['opencv', 'stable', '3.4.1'],
# ['boost', 'development', '1.67.0']])
Parse the arguments manually:
argv = 'opencv stable 3.4.1 boost development 1.67.0'.split()
args = {}
argv_itr = iter(argv)
for lib in argv_itr:
args[lib] = {'build': next(argv_itr),
'version': next(argv_itr)}
print(args)
# output:
# {'opencv': {'build': 'stable',
# 'version': '3.4.1'},
# 'boost': {'build': 'development',
# 'version': '1.67.0'}}
Upvotes: 4