← Back to team overview

arsenal-user team mailing list archive

[RFC] lpltk Entity base class


Along the lines of the Collection class I posted earlier, here is a
similarly abstracted Entity class.

Essentially, instead of writing wrapper functions for each function in a
given launchpad api object, we ask the launchpad object what functions
it has, and then dynamically create our own getter functions from that.

Further, for Collection type properties (e.g. bug.messages) it figures
out what the Launchpad data type is via the object's
'resource_type_link' field and then creates the appropriate lpltk
collection class (e.g. Messages).

You can see in the example below that with this, the Distribution class
can be represented in 3 lines (plus custom functions), yet supports more
properties than our current Distribution class.

This class would enable us to more easily wrap ALL of the remaining LP
API, and do it in a more consistent, predictable way.  If new routines
are added to the LP API, this will allow them to be picked up
automatically.  This does add a bit more complexity which might make
things trickier to debug, however that's offset by reducing the size of
the codebase.  It may also require some rejiggering of how classes are
instantiated (so that they all match), and other refactoring, which may
be a bit disruptive.

I'd like to get some feedback on this (and the Collection class) before
I add it to the codebase.  


def lp_to_tk(service, obj):
    '''Factory function to convert lp objects to corresponding lpltk classes'''

    # Look up the proper tk class to cast to
    if not hasattr(obj, 'resource_type_link'):
        return obj
    lpclass = obj.resource_type_link.split('#')[-1:][0]

    # Entry types
    if lpclass == 'team':
        from person import Person
        return Person(None, obj)
    elif lpclass == 'distro_series':
        from distro_series import DistroSeries
        return DistroSeries(service, None, obj)

    # Collection types
    elif lpclass == 'milestone-page-resource':
        from milestones import Milestones
        return Milestones(service, obj)
    elif lpclass == 'specification-page-resource':
        from specifications import Specifications
        return Specifications(service, obj)

    return None

class TKEntry(object):
    def __init__(self, service, lp_entry):
        self.__dict__ = {
            '_service': service,
            '_lp_entry': lp_entry,

        attrs = []
        for key in attrs:
            if key[0] != '_':
                self.__dict__["_"+key] = None

    def __getattr__(self, attr_name):
        if attr_name[0] != '_':
            assert(self._lp_entry is not None)
            assert("_"+attr_name in self.__dict__.keys())
            if self.__dict__["_"+attr_name] is None:
                obj = self._lp_entry.__getattr__(attr_name)
                self.__dict__["_"+attr_name] = lp_to_tk(self._service, obj)
            return self.__dict__["_"+attr_name]

if __name__ == "__main__":
    from lpltk import LaunchpadService

    class Distribution(TKEntry):
        def __init__(self, service, lp_distribution):
            super(Distribution, self).__init__(service, lp_distribution)

        def my_function(self):
            '''This shows that the dynamically added parameters are available in any member function'''
            return "%s %s" %(self._display_name, self._current_series.name)

    lp = LaunchpadService(config={'read_only':True})
    lp_distro = lp.launchpad.distributions['ubuntu']

    obj = Distribution(lp, lp_distro)
    print "Name:", obj.display_name
    print "Owner:", obj.owner
    print "Driver:", obj.driver
    print "Registrant:", obj.registrant
    print "Current:", obj.current_series
    print "Date Created:", obj.date_created
    print "Active Milestones:", obj.active_milestones
    print "All Milestones:", obj.all_milestones
    print "All Specifications:", obj.all_specifications
    print "Valid Specifications:", obj.valid_specifications
    print "Calculated:", obj.my_function()