"""
Illustrates how to use AttributeExtension to listen for change events 
across the board.

"""

from sqlalchemy.orm.interfaces import AttributeExtension, \
    InstrumentationManager

class InstallListeners(InstrumentationManager):
    def post_configure_attribute(self, class_, key, inst):
        """Add an event listener to an InstrumentedAttribute."""

        inst.impl.extensions.insert(0, AttributeListener(key))

class AttributeListener(AttributeExtension):
    """Generic event listener.

    Propagates 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")
        return value

    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")
        return value

    def _report(self, state, value, oldvalue, verb):
        state.obj().receive_change_event(verb, self.key, value, oldvalue)

if __name__ == '__main__':

    from sqlalchemy import Column, Integer, String, ForeignKey
    from sqlalchemy.orm import relationship
    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 = relationship("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


