diff options
| -rw-r--r-- | examples/custom_attributes/listen_for_events.py | 83 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 5 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 13 |
3 files changed, 95 insertions, 6 deletions
diff --git a/examples/custom_attributes/listen_for_events.py b/examples/custom_attributes/listen_for_events.py new file mode 100644 index 000000000..71f8bbade --- /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 2d7c726a1..e67026eea 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 ff15062aa..6dd2225c8 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 |
