python argparse subcommand with dependency and conflict

2024/10/13 23:18:04

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:

  1. ensure '--from' and '--interval' are always together used
  2. 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.

Answer

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
https://en.xdnf.cn/q/69480.html

Related Q&A

Running django project without django installation

I have developed a project with Django framework(python and mysql DB) in Linux OS(Ubuntu 12.04), I want to run this project in localhost in another machine with Linux(Ubuntu 12.04) without installing D…

redefine __and__ operator

Why I cant redefine the __and__ operator?class Cut(object):def __init__(self, cut):self.cut = cutdef __and__(self, other):return Cut("(" + self.cut + ") && (" + other.cut +…

How to find class of bound method during class construction in Python 3.1?

i want to write a decorator that enables methods of classes to become visible to other parties; the problem i am describing is, however, independent of that detail. the code will look roughly like this…

Multi-Threaded NLP with Spacy pipe

Im trying to apply Spacy NLP (Natural Language Processing) pipline to a big text file like Wikipedia Dump. Here is my code based on Spacys documentation example:from spacy.en import Englishinput = open…

Django Tastypie throws a maximum recursion depth exceeded when full=True on reverse relation.

I get a maximum recursion depth exceeded if a run the code below: from tastypie import fields, utils from tastypie.resources import ModelResource from core.models import Project, Clientclass ClientReso…

Adding a colorbar to two subplots with equal aspect ratios

Im trying to add a colorbar to a plot consisting of two subplots with equal aspect ratios, i.e. with set_aspect(equal):The code used to create this plot can be found in this IPython notebook.The image …

Why is C++ much faster than python with boost?

My goal is to write a small library for spectral finite elements in Python and to that purpose I tried extending python with a C++ library using Boost, with the hope that it would make my code faster. …

pandas: How to get .to_string() method to align column headers with column values?

This has been stumping me for a while and I feel like there has to be a solution since printing a dataframe always aligns the columns headers with their respective values.example:df = pd.DataFrame({Fir…

Do I need to use `nogil` in Cython

I have some Cython code that Id like to run as quickly as possible. Do I need to release the GIL in order to do this? Lets suppose my code is similar to this: import numpy as np# trivial definition ju…

supervisord environment variables setting up application

Im running an application from supervisord and I have to set up an environment for it. There are about 30 environment variables that need to be set. Ive tried putting all on one bigenvironment=line and…