From: Mike Bayer Date: Fri, 29 Aug 2008 16:15:41 +0000 (+0000) Subject: - add an example illustrating attribute event reception. X-Git-Tag: rel_0_5rc1~31 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4c6c996f9bc515373f68cdb73230a9acb1f68bde;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - add an example illustrating attribute event reception. --- diff --git a/examples/custom_attributes/listen_for_events.py b/examples/custom_attributes/listen_for_events.py new file mode 100644 index 0000000000..71f8bbadec --- /dev/null +++ b/examples/custom_attributes/listen_for_events.py @@ -0,0 +1,83 @@ +""" +Illustrates how to use AttributeExtension to listen for change events. + +""" + +from sqlalchemy.orm.interfaces import AttributeExtension, InstrumentationManager + +class InstallListeners(InstrumentationManager): + def instrument_attribute(self, class_, key, inst): + """Add an event listener to all InstrumentedAttributes.""" + + inst.impl.extensions.append(AttributeListener(key)) + return super(InstallListeners, self).instrument_attribute(class_, key, inst) + +class AttributeListener(AttributeExtension): + """Generic event listener. + + Propigates attribute change events to a + "receive_change_event()" method on the target + instance. + + """ + def __init__(self, key): + self.key = key + + def append(self, state, value, initiator): + self._report(state, value, None, "appended") + + def remove(self, state, value, initiator): + self._report(state, value, None, "removed") + + def set(self, state, value, oldvalue, initiator): + self._report(state, value, oldvalue, "set") + + def _report(self, state, value, oldvalue, verb): + state.obj().receive_change_event(verb, self.key, value, oldvalue) + +if __name__ == '__main__': + + from sqlalchemy import * + from sqlalchemy.orm import * + from sqlalchemy.ext.declarative import declarative_base + + class Base(object): + __sa_instrumentation_manager__ = InstallListeners + + def receive_change_event(self, verb, key, value, oldvalue): + s = "Value '%s' %s on attribute '%s', " % (value, verb, key) + if oldvalue: + s += "which replaced the value '%s', " % oldvalue + s += "on object %s" % self + print s + + Base = declarative_base(cls=Base) + + class MyMappedClass(Base): + __tablename__ = "mytable" + + id = Column(Integer, primary_key=True) + data = Column(String(50)) + related_id = Column(Integer, ForeignKey("related.id")) + related = relation("Related", backref="mapped") + + def __str__(self): + return "MyMappedClass(data=%r)" % self.data + + class Related(Base): + __tablename__ = "related" + + id = Column(Integer, primary_key=True) + data = Column(String(50)) + + def __str__(self): + return "Related(data=%r)" % self.data + + # classes are instrumented. Demonstrate the events ! + + m1 = MyMappedClass(data='m1', related=Related(data='r1')) + m1.data = 'm1mod' + m1.related.mapped.append(MyMappedClass(data='m2')) + del m1.data + + \ No newline at end of file diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 2d7c726a1f..e67026eead 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -1545,7 +1545,10 @@ class InstrumentationRegistry(object): def state_of(self, instance): if instance is None: raise AttributeError("None has no persistent state.") - return self.state_finders[instance.__class__](instance) + try: + return self.state_finders[instance.__class__](instance) + except KeyError: + raise AttributeError("%r is not instrumented" % instance.__class__) def state_or_default(self, instance, default=None): if instance is None: diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index ff15062aab..6dd2225c88 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -702,17 +702,20 @@ class PropertyOption(MapperOption): return l class AttributeExtension(object): - """An abstract class which specifies `append`, `delete`, and `set` - event handlers to be attached to an object property. + """An event handler for individual attribute change events. + + AttributeExtension is assembled within the descriptors associated + with a mapped class. + """ - def append(self, obj, child, initiator): + def append(self, state, value, initiator): pass - def remove(self, obj, child, initiator): + def remove(self, state, value, initiator): pass - def set(self, obj, child, oldchild, initiator): + def set(self, state, value, oldvalue, initiator): pass