0

I have an option in OptionParser as seen below:

    foo_choices = ['foo', 'bar', 'mac', 'win'] 
    parser.add_option('-t', '--test',
                  type='choice',
                  action='store',
                  dest='test',
                  choices=foo_choices,
                  default='foo')

However, I want the option to be able to take in more than one choice (can be comma delimited, or allow > 1 argument). Just setting nargs=2 works forpassing in multiple args, so I assume this can be done for type choices. I cannot figure out how to do it to variable number of nargs.

Syntax like:

./demo.py -t foo -o out.out
./demo.py -t foo,bar -o out.out

Or

./demo.py -t foo -o out.out
./demo.py -t foo bar -o out.out

I've tried using a callback to split on commas, but if the callback returns an array, it errors out because the option is an invalid choice 'foo,bar'. The error makes sense, but I'm unsure where to go from here.

def foo_callback(option, opt, value, parser):
  setattr(parser.values, option.dest, value.split(','))

Trying to go for an nargs approach, I tried implementing Callback Example 6,

#!/usr/bin/python

from optparse import OptionParser

def main():
    parser = OptionParser()
    foo_choices = ['foo', 'bar', 'mac', 'win'] 
    parser.add_option('-t', '--test',
                  type='choice',
                  action='callback',
                  dest='test',
                  choices=foo_choices,
                  callback=vararg_callback)
    (options, args) = parser.parse_args()

    print options
    print args

def vararg_callback(option, opt_str, value, parser):
     assert value is None
     value = []

     def floatable(str):
         try:
             float(str)
             return True
         except ValueError:
             return False

     for arg in parser.rargs:
         # stop on --foo like options
         if arg[:2] == "--" and len(arg) > 2:
             break
         # stop on -a, but not on -3 or -3.0
         if arg[:1] == "-" and len(arg) > 1 and not floatable(arg):
             break
         value.append(arg)

     del parser.rargs[:len(value)]
     setattr(parser.values, option.dest, value)    

if __name__ == '__main__':
    main()

This snippet introduces a way to implement a variable number of nargs. However, the only way I got this to work was to remove the type and choices from my option (defeating the purpose of what I'm trying to do). If I didn't, the error I would get is:

[vagrant@machine a]$ ./demo.py -t foo
Traceback (most recent call last):
  File "./demo.py", line 43, in <module>
    main()
  File "./demo.py", line 14, in main
    (options, args) = parser.parse_args()
  File "/usr/lib64/python2.6/optparse.py", line 1394, in parse_args
    stop = self._process_args(largs, rargs, values)
  File "/usr/lib64/python2.6/optparse.py", line 1438, in _process_args
    self._process_short_opts(rargs, values)
  File "/usr/lib64/python2.6/optparse.py", line 1545, in _process_short_opts
    option.process(opt, value, values, self)
  File "/usr/lib64/python2.6/optparse.py", line 788, in process
    self.action, self.dest, opt, value, values, parser)
  File "/usr/lib64/python2.6/optparse.py", line 808, in take_action
    self.callback(self, opt, value, parser, *args, **kwargs)
  File "./demo.py", line 20, in vararg_callback
    assert value is None
AssertionError
Shark
  • 2,322
  • 5
  • 31
  • 44
  • Possible duplicate to http://stackoverflow.com/questions/2756062/optionparser-python-module-multiple-entries-of-same-variable – pmod Feb 05 '15 at 21:21
  • In one of the answers in above link you can find example https://docs.python.org/2/library/email-examples.html – pmod Feb 05 '15 at 21:23
  • 1
    Just quick FYI, [`optparse` is deprecated in favor of `argparse`](http://stackoverflow.com/questions/3217673/why-use-argparse-rather-than-optparse) – C.B. Feb 05 '15 at 21:24
  • @C.B. I know, but I'm limited to Python 2.6 for now. – Shark Feb 05 '15 at 21:25
  • @pmod the example gives you a format like "./demo.py -t foo -t bar", which is not the append style I want. Not to mention, the examples aren't using a "choice" type option. – Shark Feb 05 '15 at 21:30

2 Answers2

1

Just use action='append', default=[] instead of your current setup; no need for a custom callback.
Then you can specify multiples as: -t foo -t bar

If you need a default you can do it afterwards; i.e. something like
test = options.test or ['foo']

tzaman
  • 46,925
  • 11
  • 90
  • 115
  • As mentioned in another comment, I don't want the user to have to repeat -t many times. Syntax should be as simple as possible, so I'd prefer to keep it like "-t foo bar" or "-t foo,bar" – Shark Feb 05 '15 at 21:37
1

I think you can try derive own Option class from standard just to override standard checking for your case:

from optparse import Option, OptionParser, OptionValueError

class MyOption(Option):
    def check_value(option, opt, value):
        for val in value.split(','):
            if val not in option.choices:
                choices = ", ".join(map(repr, option.choices))
                raise OptionValueError("option %s: invalid choice: %r (choose from %s)" % (opt, val, choices))
        return value

 parser = OptionParser(option_class=MyOption)
 foo_choices = ['foo', 'bar', 'mac', 'win'] 
 parser.add_option('-t', '--test',
          type='choice',
          dest='test',
          choices=foo_choices)


(options, args) = parser.parse_args()

print options

Output:

./test1.py -t foo,bar,mac
{'test': 'foo,bar,mac'}
pmod
  • 10,450
  • 1
  • 37
  • 50