How do you handle parameters that cannot be used together (using OptionParser)?

My Python script (for todo lists) is run from the command line as follows:

todo [options] <command> [command-options] 

Some parameters cannot be used together, for example

 todo add --pos=3 --end "Ask Stackoverflow" 

will indicate both the third position and the end of the list. Similarly

 todo list --brief --informative 

will confuse my program about being concise or informative. Since I want to have quite powerful control over the parameters, such cases will be a bunch, and new ones will undoubtedly arise in the future. If users pass a poor combination of parameters, I want to give an informative message, preferably together with the help provided by optparse. I am currently processing this with an if-else statement, which I find very ugly and poor. My dream is to have something like this in my code:

 parser.set_not_allowed(combination=["--pos", "--end"], message="--pos and --end can not be used together") 

and OptionParser will use this when parsing the parameters.

Since this does not exist, as far as I know, I ask the SO community: How do you deal with this?

+7
python optparse
source share
2 answers

Perhaps by expanding optparse.OptionParser :

 class Conflict(object): __slots__ = ("combination", "message", "parser") def __init__(self, combination, message, parser): self.combination = combination self.message = str(message) self.parser = parser def accepts(self, options): count = sum(1 for option in self.combination if hasattr(options, option)) return count <= 1 class ConflictError(Exception): def __init__(self, conflict): self.conflict = conflict def __str__(self): return self.conflict.message class MyOptionParser(optparse.OptionParser): def __init__(self, *args, **kwds): optparse.OptionParser.__init__(self, *args, **kwds) self.conflicts = [] def set_not_allowed(self, combination, message): self.conflicts.append(Conflict(combination, message, self)) def parse_args(self, *args, **kwds): # Force-ignore the default values and parse the arguments first kwds2 = dict(kwds) kwds2["values"] = optparse.Values() options, _ = optparse.OptionParser.parse_args(self, *args, **kwds2) # Check for conflicts for conflict in self.conflicts: if not conflict.accepts(options): raise ConflictError(conflict) # Parse the arguments once again, now with defaults return optparse.OptionParser.parse_args(self, *args, **kwds) 

Then you can handle ConflictError where you call parse_args :

 try: options, args = parser.parse_args() except ConflictError as err: parser.error(err.message) 
+6
source share

TamΓ‘s answer is a good start, but I couldn’t get it working because it (or had) a lot of errors, including a broken super call , the "parser" missing in Conflict.__slots__ , always raising an error when a conflict is indicated due to using parser.has_option() in Conflicts.accepts() , etc.

Since I really need this feature, I turned my own solution around and made it available from the Python Package Index as ConflictsOptionParser . It works pretty much as a drop in optparse.OptionParser replacement. (I know argparse is a new command-line syntax, but it is not available in Python 2.6 and lower and is currently smaller than optparse . Email me if you want to hack or hack an additional argparse .) The key consists of two new methods: register_conflict() and, to a lesser extent, unregister_conflict() :

 #/usr/bin/env python import conflictsparse parser = conflictsparse.ConflictsOptionParser("python %prog [OPTIONS] ARG") # You can retain the Option instances for flexibility, in case you change # option strings later verbose_opt = parser.add_option('-v', '--verbose', action='store_true') quiet_opt = parser.add_option('-q', '--quiet', action='store_true') # Alternatively, you don't need to keep references to the instances; # we can re-use the option strings later parser.add_option('--no-output', action='store_true') # Register the conflict. Specifying an error message is optional; the # generic one that is generated will usually do. parser.register_conflict((verbose_opt, quiet_opt, '--no-output')) # Now we parse the arguments as we would with # optparse.OptionParser.parse_args() opts, args = parser.parse_args() 

It has several advantages over the solution started by Tamas:

  • It works out of the box and installs pip (or easy_install , if necessary).
  • Parameters in the conflict can be indicated either by their optional lines or by their optparse.Option instances, which helps with the DRY principle; if you use instances, you can change the actual lines without worrying about breaking the conflict code.
  • This follows the normal optparse.OptionParser.parse_args() behavior and automatically calls optparse.OptionParser.error() when it detects conflicting parameters in command line arguments, rather than throwing an error directly. (This is both a function and an error, like an error in the optparse general design, but a function for this package, since it is at least consistent with the behavior of optparse .)
+3
source share

All Articles