Detecting if any command-line options were specified more than once with optparse or argparse

StackOverflow https://stackoverflow.com/questions/12461087

  •  02-07-2021
  •  | 
  •  

Question

Python optparse normally allows the user to specify an option more than once and silently ignores all occurrences of the option but the last one. For example, if the action of option --foo is store and the action of option --flag is store_const, store_true or store_false, the following commands will be equivalent:

my-command --foo=bar --foo=another --flag --foo=last --flag
my-command --flag --foo=last

(Update: argparse does just the same thing by default.)

Now, I have a lot of options, and specifying any of them more than once doesn't make sense. If a user specifies the same option more than once I'd like to warn them about the possible error.

What is the most elegant way to detect options that were specified multiple times? Note that the same option can have a short form, a long form and abbreviated long forms (so that -f, --foobar, --foob and --foo are all the same option). It would be even better if it was possible to detect the case when multiple options that have the same destination were specified simultaneously, so that a warning can be given if a user specifies both --quiet and --verbose while both options store a value into the same destination and effectively override each other.

Update: To be more user-friendly, the warning should refer to the exact option names as used on the command line. Using append actions instead of store is possible, but when we detect a conflict, we cannot say which options caused it (was it -q and --verbose or --quiet --quiet?).

Unfortunately I'm stuck with optparse and cannot use argparse because I have to support Python 2.6.

P. S. If you know of a solution that works only with argparse, please post it, too. While I try to minimize the number of external dependencies, using argparse under Python 2.6 is still an option.

Was it helpful?

Solution

I think the correct way would be to "define your action" in some way.

For example, you could use the action callback and implement a function that implement your desired behaviour. You could write a function that first checks if the destination was already filled, if it is filled then it stores the overlapping options into a list. When the parsing is finished you should check if these lists are empty, and if they are not raise the appropriate exception.

Another way of doing this could be to define your own action. You can have a look here

A small example that uses the callback:

import sys
import functools
from optparse import OptionParser


bad_option = 'BAD OPTION'

def store(option, opt, value, parser, dest, val):
    """Set option's destination *dest* to *val*  if there are no conflicting options."""
    list_name = dest + '_options_list'
    try:
        # if this option is a conflict, save its name and set the value to bad_option
        getattr(parser.values, list_name).append(opt)
        setattr(parser.values, dest, bad_option)
    except AttributeError:
        # no conflicts, set the option value and add the options list
        setattr(parser.values, dest, val)
        setattr(parser.values, list_name, [opt])

store_true = functools.partial(store, val=True)
store_false = functools.partial(store, val=False)


parser = OptionParser()
parser.add_option('-v', '--verbose',
                  action='callback', callback=store_true,
                  help='Increase output verbosity',
                  callback_kwargs={'dest': 'verbose'})

parser.add_option('-q', '--quiet',
                  action='callback', callback=store_false,
                  help='Decrease output verbosity',
                  callback_kwargs={'dest': 'verbose'})

opts, args = parser.parse_args()

# detects all conflicting options for all destinations
found = False
for dest in ('verbose',):
    if getattr(opts, dest) == bad_option:
        conflicting_opts = ', '.join(getattr(opts, dest + '_options_list'))
        print('Conflicting options %s for destination %s'
              % (conflicting_opts, dest))
        found = True

if found:
    parser.print_usage()
    sys.exit(2)

And the output:

$ python testing_optparse.py -v -q
Conflicting options -v, -q for destination verbose
Usage: prova_optparse.py [options]

Probably it would be better to raise an OptionValueError when detecting conflicts, even though this would allow to get only couple of conflicting options. If you want to get all conflicting options you have to parse the remaining arguments( in parser.rargs).

OTHER TIPS

You can use action="append" (optparse) and then check the number of appended elements. See http://docs.python.org/library/optparse.html#other-actions

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top