summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2008-08-29 16:15:41 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2008-08-29 16:15:41 +0000
commit4c6c996f9bc515373f68cdb73230a9acb1f68bde (patch)
tree17da59ff248012eb391faa706bdf7e2be7f44a30
parentebbae4fa0a936f6a4446bc7b4e165ff969d29fd0 (diff)
downloadsqlalchemy-4c6c996f9bc515373f68cdb73230a9acb1f68bde.tar.gz
- add an example illustrating attribute event reception.
-rw-r--r--examples/custom_attributes/listen_for_events.py83
-rw-r--r--lib/sqlalchemy/orm/attributes.py5
-rw-r--r--lib/sqlalchemy/orm/interfaces.py13
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