Package: glue.py
glue.py is a single lightweight utility module expanding Python with newpowerful idioms for accessors, callsets, weak methods, enums, singletons andobservable primitives that can easily be integrated with existing projects. Itis particularly useful for writing model/view oriented applications.Installation============You'll require Python 2.7, Python 3+ or PyPy 1.9 to use glue.py. Earlierversions are not supported.glue.py can easily be installed from the Python package repository usingdistutils or pip:pip install glue.pyIf you plan to make amendments to the codebase, or you're interested in thelatest development release, it is best to check out the source tree andinstall glue.py in development mode:python setup.py developUsage=====glue.py provides a larger set of interoperating utility classes and functions,of which only the most important ones will be described here in detail. It'snot a bad idea to just browse through the source and look what else is therebesides what's covered in this introduction.Generated Accessors-------------------While Python supports accessors through properties, providing regulated accessto class variables has been traditionally cumbersome and repetitive, even thoughthe syntax has somewhat improved with recent versions. Consider this classicalapproach to property access:from types import NoneTypeclass Sponge(object):passclass Pineapple(object):on_owner_changed = Nonedef __init__(self):self.__owner = Nonedef get_owner(self):return self.__ownerdef set_owner(self, value):if value == self.__owner:returnassert isinstance(value, (Sponge, NoneType))self.__owner = valueif self.on_owner_changed:self.on_owner_changed(self)owner = property(get_owner, set_owner,doc = "the owner of this Pineapple")apart from preventing a rewrite, the setter also provides an optional hook thatcan be triggered when the value changes. Notice how many times the word "owner"has to be repeated in one form or the other in order to expose this relativelysimple attribute. Until Python 2.7, most people adapted this handy recipe tocut down on repetition:def owner():doc = """the owner of this pineapple"""def fget(self):return self.__ownerdef fset(self, value):if value == self.__owner:returnassert isinstance(value, (Sponge, NoneType))self.__owner = valueif self.on_owner_changed:self.on_owner_changed(self)return property(**locals())owner = owner()That's slightly better, but not perfect, and also a little convoluted. Thesedays, this approach is the status quo:@propertydef owner(self):"""The owner of this pineapple"""return self.__owner@owner.setterdef owner(self, value):if value == self.__owner:returnassert isinstance(value, (Sponge, NoneType))self.__owner = valueif self.on_owner_changed:self.on_owner_changed(self)it seems like we're mostly shuffling details around... all three solutionsvary around 12 lines, and with a bunch of these, classes may seem a lot largerthan they are. This is how glue.py does it:from glue import (lazydecls, defproperty, autoinit, callset)from types import NoneTypeclass Sponge(object):pass@lazydeclsclass Pineapple(object):on_owner_changed = callset()owner = defproperty(default = None, types=(Sponge, NoneType), hook = on_owner_changed,doc = "The owner of this pineapple")def __init__(self):autoinit()...case closed. For more information, try `help(glue.defproperty)`.Callable Sets-------------You may have noticed that the `Pineapple` class exposes the `on_owner_changed`callback as a `callset`, which is just a short way of instantiating a`CallableSet`. Callable sets are a fast way to create observables:from glue import callsetdef func1(value):print("func1 called with value",value)return value+10def func2(value):print("func2 called with value",value)return value+20def func3(value):print("func3 called with value",value)return value+30Trying this on the command line:>>> funcs = callset([func1,func2,func3])>>> print(max(funcs(10)))func1 called with value 10func2 called with value 10func3 called with value 1040In the pineapple example, this is how a view would typically subscribe toevents sent by the `Pineapple` class:@lazydeclsclass Squid(object):neighbor_house = defproperty()annoying_coworkers = defproperty(default = set)def __init__(self, **kwargs):# auto-assign matching keyword argsautoinit(**kwargs)# track if the owner of the neighbor house changes by registering# Squid's callback. The callback will be weakly proxied, and# automatically unregisters when this Squid instance is no longer# referenced.self.neighbor_house.on_owner_changed.addweak(self.someone_moved_in)def someone_moved_in(self, event):# since the on_owner_changed callset is global to Pineapple, Squid# would be notified of any owner changes in Pineapples, so filter# for the right one.if not (event.instance is self.neighbor_house):return# check if the new tenant is one of our annoying coworkersif event.value in self.annoying_coworkers:# state opinion on the situationprint("Meh.")Trying this on the command line:>>> house = Pineapple()>>> bob = Sponge()>>> squidward = Squid(neighbor_house = house)>>> squidward.annoying_coworkers.add(bob)>>> house.owner = bobMeh.And that's the gist of it.Weak Callable Proxies---------------------TODOObservable Dicts, Lists and Sets--------------------------------TODOEnums-----TODOSingletons----------TODO