[TransWarp] Extending AbstractCommand
Phillip J. Eby
pje at telecommunity.com
Tue Oct 14 20:05:47 EDT 2003
At 02:30 PM 10/14/03 -0700, John Landahl wrote:
>And now for something completely different. I'm looking to add a subclass
>of AbstractCommand which does automatic option parsing using
>Optik/OptionParser. Below is what I've done so far, which seems to work
>pretty well. Any suggestions or comments?
>
>
> def parser():
> try: return importString('OptionParser.OptionParser')
I think you mean 'optparse.OptionParser' here, but I could be wrong.
> except:
> try: return importString('optik.OptionParser')
> except: return None
> parser = parser()
>
> class OptionedCommand(AbstractCommand):
> optionList = binding.Require('List of option descriptors')
>
> def parser(self):
> if parser: return parser()
> else: return None
> parser = binding.Make(parser)
Note that if you required OptionParser in a specific location, you could
remove all the 'def parser()' stuff and just do
'binding.Make("optparse.OptionParser")'. :)
> def parsedOptions(self):
> if not self.parser:
> return (None, None)
Somehow it seems to me that not parsing any arguments isn't what you want
to do if the parser isn't available. A traceback seems like the right
response here.
> for opt in self.optionList:
> if type(opt) in [types.ListType, types.TupleType]:
> opt = dict(opt)
> elif type(opt) is not types.DictType:
> continue
Since PEAK requires Python 2.2.2+, there's no reason not to use
'isinstance(opt,(list,tuple))' and 'not isinstance(opt,dict)' in place of
the type checks.
> args = opt['options']
> del opt['options']
>
> self.parser.add_option(*args, **opt)
>
> return self.parser.parse_args(self.argv[1:])
Also, I would've broken the bindings up a little differently, e.g.:
parserFactory = binding.Obtain('import:optparse.OptionParser')
def parser(self):
parser = self.parserFactory()
# code to initialize parser's options
return parser
parser = binding.Make(parser)
parsedOptions = binding.Make(lambda self:
self.parser.parse_args(self.argv[1:]))
The reason I'd do it this way, is that it's a bad idea for a binding to
have a side-effect on another object. In your approach, parsedOptions
modifies self.parser. If you delete parsedOptions and recreate it, the
modifications to self.parser will happen twice! In general, a
binding.Make() function should *never* modify an object it isn't
responsible for creating.
(If you can add a note on the Wiki about this when you write about
binding.Make, that would be fantastic... :) )
> parsedOptions = binding.Make(parsedOptions)
>
> options = binding.Make(lambda self: self.parsedOptions[0])
> args = binding.Make(lambda self: self.parsedOptions[1])
>
>An example command class:
>
> class aCommand(OptionedCommand):
> usage = '''Usage: aCommand -c <arg> -d <arg>'''
>
> optionList = binding.Make(lambda:
> [
> Items(options=('-c', '--create'), action='store',
> type='string', dest='create'),
> Items(options=('-d', '--delete'), action='store',
> type='string', dest='delete'),
> ]
> )
>
> def _run(self):
> print 'create:', self.options.create
> print 'delete:', self.options.delete
> print 'args: ', self.args
Note that you can also create bindings that map to the option values by
default, and use those bindings in all of the object's behavior. Then, a
user of the class can supply constructor keyword arguments for flags, etc.,
as an alternative to creating and passing in argv. e.g.:
delete = create = binding.Delegate('options')
which is equivalent to:
delete = binding.Obtain('options/delete')
create = binding.Obtain('options/create')
Of course, you'll use the latter form if you want a different attribute
name than the name that's used in the option structure.
>Using the example manually:
>
> r = config.makeRoot()
> c = aCommand(r, argv=['aCommand', '-c', 'cArg'])
>
>It seems that this could be extended to use Optik's built-in help
>generator to build the usage string dynamically.
>
>I really like how PEAK's binding and "create once" functionality allows
>the results of arbitrarily complex code to be provided as properties.
>This is an amazing leap forward in code reusability and flexibility, and
>makes for classes that are very easy to use and extend. In this example,
>writers of OptionedCommand subclasses need only provide a list of option
>descriptors, and may then simply use self.options and self.args as needed
>-- without needing to know that they spring into life on demand and are
>the result of several dynamic layers of computation. This is really great
>stuff.
That was basically the idea. I see you've now caught the "binding
fever". Tell me, was the new API helpful to you in catching it? :)
More information about the PEAK
mailing list