from __future__ import with_statement from UserDict import DictMixin from peak.util.decorators import classy from peak.util.addons import Registry from threading import currentThread, RLock from logging import getLogger services_log = getLogger('services') __all__ = ['State', 'Service', 'overrides'] class State(object, DictMixin): def __init__(self, parent=None): self.parent = parent self.data = {} if parent is None: self.lock = RLock() else: self.lock = parent.lock def keys(self): keys = set(self.data.keys()) if self.parent is not None: keys.update(self.parent.keys()) return keys def __getitem__(self, key): with self.lock: if key in self.data: return self.data[key] elif self.parent is not None: return self.parent[key] else: raise KeyError("Service unavailable: %s" % key) def __setitem__(self, key, value): with self.lock: self._check_key(key) self.data[key] = value def update(self, dct): with self.lock: for key in dct: self._check_key(key) super(State, self).update(dct) def _check_key(self, key): if key in self.data: raise KeyError("Service instance for %r is already set: %r" % (key, self.data[key])) def child(self): return State(self) def __enter__(self): services_log.debug("Entering state %s", self) state_stack().append(self) return self def __exit__(self, *exc_info): services_log.debug("Exiting state %s", self) assert State.get() is self del state_stack()[-1] @staticmethod def get(): return state_stack()[-1] root_state = State() def state_stack(): return currentThread().__dict__.setdefault(State, [root_state]) class ServiceOverrides(Registry): def created_for(self, service): self[service] = True def overrides(*services): meta = ServiceOverrides.for_enclosing_class() for service in services: for overriden in ServiceOverrides(service): meta[overriden] = True def redirect_attribute(cls, name, payload): setattr(type(cls), name, property( lambda s: getattr(s.get(), name), lambda s,v: setattr(s.get(), name, v), lambda s: delattr(s.get(), name), )) class ServiceAvailableProperty(object): def __get__(self, instance, owner): return owner in State.get() _service_methods = frozenset('__getitem__ __setitem__ __delitem__ __iter__'.split()) class Service(classy): def __class_init__(cls, name, bases, cdict, supr): supr()(cls, name, bases, cdict, supr) metabases = (type(cls),) #if len(bases) > 1: # metabases += tuple(map(type, bases[1:])) #print cls, bases, metabases cls.__class__ = type(metabases[0])(cls.__name__ + 'Class', metabases, dict(__module__=cls.__module__))#, __for_class__=cls)) for k, v in cdict.items(): if isinstance(k, basestring): if isinstance(v, (classmethod, staticmethod, ServiceAvailableProperty)): continue #if isinstance(v, property): # if isinstance(v.fget, (classmethod, staticmethod)): # continue if not k.startswith('_') or k in _service_methods: redirect_attribute(cls, k, v) if bases == (classy,): return overrides = ServiceOverrides(cls) for base in bases: if issubclass(base, Service) and base is not Service: overrides[base] = True overrides.update(ServiceOverrides(base)) @classmethod def get(cls): return State.get()[cls] available = ServiceAvailableProperty() @classmethod def activate(cls, *args, **kw): services_log.info("Activating service %s", cls) service = cls(*args, **kw) upd = dict.fromkeys(ServiceOverrides(cls).keys(), service) State.get().update(upd) if __name__ == '__main__': class Base(Service): pass class Ext(Service): overrides(Base) a = 1 class ExtPlus(Ext): pass from pprint import * from nose.tools import * assert set(ServiceOverrides(ExtPlus).keys()) == set([Base, Ext, ExtPlus]) assert_raises(KeyError, Base.get) assert not Base.available Base.activate() assert Base.available assert not Ext.available base = Base.get() assert_raises(KeyError, Ext.get) with State.get().child(): Ext.activate() assert Ext.get() is Base.get() is not base assert Base.get() is base assert_raises(KeyError, Ext.activate) with State(): Ext.activate() assert isinstance(Base.get(), Ext) with State.get().child() as state: Ext.activate() #pprint(state.factories) assert isinstance(Base.get(), Ext) with State.get().child(): ExtPlus.activate() assert Ext.get() is ExtPlus.get() assert Base.get() is ExtPlus.get() assert_raises(KeyError, Ext.activate) assert ExtPlus.a == 1 assert Ext.a == 1 Ext.a = 2 assert ExtPlus.a == 2 assert_raises(AttributeError, lambda: Base.a) assert_raises(KeyError, ExtPlus.get) with State(): Ext.activate() assert isinstance(Base.get(), Ext) assert Base.available with State.get().child(): assert Base.get() is base ExtPlus.activate() Ext.a = 'foo' with State.get().child(): assert ExtPlus.get() is Ext.get() is Base.get() assert ExtPlus.a == 'foo' assert_raises(KeyError, ExtPlus.activate) assert_raises(KeyError, Ext.activate) assert_raises(KeyError, Base.activate)