[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