]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
merge tip
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 18 Nov 2010 23:49:42 +0000 (18:49 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 18 Nov 2010 23:49:42 +0000 (18:49 -0500)
1  2 
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/deprecated_interfaces.py
lib/sqlalchemy/orm/events.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/strategies.py
test/orm/test_generative.py
test/orm/test_mapper.py

Simple merge
index 3e4f268d08057b8ca5eba520e3bf199f3fd3b46d,0000000000000000000000000000000000000000..52193b149abd5959b572bd8d430eb801ce1ad000
mode 100644,000000..100644
--- /dev/null
@@@ -1,569 -1,0 +1,573 @@@
 +from sqlalchemy import event, util
 +from interfaces import EXT_CONTINUE
 +
 +
 +class MapperExtension(object):
 +    """Base implementation for :class:`.Mapper` event hooks.
 +
 +    .. note:: :class:`.MapperExtension` is deprecated.   Please
 +       refer to :func:`.event.listen` as well as 
 +       :class:`.MapperEvents`.
 +    
 +    New extension classes subclass :class:`.MapperExtension` and are specified
 +    using the ``extension`` mapper() argument, which is a single
 +    :class:`.MapperExtension` or a list of such::
 +    
 +        from sqlalchemy.orm.interfaces import MapperExtension
 +    
 +        class MyExtension(MapperExtension):
 +            def before_insert(self, mapper, connection, instance):
 +                print "instance %s before insert !" % instance
 +    
 +        m = mapper(User, users_table, extension=MyExtension())
 +    
 +    A single mapper can maintain a chain of ``MapperExtension``
 +    objects. When a particular mapping event occurs, the
 +    corresponding method on each ``MapperExtension`` is invoked
 +    serially, and each method has the ability to halt the chain
 +    from proceeding further::
 +    
 +        m = mapper(User, users_table, extension=[ext1, ext2, ext3])
 +    
 +    Each ``MapperExtension`` method returns the symbol
 +    EXT_CONTINUE by default.   This symbol generally means "move
 +    to the next ``MapperExtension`` for processing".  For methods
 +    that return objects like translated rows or new object
 +    instances, EXT_CONTINUE means the result of the method
 +    should be ignored.   In some cases it's required for a 
 +    default mapper activity to be performed, such as adding a 
 +    new instance to a result list.
 +    
 +    The symbol EXT_STOP has significance within a chain
 +    of ``MapperExtension`` objects that the chain will be stopped
 +    when this symbol is returned.  Like EXT_CONTINUE, it also
 +    has additional significance in some cases that a default
 +    mapper activity will not be performed.
 +    
 +    """
 +
 +    @classmethod
 +    def _adapt_instrument_class(cls, self, listener):
 +        cls._adapt_listener_methods(self, listener, ('instrument_class',))
 +
 +    @classmethod
 +    def _adapt_listener(cls, self, listener):
 +        cls._adapt_listener_methods(
 +            self, listener,
 +            (
 +            'init_instance',
 +            'init_failed',
 +            'translate_row',
 +            'create_instance',
 +            'append_result',
 +            'populate_instance',
 +            'reconstruct_instance',
 +            'before_insert',
 +            'after_insert',
 +            'before_update',
 +            'after_update',
 +            'before_delete',
 +            'after_delete'
 +        ))
 +        
 +    @classmethod
 +    def _adapt_listener_methods(cls, self, listener, methods):
 +        
 +        for meth in methods:
 +            me_meth = getattr(MapperExtension, meth)
 +            ls_meth = getattr(listener, meth)
 +            
 +            # TODO: comparing self.methods to cls.method, 
 +            # this comparison is probably moot
 +            
 +            if me_meth is not ls_meth:
 +                if meth == 'reconstruct_instance':
 +                    def go(ls_meth):
 +                        def reconstruct(instance):
 +                            ls_meth(self, instance)
 +                        return reconstruct
 +                    event.listen(go(ls_meth), 'on_load', 
 +                                        self.class_manager, raw=False, propagate=True)
 +                elif meth == 'init_instance':
 +                    def go(ls_meth):
 +                        def init_instance(instance, args, kwargs):
 +                            ls_meth(self, self.class_, 
 +                                        self.class_manager.original_init, 
 +                                        instance, args, kwargs)
 +                        return init_instance
 +                    event.listen(go(ls_meth), 'on_init', 
 +                                            self.class_manager, raw=False, propagate=True)
 +                elif meth == 'init_failed':
 +                    def go(ls_meth):
 +                        def init_failed(instance, args, kwargs):
 +                            util.warn_exception(ls_meth, self, self.class_, 
 +                                            self.class_manager.original_init, 
 +                                            instance, args, kwargs)
 +                            
 +                        return init_failed
 +                    event.listen(go(ls_meth), 'on_init_failure', 
 +                                        self.class_manager, raw=False, propagate=True)
 +                else:
 +                    event.listen(ls_meth, "on_%s" % meth, self, 
 +                                        raw=False, retval=True, propagate=True)
 +
 +
 +    def instrument_class(self, mapper, class_):
 +        """Receive a class when the mapper is first constructed, and has
 +        applied instrumentation to the mapped class.
 +        
 +        The return value is only significant within the ``MapperExtension`` 
 +        chain; the parent mapper's behavior isn't modified by this method.
 +        
 +        """
 +        return EXT_CONTINUE
 +
 +    def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
 +        """Receive an instance when it's constructor is called.
 +        
 +        This method is only called during a userland construction of 
 +        an object.  It is not called when an object is loaded from the
 +        database.
 +        
 +        The return value is only significant within the ``MapperExtension`` 
 +        chain; the parent mapper's behavior isn't modified by this method.
 +
 +        """
 +        return EXT_CONTINUE
 +
 +    def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
 +        """Receive an instance when it's constructor has been called, 
 +        and raised an exception.
 +        
 +        This method is only called during a userland construction of 
 +        an object.  It is not called when an object is loaded from the
 +        database.
 +        
 +        The return value is only significant within the ``MapperExtension`` 
 +        chain; the parent mapper's behavior isn't modified by this method.
 +
 +        """
 +        return EXT_CONTINUE
 +
 +    def translate_row(self, mapper, context, row):
 +        """Perform pre-processing on the given result row and return a
 +        new row instance.
 +
 +        This is called when the mapper first receives a row, before
 +        the object identity or the instance itself has been derived
 +        from that row.   The given row may or may not be a 
 +        ``RowProxy`` object - it will always be a dictionary-like
 +        object which contains mapped columns as keys.  The 
 +        returned object should also be a dictionary-like object
 +        which recognizes mapped columns as keys.
 +        
 +        If the ultimate return value is EXT_CONTINUE, the row
 +        is not translated.
 +        
 +        """
 +        return EXT_CONTINUE
 +
 +    def create_instance(self, mapper, selectcontext, row, class_):
 +        """Receive a row when a new object instance is about to be
 +        created from that row.
 +
 +        The method can choose to create the instance itself, or it can return
 +        EXT_CONTINUE to indicate normal object creation should take place.
 +
 +        mapper
 +          The mapper doing the operation
 +
 +        selectcontext
 +          The QueryContext generated from the Query.
 +
 +        row
 +          The result row from the database
 +
 +        class\_
 +          The class we are mapping.
 +
 +        return value
 +          A new object instance, or EXT_CONTINUE
 +
 +        """
 +        return EXT_CONTINUE
 +
 +    def append_result(self, mapper, selectcontext, row, instance, 
 +                        result, **flags):
 +        """Receive an object instance before that instance is appended
 +        to a result list.
 +
 +        If this method returns EXT_CONTINUE, result appending will proceed
 +        normally.  if this method returns any other value or None,
 +        result appending will not proceed for this instance, giving
 +        this extension an opportunity to do the appending itself, if
 +        desired.
 +
 +        mapper
 +          The mapper doing the operation.
 +
 +        selectcontext
 +          The QueryContext generated from the Query.
 +
 +        row
 +          The result row from the database.
 +
 +        instance
 +          The object instance to be appended to the result.
 +
 +        result
 +          List to which results are being appended.
 +
 +        \**flags
 +          extra information about the row, same as criterion in
 +          ``create_row_processor()`` method of
 +          :class:`~sqlalchemy.orm.interfaces.MapperProperty`
 +        """
 +
 +        return EXT_CONTINUE
 +
 +    def populate_instance(self, mapper, selectcontext, row, 
 +                            instance, **flags):
 +        """Receive an instance before that instance has
 +        its attributes populated.
 +
 +        This usually corresponds to a newly loaded instance but may
 +        also correspond to an already-loaded instance which has
 +        unloaded attributes to be populated.  The method may be called
 +        many times for a single instance, as multiple result rows are
 +        used to populate eagerly loaded collections.
 +
 +        If this method returns EXT_CONTINUE, instance population will
 +        proceed normally.  If any other value or None is returned,
 +        instance population will not proceed, giving this extension an
 +        opportunity to populate the instance itself, if desired.
 +
 +        As of 0.5, most usages of this hook are obsolete.  For a
 +        generic "object has been newly created from a row" hook, use
 +        ``reconstruct_instance()``, or the ``@orm.reconstructor``
 +        decorator.
 +
 +        """
 +        return EXT_CONTINUE
 +
 +    def reconstruct_instance(self, mapper, instance):
 +        """Receive an object instance after it has been created via
 +        ``__new__``, and after initial attribute population has
 +        occurred.
 +
 +        This typically occurs when the instance is created based on
 +        incoming result rows, and is only called once for that
 +        instance's lifetime.
 +
 +        Note that during a result-row load, this method is called upon
 +        the first row received for this instance.  Note that some 
 +        attributes and collections may or may not be loaded or even 
 +        initialized, depending on what's present in the result rows.
 +
 +        The return value is only significant within the ``MapperExtension`` 
 +        chain; the parent mapper's behavior isn't modified by this method.
 +
 +        """
 +        return EXT_CONTINUE
 +
 +    def before_insert(self, mapper, connection, instance):
 +        """Receive an object instance before that instance is inserted
 +        into its table.
 +
 +        This is a good place to set up primary key values and such
 +        that aren't handled otherwise.
 +
 +        Column-based attributes can be modified within this method
 +        which will result in the new value being inserted.  However
 +        *no* changes to the overall flush plan can be made, and 
 +        manipulation of the ``Session`` will not have the desired effect.
 +        To manipulate the ``Session`` within an extension, use 
 +        ``SessionExtension``.
 +
 +        The return value is only significant within the ``MapperExtension`` 
 +        chain; the parent mapper's behavior isn't modified by this method.
 +
 +        """
 +
 +        return EXT_CONTINUE
 +
 +    def after_insert(self, mapper, connection, instance):
 +        """Receive an object instance after that instance is inserted.
 +
 +        The return value is only significant within the ``MapperExtension`` 
 +        chain; the parent mapper's behavior isn't modified by this method.
 +        
 +        """
 +
 +        return EXT_CONTINUE
 +
 +    def before_update(self, mapper, connection, instance):
 +        """Receive an object instance before that instance is updated.
 +
 +        Note that this method is called for all instances that are marked as
 +        "dirty", even those which have no net changes to their column-based
 +        attributes. An object is marked as dirty when any of its column-based
 +        attributes have a "set attribute" operation called or when any of its
 +        collections are modified. If, at update time, no column-based
 +        attributes have any net changes, no UPDATE statement will be issued.
 +        This means that an instance being sent to before_update is *not* a
 +        guarantee that an UPDATE statement will be issued (although you can
 +        affect the outcome here).
 +        
 +        To detect if the column-based attributes on the object have net
 +        changes, and will therefore generate an UPDATE statement, use
 +        ``object_session(instance).is_modified(instance,
 +        include_collections=False)``.
 +
 +        Column-based attributes can be modified within this method
 +        which will result in the new value being updated.  However
 +        *no* changes to the overall flush plan can be made, and 
 +        manipulation of the ``Session`` will not have the desired effect.
 +        To manipulate the ``Session`` within an extension, use 
 +        ``SessionExtension``.
 +
 +        The return value is only significant within the ``MapperExtension`` 
 +        chain; the parent mapper's behavior isn't modified by this method.
 +
 +        """
 +
 +        return EXT_CONTINUE
 +
 +    def after_update(self, mapper, connection, instance):
 +        """Receive an object instance after that instance is updated.
 +
 +        The return value is only significant within the ``MapperExtension`` 
 +        chain; the parent mapper's behavior isn't modified by this method.
 +        
 +        """
 +
 +        return EXT_CONTINUE
 +
 +    def before_delete(self, mapper, connection, instance):
 +        """Receive an object instance before that instance is deleted.
 +
 +        Note that *no* changes to the overall flush plan can be made
 +        here; and manipulation of the ``Session`` will not have the
 +        desired effect. To manipulate the ``Session`` within an
 +        extension, use ``SessionExtension``.
 +
 +        The return value is only significant within the ``MapperExtension`` 
 +        chain; the parent mapper's behavior isn't modified by this method.
 +
 +        """
 +
 +        return EXT_CONTINUE
 +
 +    def after_delete(self, mapper, connection, instance):
 +        """Receive an object instance after that instance is deleted.
 +
 +        The return value is only significant within the ``MapperExtension``
 +        chain; the parent mapper's behavior isn't modified by this method.
 +
 +        """
 +
 +        return EXT_CONTINUE
 +
 +class SessionExtension(object):
 +
 +    """Base implementation for :class:`.Session` event hooks.
 +    
 +    .. note:: :class:`.SessionExtension` is deprecated.   Please
 +       refer to :func:`.event.listen` as well as 
 +       :class:`.SessionEvents`.
 +    
 +    Subclasses may be installed into a :class:`.Session` (or
 +    :func:`.sessionmaker`) using the ``extension`` keyword
 +    argument::
 +    
 +        from sqlalchemy.orm.interfaces import SessionExtension
 +    
 +        class MySessionExtension(SessionExtension):
 +            def before_commit(self, session):
 +                print "before commit!"
 +
 +        Session = sessionmaker(extension=MySessionExtension())
 +
 +    The same :class:`.SessionExtension` instance can be used
 +    with any number of sessions.
 +
 +    """
 +
 +    @classmethod
 +    def _adapt_listener(cls, self, listener):
 +        event.listen(listener.before_commit, 'on_before_commit', self)
 +        event.listen(listener.after_commit, 'on_after_commit', self)
 +        event.listen(listener.after_rollback, 'on_after_rollback', self)
 +        event.listen(listener.before_flush, 'on_before_flush', self)
 +        event.listen(listener.after_flush, 'on_after_flush', self)
 +        event.listen(listener.after_flush_postexec, 'on_after_flush_postexec', self)
 +        event.listen(listener.after_begin, 'on_after_begin', self)
 +        event.listen(listener.after_attach, 'on_after_attach', self)
 +        event.listen(listener.after_bulk_update, 'on_after_bulk_update', self)
 +        event.listen(listener.after_bulk_delete, 'on_after_bulk_delete', self)
 +
 +    def before_commit(self, session):
 +        """Execute right before commit is called.
 +        
 +        Note that this may not be per-flush if a longer running
 +        transaction is ongoing."""
 +
 +    def after_commit(self, session):
 +        """Execute after a commit has occured.
 +        
 +        Note that this may not be per-flush if a longer running
 +        transaction is ongoing."""
 +
 +    def after_rollback(self, session):
 +        """Execute after a rollback has occured.
 +        
 +        Note that this may not be per-flush if a longer running
 +        transaction is ongoing."""
 +
 +    def before_flush( self, session, flush_context, instances):
 +        """Execute before flush process has started.
 +        
 +        `instances` is an optional list of objects which were passed to
 +        the ``flush()`` method. """
 +
 +    def after_flush(self, session, flush_context):
 +        """Execute after flush has completed, but before commit has been
 +        called.
 +        
 +        Note that the session's state is still in pre-flush, i.e. 'new',
 +        'dirty', and 'deleted' lists still show pre-flush state as well
 +        as the history settings on instance attributes."""
 +
 +    def after_flush_postexec(self, session, flush_context):
 +        """Execute after flush has completed, and after the post-exec
 +        state occurs.
 +        
 +        This will be when the 'new', 'dirty', and 'deleted' lists are in
 +        their final state.  An actual commit() may or may not have
 +        occured, depending on whether or not the flush started its own
 +        transaction or participated in a larger transaction. """
 +
 +    def after_begin( self, session, transaction, connection):
 +        """Execute after a transaction is begun on a connection
 +        
 +        `transaction` is the SessionTransaction. This method is called
 +        after an engine level transaction is begun on a connection. """
 +
 +    def after_attach(self, session, instance):
 +        """Execute after an instance is attached to a session.
 +        
 +        This is called after an add, delete or merge. """
 +
 +    def after_bulk_update( self, session, query, query_context, result):
 +        """Execute after a bulk update operation to the session.
 +        
 +        This is called after a session.query(...).update()
 +        
 +        `query` is the query object that this update operation was
 +        called on. `query_context` was the query context object.
 +        `result` is the result object returned from the bulk operation.
 +        """
 +
 +    def after_bulk_delete( self, session, query, query_context, result):
 +        """Execute after a bulk delete operation to the session.
 +        
 +        This is called after a session.query(...).delete()
 +        
 +        `query` is the query object that this delete operation was
 +        called on. `query_context` was the query context object.
 +        `result` is the result object returned from the bulk operation.
 +        """
 +
 +
 +class AttributeExtension(object):
 +    """Base implementation for :class:`.AttributeImpl` event hooks, events
 +    that fire upon attribute mutations in user code.
 +
 +    .. note:: :class:`.AttributeExtension` is deprecated.   Please
 +       refer to :func:`.event.listen` as well as 
 +       :class:`.AttributeEvents`.
 +    
 +    :class:`.AttributeExtension` is used to listen for set,
 +    remove, and append events on individual mapped attributes.
 +    It is established on an individual mapped attribute using
 +    the `extension` argument, available on
 +    :func:`.column_property`, :func:`.relationship`, and
 +    others::
 +
 +        from sqlalchemy.orm.interfaces import AttributeExtension
 +        from sqlalchemy.orm import mapper, relationship, column_property
 +    
 +        class MyAttrExt(AttributeExtension):
 +            def append(self, state, value, initiator):
 +                print "append event !"
 +                return value
 +        
 +            def set(self, state, value, oldvalue, initiator):
 +                print "set event !"
 +                return value
 +            
 +        mapper(SomeClass, sometable, properties={
 +            'foo':column_property(sometable.c.foo, extension=MyAttrExt()),
 +            'bar':relationship(Bar, extension=MyAttrExt())
 +        })
 +
 +    Note that the :class:`AttributeExtension` methods
 +    :meth:`~.AttributeExtension.append` and
 +    :meth:`~.AttributeExtension.set` need to return the
 +    ``value`` parameter. The returned value is used as the
 +    effective value, and allows the extension to change what is
 +    ultimately persisted.
 +    
 +    AttributeExtension is assembled within the descriptors associated
 +    with a mapped class.
 +    
 +    """
 +
 +    active_history = True
 +    """indicates that the set() method would like to receive the 'old' value,
 +    even if it means firing lazy callables.
++
++    Note that ``active_history`` can also be set directly via
++    :func:`.column_property` and :func:`.relationship`.
++    
 +    """
 +
 +    @classmethod
 +    def _adapt_listener(cls, self, listener):
 +        event.listen(listener.append, 'on_append', self,
 +                            active_history=listener.active_history,
 +                            raw=True, retval=True)
 +        event.listen(listener.remove, 'on_remove', self,
 +                            active_history=listener.active_history, 
 +                            raw=True, retval=True)
 +        event.listen(listener.set, 'on_set', self,
 +                            active_history=listener.active_history, 
 +                            raw=True, retval=True)
 +    
 +    def append(self, state, value, initiator):
 +        """Receive a collection append event.
 +
 +        The returned value will be used as the actual value to be
 +        appended.
 +
 +        """
 +        return value
 +
 +    def remove(self, state, value, initiator):
 +        """Receive a remove event.
 +
 +        No return value is defined.
 +
 +        """
 +        pass
 +
 +    def set(self, state, value, oldvalue, initiator):
 +        """Receive a set event.
 +
 +        The returned value will be used as the actual value to be
 +        set.
 +
 +        """
 +        return value
 +
 +
index fc8cab2ed734a8042d997c686e643c4c680b67e0,0000000000000000000000000000000000000000..b610408b74104e39b57fe07cee529fcc8b9fc633
mode 100644,000000..100644
--- /dev/null
@@@ -1,893 -1,0 +1,896 @@@
-       "on_set" event would like to receive the "old" value 
-       being replaced unconditionally, even if this requires
-       firing off database loads.
 +"""ORM event interfaces.
 +
 +"""
 +from sqlalchemy import event, exc
 +import inspect
 +
 +class InstrumentationEvents(event.Events):
 +    """Events related to class instrumentation events.
 +    
 +    The listeners here support being established against
 +    any new style class, that is any object that is a subclass
 +    of 'type'.  Events will then be fired off for events
 +    against that class as well as all subclasses.  
 +    'type' itself is also accepted as a target
 +    in which case the events fire for all classes.
 +    
 +    """
 +    
 +    @classmethod
 +    def accept_with(cls, target):
 +        from sqlalchemy.orm.instrumentation import instrumentation_registry
 +        
 +        if isinstance(target, type):
 +            return instrumentation_registry
 +        else:
 +            return None
 +
 +    @classmethod
 +    def listen(cls, fn, identifier, target, propagate=False):
 +        event.Events.listen(fn, identifier, target, propagate=propagate)
 +
 +    @classmethod
 +    def remove(cls, fn, identifier, target):
 +        raise NotImplementedError("Removal of instrumentation events not yet implemented")
 +
 +    def on_class_instrument(self, cls):
 +        """Called after the given class is instrumented.
 +        
 +        To get at the :class:`.ClassManager`, use
 +        :func:`.manager_of_class`.
 +        
 +        """
 +
 +    def on_class_uninstrument(self, cls):
 +        """Called before the given class is uninstrumented.
 +        
 +        To get at the :class:`.ClassManager`, use
 +        :func:`.manager_of_class`.
 +        
 +        """
 +        
 +        
 +    def on_attribute_instrument(self, cls, key, inst):
 +        """Called when an attribute is instrumented."""
 +
 +class InstanceEvents(event.Events):
 +    """Define events specific to object lifecycle.
 +    
 +    Instance-level don't automatically propagate their associations
 +    to subclasses.
 +    
 +    """
 +    @classmethod
 +    def accept_with(cls, target):
 +        from sqlalchemy.orm.instrumentation import ClassManager, manager_of_class
 +        from sqlalchemy.orm import Mapper, mapper
 +        
 +        if isinstance(target, ClassManager):
 +            return target
 +        elif isinstance(target, Mapper):
 +            return target.class_manager
 +        elif target is mapper:
 +            return ClassManager
 +        elif isinstance(target, type):
 +            if issubclass(target, Mapper):
 +                return ClassManager
 +            else:
 +                manager = manager_of_class(target)
 +                if manager:
 +                    return manager
 +        return None
 +    
 +    @classmethod
 +    def listen(cls, fn, identifier, target, raw=False, propagate=False):
 +        if not raw:
 +            orig_fn = fn
 +            def wrap(state, *arg, **kw):
 +                return orig_fn(state.obj(), *arg, **kw)
 +            fn = wrap
 +
 +        event.Events.listen(fn, identifier, target, propagate=propagate)
 +        if propagate:
 +            for mgr in target.subclass_managers(True):
 +                event.Events.listen(fn, identifier, mgr, True)
 +            
 +    @classmethod
 +    def remove(cls, fn, identifier, target):
 +        raise NotImplementedError("Removal of instance events not yet implemented")
 +        
 +    def on_init(self, target, args, kwargs):
 +        """Receive an instance when it's constructor is called.
 +        
 +        This method is only called during a userland construction of 
 +        an object.  It is not called when an object is loaded from the
 +        database.
 +
 +        """
 +        
 +    def on_init_failure(self, target, args, kwargs):
 +        """Receive an instance when it's constructor has been called, 
 +        and raised an exception.
 +        
 +        This method is only called during a userland construction of 
 +        an object.  It is not called when an object is loaded from the
 +        database.
 +
 +        """
 +    
 +    def on_load(self, target):
 +        """Receive an object instance after it has been created via
 +        ``__new__``, and after initial attribute population has
 +        occurred.
 +
 +        This typically occurs when the instance is created based on
 +        incoming result rows, and is only called once for that
 +        instance's lifetime.
 +
 +        Note that during a result-row load, this method is called upon
 +        the first row received for this instance.  Note that some 
 +        attributes and collections may or may not be loaded or even 
 +        initialized, depending on what's present in the result rows.
 +
 +        """
 +    
 +    def on_resurrect(self, target):
 +        """Receive an object instance as it is 'resurrected' from 
 +        garbage collection, which occurs when a "dirty" state falls
 +        out of scope."""
 +
 +        
 +class MapperEvents(event.Events):
 +    """Define events specific to mappings.
 +
 +    e.g.::
 +    
 +        from sqlalchemy import event
 +
 +        def my_before_insert_listener(mapper, connection, target):
 +            # execute a stored procedure upon INSERT,
 +            # apply the value to the row to be inserted
 +            target.calculated_value = connection.scalar(
 +                                        "select my_special_function(%d)" 
 +                                        % target.special_number)
 +        
 +        # associate the listener function with SomeMappedClass,
 +        # to execute during the "on_before_insert" hook
 +        event.listen(my_before_insert_listener, 'on_before_insert', SomeMappedClass)
 +
 +    Available targets include mapped classes, instances of
 +    :class:`.Mapper` (i.e. returned by :func:`.mapper`,
 +    :func:`.class_mapper` and similar), as well as the
 +    :class:`.Mapper` class and :func:`.mapper` function itself
 +    for global event reception::
 +
 +        from sqlalchemy.orm import mapper
 +        
 +        def some_listener(mapper, connection, target):
 +            log.debug("Instance %s being inserted" % target)
 +            
 +        # attach to all mappers
 +        event.listen(some_listener, 'on_before_insert', mapper)
 +    
 +    Mapper events provide hooks into critical sections of the
 +    mapper, including those related to object instrumentation,
 +    object loading, and object persistence. In particular, the
 +    persistence methods :meth:`~.MapperEvents.on_before_insert`,
 +    and :meth:`~.MapperEvents.on_before_update` are popular
 +    places to augment the state being persisted - however, these
 +    methods operate with several significant restrictions. The
 +    user is encouraged to evaluate the
 +    :meth:`.SessionEvents.on_before_flush` and
 +    :meth:`.SessionEvents.on_after_flush` methods as more
 +    flexible and user-friendly hooks in which to apply
 +    additional database state during a flush.
 +    
 +    When using :class:`.MapperEvents`, several modifiers are
 +    available to the :func:`.event.listen` function.
 +    
 +    :param propagate=False: When True, the event listener should 
 +       be applied to all inheriting mappers as well as the 
 +       mapper which is the target of this listener.
 +    :param raw=False: When True, the "target" argument passed
 +       to applicable event listener functions will be the 
 +       instance's :class:`.InstanceState` management
 +       object, rather than the mapped instance itself.
 +    :param retval=False: when True, the user-defined event function
 +       must have a return value, the purpose of which is either to
 +       control subsequent event propagation, or to otherwise alter 
 +       the operation in progress by the mapper.   Possible return
 +       values are:
 +      
 +       * ``sqlalchemy.orm.interfaces.EXT_CONTINUE`` - continue event
 +         processing normally.
 +       * ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent
 +         event handlers in the chain.
 +       * other values - the return value specified by specific listeners,
 +         such as :meth:`~.MapperEvents.on_translate_row` or 
 +         :meth:`~.MapperEvents.on_create_instance`.
 +     
 +    """
 +
 +    @classmethod
 +    def accept_with(cls, target):
 +        from sqlalchemy.orm import mapper, class_mapper, Mapper
 +        if target is mapper:
 +            return Mapper
 +        elif isinstance(target, type):
 +            if issubclass(target, Mapper):
 +                return target
 +            else:
 +                return class_mapper(target)
 +        else:
 +            return target
 +        
 +    @classmethod
 +    def listen(cls, fn, identifier, target, 
 +                            raw=False, retval=False, propagate=False):
 +        from sqlalchemy.orm.interfaces import EXT_CONTINUE
 +
 +        if not raw or not retval:
 +            if not raw:
 +                meth = getattr(cls, identifier)
 +                try:
 +                    target_index = inspect.getargspec(meth)[0].index('target') - 1
 +                except ValueError:
 +                    target_index = None
 +            
 +            wrapped_fn = fn
 +            def wrap(*arg, **kw):
 +                if not raw and target_index is not None:
 +                    arg = list(arg)
 +                    arg[target_index] = arg[target_index].obj()
 +                if not retval:
 +                    wrapped_fn(*arg, **kw)
 +                    return EXT_CONTINUE
 +                else:
 +                    return wrapped_fn(*arg, **kw)
 +            fn = wrap
 +        
 +        if propagate:
 +            for mapper in target.self_and_descendants:
 +                event.Events.listen(fn, identifier, mapper, propagate=True)
 +        else:
 +            event.Events.listen(fn, identifier, target)
 +        
 +    def on_instrument_class(self, mapper, class_):
 +        """Receive a class when the mapper is first constructed, and has
 +        applied instrumentation to the mapped class.
 +        
 +        This listener can generally only be applied to the :class:`.Mapper`
 +        class overall.
 +        
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param class\_: the mapped class.
 +        
 +        """
 +
 +    def on_translate_row(self, mapper, context, row):
 +        """Perform pre-processing on the given result row and return a
 +        new row instance.
 +
 +        This listener is typically registered with ``retval=True``.
 +        It is called when the mapper first receives a row, before
 +        the object identity or the instance itself has been derived
 +        from that row.   The given row may or may not be a 
 +        :class:`.RowProxy` object - it will always be a dictionary-like
 +        object which contains mapped columns as keys.  The 
 +        returned object should also be a dictionary-like object
 +        which recognizes mapped columns as keys.
 +        
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param context: the :class:`.QueryContext`, which includes
 +         a handle to the current :class:`.Query` in progress as well
 +         as additional state information.
 +        :param row: the result row being handled.  This may be 
 +         an actual :class:`.RowProxy` or may be a dictionary containing
 +         :class:`.Column` objects as keys.
 +        :return: When configured with ``retval=True``, the function
 +         should return a dictionary-like row object, or ``EXT_CONTINUE``,
 +         indicating the original row should be used.
 +         
 +        
 +        """
 +
 +    def on_create_instance(self, mapper, context, row, class_):
 +        """Receive a row when a new object instance is about to be
 +        created from that row.
 +
 +        The method can choose to create the instance itself, or it can return
 +        EXT_CONTINUE to indicate normal object creation should take place.
 +        This listener is typically registered with ``retval=True``.
 +
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param context: the :class:`.QueryContext`, which includes
 +         a handle to the current :class:`.Query` in progress as well
 +         as additional state information.
 +        :param row: the result row being handled.  This may be 
 +         an actual :class:`.RowProxy` or may be a dictionary containing
 +         :class:`.Column` objects as keys.
 +        :param class\_: the mapped class.
 +        :return: When configured with ``retval=True``, the return value
 +         should be a newly created instance of the mapped class, 
 +         or ``EXT_CONTINUE`` indicating that default object construction
 +         should take place.
 +
 +        """
 +
 +    def on_append_result(self, mapper, context, row, target, 
 +                        result, **flags):
 +        """Receive an object instance before that instance is appended
 +        to a result list.
 +        
 +        This is a rarely used hook which can be used to alter
 +        the construction of a result list returned by :class:`.Query`.
 +        
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param context: the :class:`.QueryContext`, which includes
 +         a handle to the current :class:`.Query` in progress as well
 +         as additional state information.
 +        :param row: the result row being handled.  This may be 
 +         an actual :class:`.RowProxy` or may be a dictionary containing
 +         :class:`.Column` objects as keys.
 +        :param target: the mapped instance being populated.  If 
 +         the event is configured with ``raw=True``, this will 
 +         instead be the :class:`.InstanceState` state-management
 +         object associated with the instance.
 +        :param result: a list-like object where results are being
 +         appended.
 +        :param \**flags: Additional state information about the 
 +         current handling of the row.
 +        :return: If this method is registered with ``retval=True``,
 +         a return value of ``EXT_STOP`` will prevent the instance
 +         from being appended to the given result list, whereas a 
 +         return value of ``EXT_CONTINUE`` will result in the default
 +         behavior of appending the value to the result list.
 +
 +        """
 +
 +
 +    def on_populate_instance(self, mapper, context, row, 
 +                            target, **flags):
 +        """Receive an instance before that instance has
 +        its attributes populated.
 +
 +        This usually corresponds to a newly loaded instance but may
 +        also correspond to an already-loaded instance which has
 +        unloaded attributes to be populated.  The method may be called
 +        many times for a single instance, as multiple result rows are
 +        used to populate eagerly loaded collections.
 +        
 +        Most usages of this hook are obsolete.  For a
 +        generic "object has been newly created from a row" hook, use
 +        :meth:`.InstanceEvents.on_load`.
 +
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param context: the :class:`.QueryContext`, which includes
 +         a handle to the current :class:`.Query` in progress as well
 +         as additional state information.
 +        :param row: the result row being handled.  This may be 
 +         an actual :class:`.RowProxy` or may be a dictionary containing
 +         :class:`.Column` objects as keys.
 +        :param class\_: the mapped class.
 +        :return: When configured with ``retval=True``, a return
 +         value of ``EXT_STOP`` will bypass instance population by
 +         the mapper. A value of ``EXT_CONTINUE`` indicates that
 +         default instance population should take place.
 +
 +        """
 +
 +    def on_before_insert(self, mapper, connection, target):
 +        """Receive an object instance before an INSERT statement
 +        is emitted corresponding to that instance.
 +        
 +        This event is used to modify local, non-object related 
 +        attributes on the instance before an INSERT occurs, as well
 +        as to emit additional SQL statements on the given 
 +        connection.   
 +        
 +        The event is often called for a batch of objects of the
 +        same class before their INSERT statements are emitted at
 +        once in a later step. In the extremely rare case that
 +        this is not desirable, the :func:`.mapper` can be
 +        configured with ``batch=False``, which will cause
 +        batches of instances to be broken up into individual
 +        (and more poorly performing) event->persist->event
 +        steps.
 +        
 +        Handlers should **not** modify any attributes which are
 +        mapped by :func:`.relationship`, nor should they attempt
 +        to make any modifications to the :class:`.Session` in
 +        this hook (including :meth:`.Session.add`, 
 +        :meth:`.Session.delete`, etc.) - such changes will not
 +        take effect. For overall changes to the "flush plan",
 +        use :meth:`.SessionEvents.before_flush`.
 +
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param connection: the :class:`.Connection` being used to 
 +         emit INSERT statements for this instance.  This
 +         provides a handle into the current transaction on the 
 +         target database specific to this instance.
 +        :param target: the mapped instance being persisted.  If 
 +         the event is configured with ``raw=True``, this will 
 +         instead be the :class:`.InstanceState` state-management
 +         object associated with the instance.
 +        :return: No return value is supported by this event.
 +
 +        """
 +
 +    def on_after_insert(self, mapper, connection, target):
 +        """Receive an object instance after an INSERT statement
 +        is emitted corresponding to that instance.
 +        
 +        This event is used to modify in-Python-only
 +        state on the instance after an INSERT occurs, as well
 +        as to emit additional SQL statements on the given 
 +        connection.   
 +
 +        The event is often called for a batch of objects of the
 +        same class after their INSERT statements have been
 +        emitted at once in a previous step. In the extremely
 +        rare case that this is not desirable, the
 +        :func:`.mapper` can be configured with ``batch=False``,
 +        which will cause batches of instances to be broken up
 +        into individual (and more poorly performing)
 +        event->persist->event steps.
 +
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param connection: the :class:`.Connection` being used to 
 +         emit INSERT statements for this instance.  This
 +         provides a handle into the current transaction on the 
 +         target database specific to this instance.
 +        :param target: the mapped instance being persisted.  If 
 +         the event is configured with ``raw=True``, this will 
 +         instead be the :class:`.InstanceState` state-management
 +         object associated with the instance.
 +        :return: No return value is supported by this event.
 +        
 +        """
 +
 +    def on_before_update(self, mapper, connection, target):
 +        """Receive an object instance before an UPDATE statement
 +        is emitted corresponding to that instance.
 +
 +        This event is used to modify local, non-object related 
 +        attributes on the instance before an UPDATE occurs, as well
 +        as to emit additional SQL statements on the given 
 +        connection.   
 +
 +        This method is called for all instances that are
 +        marked as "dirty", *even those which have no net changes
 +        to their column-based attributes*. An object is marked
 +        as dirty when any of its column-based attributes have a
 +        "set attribute" operation called or when any of its
 +        collections are modified. If, at update time, no
 +        column-based attributes have any net changes, no UPDATE
 +        statement will be issued. This means that an instance
 +        being sent to :meth:`~.MapperEvents.on_before_update` is
 +        *not* a guarantee that an UPDATE statement will be
 +        issued, although you can affect the outcome here by
 +        modifying attributes so that a net change in value does
 +        exist.
 +        
 +        To detect if the column-based attributes on the object have net
 +        changes, and will therefore generate an UPDATE statement, use
 +        ``object_session(instance).is_modified(instance,
 +        include_collections=False)``.
 +
 +        The event is often called for a batch of objects of the
 +        same class before their UPDATE statements are emitted at
 +        once in a later step. In the extremely rare case that
 +        this is not desirable, the :func:`.mapper` can be
 +        configured with ``batch=False``, which will cause
 +        batches of instances to be broken up into individual
 +        (and more poorly performing) event->persist->event
 +        steps.
 +        
 +        Handlers should **not** modify any attributes which are
 +        mapped by :func:`.relationship`, nor should they attempt
 +        to make any modifications to the :class:`.Session` in
 +        this hook (including :meth:`.Session.add`, 
 +        :meth:`.Session.delete`, etc.) - such changes will not
 +        take effect. For overall changes to the "flush plan",
 +        use :meth:`.SessionEvents.before_flush`.
 +
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param connection: the :class:`.Connection` being used to 
 +         emit UPDATE statements for this instance.  This
 +         provides a handle into the current transaction on the 
 +         target database specific to this instance.
 +        :param target: the mapped instance being persisted.  If 
 +         the event is configured with ``raw=True``, this will 
 +         instead be the :class:`.InstanceState` state-management
 +         object associated with the instance.
 +        :return: No return value is supported by this event.
 +        """
 +
 +    def on_after_update(self, mapper, connection, target):
 +        """Receive an object instance after an UPDATE statement
 +        is emitted corresponding to that instance.
 +
 +        This event is used to modify in-Python-only
 +        state on the instance after an UPDATE occurs, as well
 +        as to emit additional SQL statements on the given 
 +        connection.   
 +
 +        This method is called for all instances that are
 +        marked as "dirty", *even those which have no net changes
 +        to their column-based attributes*, and for which 
 +        no UPDATE statement has proceeded. An object is marked
 +        as dirty when any of its column-based attributes have a
 +        "set attribute" operation called or when any of its
 +        collections are modified. If, at update time, no
 +        column-based attributes have any net changes, no UPDATE
 +        statement will be issued. This means that an instance
 +        being sent to :meth:`~.MapperEvents.on_after_update` is
 +        *not* a guarantee that an UPDATE statement has been
 +        issued.
 +        
 +        To detect if the column-based attributes on the object have net
 +        changes, and therefore resulted in an UPDATE statement, use
 +        ``object_session(instance).is_modified(instance,
 +        include_collections=False)``.
 +
 +        The event is often called for a batch of objects of the
 +        same class after their UPDATE statements have been emitted at
 +        once in a previous step. In the extremely rare case that
 +        this is not desirable, the :func:`.mapper` can be
 +        configured with ``batch=False``, which will cause
 +        batches of instances to be broken up into individual
 +        (and more poorly performing) event->persist->event
 +        steps.
 +        
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param connection: the :class:`.Connection` being used to 
 +         emit UPDATE statements for this instance.  This
 +         provides a handle into the current transaction on the 
 +         target database specific to this instance.
 +        :param target: the mapped instance being persisted.  If 
 +         the event is configured with ``raw=True``, this will 
 +         instead be the :class:`.InstanceState` state-management
 +         object associated with the instance.
 +        :return: No return value is supported by this event.
 +        
 +        """
 +
 +    def on_before_delete(self, mapper, connection, target):
 +        """Receive an object instance before a DELETE statement
 +        is emitted corresponding to that instance.
 +        
 +        This event is used to emit additional SQL statements on 
 +        the given connection as well as to perform application
 +        specific bookkeeping related to a deletion event.
 +        
 +        The event is often called for a batch of objects of the
 +        same class before their DELETE statements are emitted at
 +        once in a later step. 
 +        
 +        Handlers should **not** modify any attributes which are
 +        mapped by :func:`.relationship`, nor should they attempt
 +        to make any modifications to the :class:`.Session` in
 +        this hook (including :meth:`.Session.add`, 
 +        :meth:`.Session.delete`, etc.) - such changes will not
 +        take effect. For overall changes to the "flush plan",
 +        use :meth:`.SessionEvents.before_flush`.
 +
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param connection: the :class:`.Connection` being used to 
 +         emit DELETE statements for this instance.  This
 +         provides a handle into the current transaction on the 
 +         target database specific to this instance.
 +        :param target: the mapped instance being deleted.  If 
 +         the event is configured with ``raw=True``, this will 
 +         instead be the :class:`.InstanceState` state-management
 +         object associated with the instance.
 +        :return: No return value is supported by this event.
 +        
 +        """
 +
 +    def on_after_delete(self, mapper, connection, target):
 +        """Receive an object instance after a DELETE statement
 +        has been emitted corresponding to that instance.
 +        
 +        This event is used to emit additional SQL statements on 
 +        the given connection as well as to perform application
 +        specific bookkeeping related to a deletion event.
 +        
 +        The event is often called for a batch of objects of the
 +        same class after their DELETE statements have been emitted at
 +        once in a previous step. 
 +
 +        :param mapper: the :class:`.Mapper` which is the target
 +         of this event.
 +        :param connection: the :class:`.Connection` being used to 
 +         emit DELETE statements for this instance.  This
 +         provides a handle into the current transaction on the 
 +         target database specific to this instance.
 +        :param target: the mapped instance being deleted.  If 
 +         the event is configured with ``raw=True``, this will 
 +         instead be the :class:`.InstanceState` state-management
 +         object associated with the instance.
 +        :return: No return value is supported by this event.
 +        
 +        """
 +
 +    @classmethod
 +    def remove(cls, fn, identifier, target):
 +        raise NotImplementedError("Removal of mapper events not yet implemented")
 +    
 +class SessionEvents(event.Events):
 +    """Define events specific to :class:`.Session` lifecycle.
 +    
 +    e.g.::
 +    
 +        from sqlalchemy import event
 +        from sqlalchemy.orm import sessionmaker
 +        
 +        class my_before_commit(session):
 +            print "before commit!"
 +        
 +        Session = sessionmaker()
 +        
 +        event.listen(my_before_commit, "on_before_commit", Session)
 +    
 +    The :func:`~.event.listen` function will accept
 +    :class:`.Session` objects as well as the return result
 +    of :func:`.sessionmaker` and :func:`.scoped_session`.
 +    
 +    Additionally, it accepts the :class:`.Session` class which
 +    will apply listeners to all :class:`.Session` instances
 +    globally.
 +        
 +    """
 +
 +    @classmethod
 +    def accept_with(cls, target):
 +        from sqlalchemy.orm import ScopedSession, Session
 +        if isinstance(target, ScopedSession):
 +            if not isinstance(target.session_factory, type) or \
 +                not issubclass(target.session_factory, Session):
 +                raise exc.ArgumentError(
 +                            "Session event listen on a ScopedSession "
 +                            "requries that its creation callable "
 +                            "is a Session subclass.")
 +            return target.session_factory
 +        elif isinstance(target, type):
 +            if issubclass(target, ScopedSession):
 +                return Session
 +            elif issubclass(target, Session):
 +                return target
 +        elif isinstance(target, Session):
 +            return target
 +        else:
 +            return None
 +        
 +    @classmethod
 +    def remove(cls, fn, identifier, target):
 +        raise NotImplementedError("Removal of session events not yet implemented")
 +
 +    def on_before_commit(self, session):
 +        """Execute before commit is called.
 +        
 +        Note that this may not be per-flush if a longer running
 +        transaction is ongoing."""
 +
 +    def on_after_commit(self, session):
 +        """Execute after a commit has occured.
 +        
 +        Note that this may not be per-flush if a longer running
 +        transaction is ongoing."""
 +
 +    def on_after_rollback(self, session):
 +        """Execute after a rollback has occured.
 +        
 +        Note that this may not be per-flush if a longer running
 +        transaction is ongoing."""
 +
 +    def on_before_flush( self, session, flush_context, instances):
 +        """Execute before flush process has started.
 +        
 +        `instances` is an optional list of objects which were passed to
 +        the ``flush()`` method. """
 +
 +    def on_after_flush(self, session, flush_context):
 +        """Execute after flush has completed, but before commit has been
 +        called.
 +        
 +        Note that the session's state is still in pre-flush, i.e. 'new',
 +        'dirty', and 'deleted' lists still show pre-flush state as well
 +        as the history settings on instance attributes."""
 +
 +    def on_after_flush_postexec(self, session, flush_context):
 +        """Execute after flush has completed, and after the post-exec
 +        state occurs.
 +        
 +        This will be when the 'new', 'dirty', and 'deleted' lists are in
 +        their final state.  An actual commit() may or may not have
 +        occured, depending on whether or not the flush started its own
 +        transaction or participated in a larger transaction. """
 +
 +    def on_after_begin( self, session, transaction, connection):
 +        """Execute after a transaction is begun on a connection
 +        
 +        `transaction` is the SessionTransaction. This method is called
 +        after an engine level transaction is begun on a connection. """
 +
 +    def on_after_attach(self, session, instance):
 +        """Execute after an instance is attached to a session.
 +        
 +        This is called after an add, delete or merge. """
 +
 +    def on_after_bulk_update( self, session, query, query_context, result):
 +        """Execute after a bulk update operation to the session.
 +        
 +        This is called after a session.query(...).update()
 +        
 +        `query` is the query object that this update operation was
 +        called on. `query_context` was the query context object.
 +        `result` is the result object returned from the bulk operation.
 +        """
 +
 +    def on_after_bulk_delete( self, session, query, query_context, result):
 +        """Execute after a bulk delete operation to the session.
 +        
 +        This is called after a session.query(...).delete()
 +        
 +        `query` is the query object that this delete operation was
 +        called on. `query_context` was the query context object.
 +        `result` is the result object returned from the bulk operation.
 +        """
 +
 +
 +class AttributeEvents(event.Events):
 +    """Define events for object attributes.
 +    
 +    These are typically defined on the class-bound descriptor for the
 +    target class.
 +
 +    e.g.::
 +    
 +        from sqlalchemy import event
 +        
 +        def my_append_listener(target, value, initiator):
 +            print "received append event for target: %s" % target
 +        
 +        event.listen(my_append_listener, 'on_append', MyClass.collection)
 +    
 +    Listeners have the option to return a possibly modified version
 +    of the value, when the ``retval=True`` flag is passed
 +    to :func:`~.event.listen`::
 +    
 +        def validate_phone(target, value, oldvalue, initiator):
 +            "Strip non-numeric characters from a phone number"
 +        
 +            return re.sub(r'(?![0-9])', '', value)
 +        
 +        # setup listener on UserContact.phone attribute, instructing
 +        # it to use the return value
 +        listen(validate_phone, 'on_set', UserContact.phone, retval=True)
 +    
 +    A validation function like the above can also raise an exception
 +    such as :class:`ValueError` to halt the operation.
 +        
 +    Several modifiers are available to the :func:`~.event.listen` function.
 +    
 +    :param active_history=False: When True, indicates that the
++      "on_set" event would like to receive the "old" value being
++      replaced unconditionally, even if this requires firing off
++      database loads. Note that ``active_history`` can also be
++      set directly via :func:`.column_property` and
++      :func:`.relationship`.
++
 +    :param propagate=False: When True, the listener function will
 +      be established not just for the class attribute given, but
 +      for attributes of the same name on all current subclasses 
 +      of that class, as well as all future subclasses of that 
 +      class, using an additional listener that listens for 
 +      instrumentation events.
 +    :param raw=False: When True, the "target" argument to the
 +      event will be the :class:`.InstanceState` management
 +      object, rather than the mapped instance itself.
 +    :param retval=False: when True, the user-defined event 
 +      listening must return the "value" argument from the 
 +      function.  This gives the listening function the opportunity
 +      to change the value that is ultimately used for a "set"
 +      or "append" event.   
 +    
 +    """
 +    
 +    @classmethod
 +    def listen(cls, fn, identifier, target, active_history=False, 
 +                                        raw=False, retval=False,
 +                                        propagate=False):
 +        if active_history:
 +            target.dispatch.active_history = True
 +        
 +        # TODO: for removal, need to package the identity
 +        # of the wrapper with the original function.
 +        
 +        if not raw or not retval:
 +            orig_fn = fn
 +            def wrap(target, value, *arg):
 +                if not raw:
 +                    target = target.obj()
 +                if not retval:
 +                    orig_fn(target, value, *arg)
 +                    return value
 +                else:
 +                    return orig_fn(target, value, *arg)
 +            fn = wrap
 +            
 +        event.Events.listen(fn, identifier, target, propagate)
 +        
 +        if propagate:
 +            from sqlalchemy.orm.instrumentation import manager_of_class
 +            
 +            manager = manager_of_class(target.class_)
 +            
 +            for mgr in manager.subclass_managers(True):
 +                event.Events.listen(fn, identifier, mgr[target.key], True)
 +        
 +    @classmethod
 +    def remove(cls, fn, identifier, target):
 +        raise NotImplementedError("Removal of attribute events not yet implemented")
 +        
 +    def on_append(self, target, value, initiator):
 +        """Receive a collection append event.
 +
 +        :param target: the object instance receiving the event.
 +          If the listener is registered with ``raw=True``, this will
 +          be the :class:`.InstanceState` object.
 +        :param value: the value being appended.  If this listener
 +          is registered with ``retval=True``, the listener
 +          function must return this value, or a new value which 
 +          replaces it.
 +        :param initiator: the attribute implementation object 
 +          which initiated this event.
 +        :return: if the event was registered with ``retval=True``,
 +         the given value, or a new effective value, should be returned.
 +         
 +        """
 +
 +    def on_remove(self, target, value, initiator):
 +        """Receive a collection remove event.
 +
 +        :param target: the object instance receiving the event.
 +          If the listener is registered with ``raw=True``, this will
 +          be the :class:`.InstanceState` object.
 +        :param value: the value being removed.
 +        :param initiator: the attribute implementation object 
 +          which initiated this event.
 +        :return: No return value is defined for this event.
 +        """
 +
 +    def on_set(self, target, value, oldvalue, initiator):
 +        """Receive a scalar set event.
 +
 +        :param target: the object instance receiving the event.
 +          If the listener is registered with ``raw=True``, this will
 +          be the :class:`.InstanceState` object.
 +        :param value: the value being set.  If this listener
 +          is registered with ``retval=True``, the listener
 +          function must return this value, or a new value which 
 +          replaces it.
 +        :param oldvalue: the previous value being replaced.  This
 +          may also be the symbol ``NEVER_SET`` or ``NO_VALUE``.
 +          If the listener is registered with ``active_history=True``,
 +          the previous value of the attribute will be loaded from
 +          the database if the existing value is currently unloaded 
 +          or expired.
 +        :param initiator: the attribute implementation object 
 +          which initiated this event.
 +        :return: if the event was registered with ``retval=True``,
 +         the given value, or a new effective value, should be returned.
 +
 +        """
 +
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge