# # There are a fair number of decorator classes lying around, # and each wrapper needs a new instance of each decorator that's # going to be used. # # Each decorator would like to intercept some requests and # leave others alone (by redefining some methods and not others). # But sometimes we want to leave some of the decorators behind and # only keep the object (for example, we must remove IdDecorator # before we pickle a wrapper because it contains a reference to an # arbitrary object). So the decorators must be kept separate from # the wrapper itself. # # But it would be inefficient (in both space and time) to chain # the decorators together. Each decorator would need to keep a # reference to the next in line, and any request would pass through # all the decorators before finding its destination. # # So this code combines (using inheritance) many decorator classes # into one. It doesn't join together any arbitrary decorator classes, # though: it keeps separate, for example, decorators which can # safely be pickled and ones which can't, so that the unsafe # decorator group is chained to the safe decorator group, which # is chained to the wrapper. In this way code which wants to pickle # a wrapper can pickle the safe group, to preserve as much # information as possible. # import decorator import utils import copy class DependencyError(Exception): pass class Melter(object): """An object which knows how to join decorators with a wrapper. It tries to join decorators into groups.""" def __init__(self, decorators, predicates): """predicates is a list [p1, ..., pn] which decides which decorator can go in which group. It makes n groups containing the decorators for which p1 holds, for which p2 but not p1 holds, and so on. Also, no dependencies of a decorator can be in a higher group.""" self.decorators = {} self.partial_decorators = set() self.bottom = Node.new_node(self, decorator.Decorator, [], []) self.chain = [] self.predicates = predicates for dec in decorators: self.register(dec) def register(self, dec): Node.new_node(self, dec, [self.bottom]) bottom = copy.deepcopy(self.bottom) self.chain = [] # Greedily try to make groups, going from the bottom up. all_predicates = self.predicates + [lambda dec: true] p = all_predicates[0] predicates = all_predicates[1:] del all_predicates ready = [bottom] unready = [] current_group = [] while len(ready) > 0 or len(unready) > 0: # Pick all things which satisfy the predicate and don't # have dependency problems. while len(ready) > 0: single = ready.pop(0) current_group.append(single.dec) for depended in single.depended: single.undepend(depended) if len(depended.depends_on) == 0: if p(depended.dec): ready.append(depended) else: unready.append(depended) # OK, that's all we can do for now. # Put this lot in a group. if len(current_group) != 0: self.chain.append(self._newclass(current_group)) # Now advance the predicate. # There should always be one, since the last entry # accepts any decorator, so only dependency problems # could cause that to fail, and they should have been # caught by Node.new_node. # The combined predicate will hold if any of the old ones # hold or the new one does. p = (lambda p, q: lambda d: p(d) or q(d)) \ (p, predicates[0]) predicates = predicates[1:] # Now some unready things might have become ready. ready = [ n for n in unready if p(n.dec) ] unready = [ n for n in unready if not p(n.dec) ] current_group = [] def melt(self, wrapper): """Decorate a wrapper with the registered decorators.""" for c in self.chain: wrapper = c(wrapper) return wrapper def report(self): self.report_from(self.bottom, "") def report_from(self, node, pre): print pre + str(node.dec) for dep in node.depended: self.report_from(dep, pre + " ") @staticmethod def _newclass(bases): """Produce a new class derived from bases.""" # Remove duplicate bases. We'll have to hope that # the bases are in an acceptable order for now... # (should be possible to fix by looking at the MRO) return type("DerivedClass", tuple(utils.unique(bases)), {}) class DefaultMelter(Melter): def __init__(self): Melter.__init__(self, [id.IdDecorator], [lambda dec: dec.idempotent() and dec.picklable(), lambda dec: dec.idempotent()]) class Node(object): """A node of the dependency graph used by the melter.""" @staticmethod def new_node(melter, dec, bottom): if dec in melter.decorators: return melter.decorators[dec] elif dec in melter.partial_decorators: raise DependencyError, "Recursive dependency between " + \ melter.partial_decorators self = Node() self.dec = dec self.depends_on = set() self.depended = set() self.melter = melter self.bottom = bottom melter.partial_decorators.add(dec) # Now sort out the dependencies. for d in dec.depends(): node = Node.new_node(melter, d, bottom, top) self.depend(node) # Things that don't depend on anything are meant to # depend on bottom. if len(dec.depends()) == 0: for b in bottom: self.depend(b) # Oh good, we're finished. melter.partial_decorators.remove(dec) melter.decorators[dec] = self return self def depend(self, node): self.depends_on.add(node) node.depended.add(self) def undepend(self, node): self.depends_on.discard(node) node.depended.discard(self) def __deepcopy__(self, memo): # We need to ignore self.dec and self.melter # when performing a deep copy. # That leaves depends_on, depended and bottom to copy. new_self = copy.copy(self) new_self.depends_on = copy.deepcopy(new_self.depends_on, memo) new_self.depended = copy.deepcopy(new_self.depended, memory) new_self.bottom = copy.deepcopy(new_self.bottom, memory) return new_self