multiple callbacks

[email protected] Thu, 26 Jun 2008 19:17:44 -0500
Newsgroups gmane.comp.python.optik.user
Message-ID <OF500744C6.956AAD48-ON86257474.008259C5-86257475.00019F8F@uscmail.uscourts.gov>
Hi.  So I had come up with my own way of parsing through options and
determining what was good and what needed to be errored.  No big deal.  Had
a thought that it would be kewl to extend Options to handle this
automagically, and then it occurred to me that I should probably re-check
the documentation.  Apparently I stopped reading it before I got to the
good bits (ain't that always the case?).

So after reading through the docs and looking in the code a bit (gotta love
open source), it occurred to me that it might be a "nice" feature to be
able to provide multiple callbacks per option.

Here is a use case:

I have 3 options that I want the user to provide.  2 directories and 1
file.  I want to verify that the directories both exist, and are actually
directories.  I want 1 directory to be an absolute path and also writeable.
I want to also verify that the file exists and is writeable.  I've written
4 callbacks that could handle that, without repeating code, but only if I
could pass multiple functions through callback.

ie:

#!/usr/bin/env python

from optparse import make_options, OptionParser, OptionValueError
import os

OPTIONS_LIST = [
  make_option('-c', '--chroot', default=None,
      action='callback', callback=(check_path_exists, check_path_isdir),
      help="specifies the root of the chroot environment to work with"),
  make_option('-w', '--working', default=None,
      action='callback', callback=(check_path_exists, check_path_isdir,
check_writeable),
      help="specifies the working directory"),
  make_option('-o', '--output', default=None,
      action='callback', callback=(check_path_exists, check_writeable),
      help="specifies the file to save the output to"),
  make_option('-d', '--debug', action="store_true", default=False,
      help="enable debugging")
]

def check_path_exists(option, opt_str, value, parser):
  if (not os.path.exists(value)):
      raise OptionValueError('Specified %s path does not exist' %
(opt_str))
  setattr(parser.values, option.dest, value)

def check_path_isdir(option, opt_str, value, parser):
  if (not os.path.isdir(value)):
    raise OptionValueError('Specified %s path is not a directory' %
(opt_str))
  setattr(parser.values, option.dest, value)

def check_abspath(option, opt_str, value, parser):
  if (not options.working.startswith("/")):
    raise OptionValueError("Specified directory must reference an absolute
path")
  setattr(parser.values, option.dest, value)

def check_writable(option, opt_str, value, parser):
  if (not os.access(options.output, os.W_OK)):
    raise OptionValueError('Cannot write to location provided')
  setattr(parser.values, option.dest, value)

p = OptionParser(OPTION_LIST)
(options, args) = p.parse_args()

######### End script

This of course doesn't work at this point.... but a simple modification to
2 spots inside the {optik,optparse}.py such as this:

Make this change in Option._check_callback (approx line704)

Before:
    def _check_callback(self):
        if self.action == "callback":
            if not callable(self.callback):
                raise OptionError(
                    "callback not callable: %r" % self.callback, self)
After:
    def _check_callback(self):
        if self.action == "callback":
            if type(self.callback) is not types.TupleType:
      self.callback = (self.callback,)
            for cb in self.callback:
                if not callable(cb):
                    raise OptionError(
                        "callback not callable: %r" % cb, self)


Then in Option.take_action (approx line 804):

Before:
        elif action == "callback":
            args = self.callback_args or ()
            kwargs = self.callback_kwargs or {}
            self.callback(self, opt, value, parser, *args, **kwargs)

After:
        elif action == "callback":
            args = self.callback_args or ()
            kwargs = self.callback_kwargs or {}
            for cb in self.callback:
                cb(self, opt, value, parser, *args, **kwargs)


Anyways... just a thought.  There is probably more to the change (and I'm
sure a unittest would have to probably be made/adjusted) but I just wanted
to throw it out there.

-greg


-------------------------------------------------------------------------
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php