← Back to team overview

arsenal-user team mailing list archive

Re: [RFC] lpltk Collection base class

 

On 07/18/2012 01:26 PM, Bryce Harrington wrote:
> lpltk includes classes for objects and for object-collections.
> For example, Message and Messages or Bug and Bugs.
> 
> All of the collection classes have certain functions defined, __iter__,
> __get__, et al.  These are all more or less identical functions.
> 
> I'd like to propose we establish a common (abstract) base class called
> Collection for all of these collection classes, that provides those
> identical functions.
> 
> The benefits of doing this are a) to eliminate code duplication and thus
> make maintenace easier, and b) to reduce the amount of boilerplate
> needed when adding a new collection class.  The net result of adding
> this class should be an overall reduction in line count.
> 
> With this base class, a simple class like Distributions can be reduced
> from 45 lines to 8.  It would look like this:
> 
>     from distribution import Distribution
> 
>     class Distributions(Collection):
>         def __init__(self, lp):
>             Collection.__init__(self, Distribution, lp)
> 
>         def _fetch_all(self):
>             return self._service.launchpad.distributions
> 
> For more complex classes like Bugs or BugTasks, the difference will not
> be as stark, but should still help eliminate redundancy.
> 
> For classes that need to customize behaviors beyond what the base class
> does, all routines will be named with a single underscore so they can
> easily overridden as necessary.
> 
> Below is my draft of the class.  Questions and critique (and +1's)
> welcomed.
> 
> Bryce
> 
> 
> #!/usr/bin/python
> 
> from utils import typecheck_Collection
> 
> class Collection(object):
>     def __init__(self, tk_type, service, lp_objects=None):
>         '''Initialize the instance from a Launchpad bug.'''
>         self._tk_type       = tk_type
>         self._service       = service
>         self._lp_objects    = None
> 
>         if lp_objects:
>             self._lp_objects    = typecheck_Collection(lp_objects)
> 
>     def __len__(self):
>         return len(list(self.__iter__()))
> 
>     def __getitem__(self, key):
>         self._fetch_if_needed()
>         return self._tk_type(self._service, self._lp_objects[key])
> 
>     def __iter__(self):
>         self._fetch_if_needed()
>         for obj in self._lp_objects:
>             o = self._get(obj)
>             yield o
> 
>     def __contains__(self, item):
>         return item in self.__iter__()
> 
>     def _fetch_if_needed(self):
>         if self._lp_objects == None:
>             self._lp_objects = self._fetch_all()
>         assert(self._lp_objects is not None)
> 
>     def _get(self, obj):
>         return self._tk_type(self._service, obj)
> 
>     def _fetch_all(self):
>         '''Override this routine in the subclass to retrieve the necessary
>            data from launchpad and return it as a LPCollection of LP objects.'''
>         import inspect
>         raise TypeError('Abstract method `%s.%s` called' %(
>             type(self).__name__, inspect.stack()[0][3]))
> 
> 
> if __name__ == "__main__":
>     from lpltk import LaunchpadService
>     from distribution import Distribution
> 
>     class MyDists(Collection):
>         def __init__(self, lp):
>             Collection.__init__(self, Distribution, lp)
> 
>         def _fetch_all(self):
>             return self._service.launchpad.distributions
> 
>     lp = LaunchpadService(config={'read_only':True})
>     objects = MyDists(lp)
>     for obj in objects:
>         print obj.display_name,
>         if obj.current_series:
>             print obj.current_series.name
>         else:
>             print "<no series>"
> 
> # vi:set ts=4 sw=4 expandtab:
> 

Makes sense to me. I don't see any problems with this.

-- 
Brad Figg brad.figg@xxxxxxxxxxxxx http://www.canonical.com


Follow ups

References