I want to use argparse to build a tool with subcommand. The possible syntax could be
/tool.py download --from 1234 --interval 60
/tool.py download --build 1432
/tool.py clean --numbers 10
So I want to use argparse to implement:
- ensure '--from' and '--interval' are always together used
- ensure '--build' is never used with other arguments
But I didn't find a way to pair '--from' and '--internal' to a group, then make the group is mutual exclusive with '--build'.
Below is my current code, and it only make the '--from' and '--build' are mutual exclusive. Neither ensure '--from' and '--interval' come together, nor ensure '--interval' and '--build' are mutual exclusive.
parser = argparse.ArgumentParser(description='A Tool')
subparsers = parser.add_subparsers(help='sub-command help')#create the parser for the 'download' command
download_parser = subparsers.add_parser('download', help='download help')
download_parser.add_argument('--interval', dest='interval', type=int,help='interval help')
group = download_parser.add_mutually_exclusive_group()
group.add_argument('--from',type=int, help='from help')
group.add_argument('--build', type=int, help='interval help')
For example,
/tool.py download --from 1234
should not be allowed because '--from' must work with '--interval'. But my code accepts it silently.
And
/tool.py download --interval 1234 --build 5678
should not be allowed because '--build' can't be used with other argument. But my code accepts it too.
Any suggestion will be highly appreciated. Thanks.
You could use custom actions for this:
import argparse
import sysclass VerifyNoBuild(argparse.Action):def __call__(self, parser, args, values, option_string=None):# print 'No: {n} {v} {o}'.format(n=args, v=values, o=option_string)if args.build is not None:parser.error('--build should not be used with --from or --interval')setattr(args, self.dest, values)class VerifyOnlyBuild(argparse.Action):def __call__(self, parser, args, values, option_string=None):# print 'Only: {n} {v} {o}'.format(n=args, v=values, o=option_string)if getattr(args, 'from') is not None:parser.error('--from should not be used with --build')if getattr(args, 'interval') is not None:parser.error('--interval should not be used with --build')setattr(args, self.dest, values)parser = argparse.ArgumentParser(description='A Tool')
subparsers = parser.add_subparsers(help='sub-command help')# create the parser for the 'download' command
download_parser = subparsers.add_parser('download', help='download help')download_parser.add_argument('--interval',type=int, help='interval help',action=VerifyNoBuild)
download_parser.add_argument('--from',type=int, action=VerifyNoBuild)
download_parser.add_argument('--build',type=int, action=VerifyOnlyBuild)args = parser.parse_args('download --from 1234 --interval 60'.split())
print(args)
# Namespace(build=None, from=1234, interval=60)args = parser.parse_args('download --build 1432'.split())
print(args)
# Namespace(build=1432, from=None, interval=None)args = parser.parse_args('download --build 1432 --from 1234'.split())
print(args)
# usage: test.py download [-h] [--interval INTERVAL] [--from FROM] [--build BUILD]
# test.py download: error: --build should not be used with --from or --intervalargs = parser.parse_args('download --build 1432 --interval 60'.split())
print(args)
# usage: test.py download [-h] [--interval INTERVAL] [--from FROM] [--build BUILD]
# test.py download: error: --build should not be used with --from or --interval
But really, I think this is shorter and simpler:
def parse_options():parser = argparse.ArgumentParser(description='A Tool')subparsers = parser.add_subparsers(help='sub-command help')#create the parser for the 'download' commanddownload_parser = subparsers.add_parser('download', help='download help')download_parser.add_argument('--interval', type=int, help='interval help')download_parser.add_argument('--from', type=int)download_parser.add_argument('--build', type=int)opt=parser.parse_args()from_interval=[getattr(opt,key) is not None for key in ('from','interval')]if opt.build is not None:if any(from_interval):sys.exit('error!')elif not all(from_interval):sys.exit('error!')return opt