]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add new documentation that applies to all modern SQLA versions,
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 28 Aug 2015 20:06:03 +0000 (16:06 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 2 Sep 2015 21:55:48 +0000 (17:55 -0400)
detailing ORM event recipes across the board.

(cherry picked from commit 8be93c23ee566de7cefd7d1b8ef044324132a70f)

doc/build/core/events.rst
doc/build/orm/events.rst
doc/build/orm/examples.rst
doc/build/orm/session.rst
doc/build/orm/session_events.rst [new file with mode: 0644]
doc/build/orm/session_state_management.rst
doc/build/orm/session_transaction.rst
lib/sqlalchemy/orm/events.py

index d19b910b1c0b3efd7be87a9421912ace135553f1..451cb9460c50bd041c598ae31fe3157e61b634de 100644 (file)
@@ -11,10 +11,6 @@ ORM events are described in :ref:`orm_event_toplevel`.
 .. autoclass:: sqlalchemy.event.base.Events
    :members:
 
-.. versionadded:: 0.7
-    The event system supersedes the previous system of "extension", "listener",
-    and "proxy" classes.
-
 Connection Pool Events
 -----------------------
 
index e9673bed0e5f9cc9fd73948063d2df44660ce413..470a9386bb8697e115eb6c2d5b2f9b863062d447 100644 (file)
@@ -5,12 +5,10 @@ ORM Events
 
 The ORM includes a wide variety of hooks available for subscription.
 
-.. versionadded:: 0.7
-    The event supersedes the previous system of "extension" classes.
-
-For an introduction to the event API, see :ref:`event_toplevel`.  Non-ORM events
-such as those regarding connections and low-level statement execution are described in
-:ref:`core_event_toplevel`.
+For an introduction to the most commonly used ORM events, see the section
+:ref:`session_events_toplevel`.   The event system in general is discussed
+at :ref:`event_toplevel`.  Non-ORM events such as those regarding connections
+and low-level statement execution are described in :ref:`core_event_toplevel`.
 
 Attribute Events
 ----------------
index 4db7c00dc694b2c566ce75b2c7dc6f03a6359aca..25d2430229334eff3f7b745338781aeb53a094e0 100644 (file)
@@ -93,6 +93,8 @@ Versioning with a History Table
 
 .. automodule:: examples.versioned_history
 
+.. _examples_versioned_rows:
+
 Versioning using Temporal Rows
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
index 624ee9f75fab781df4a6e4c671ce089fc79c964a..79ea70137710767c30c05952c8c6fd53221a4ef0 100644 (file)
@@ -20,6 +20,7 @@ configured, the primary usage interface for persistence operations is the
     session_transaction
     persistence_techniques
     contextual
+    session_events
     session_api
 
 
diff --git a/doc/build/orm/session_events.rst b/doc/build/orm/session_events.rst
new file mode 100644 (file)
index 0000000..231311a
--- /dev/null
@@ -0,0 +1,218 @@
+.. _session_events_toplevel:
+
+Tracking Object and Session Changes with Events
+===============================================
+
+SQLAlchemy features an extensive :ref:`Event Listening <event_toplevel>`
+system used throughout the Core and ORM.   Within the ORM, there are a
+wide variety of event listener hooks, which are documented at an API
+level at :ref:`orm_event_toplevel`.   This collection of events has
+grown over the years to include lots of very useful new events as well
+as some older events that aren't as relevant as they once were.  This
+section will attempt to introduce the major event hooks and when they
+might be used.
+
+.. _session_persistence_events:
+
+Persistence Events
+------------------
+
+Probably the most widely used series of events are the "persistence" events,
+which correspond to the :ref:`flush process<session_flushing>`.
+The flush is where all the decisions are made about pending changes to
+objects and are then emitted out to the database in the form of INSERT,
+UPDATE, and DELETE staetments.
+
+``before_flush()``
+^^^^^^^^^^^^^^^^^^
+
+The :meth:`.SessionEvents.before_flush` hook is by far the most generally
+useful event to use when an application wants to ensure that
+additional persistence changes to the database are made when a flush proceeds.
+Use :meth:`.SessionEvents.before_flush` in order to operate
+upon objects to validate their state as well as to compose additional objects
+and references before they are persisted.   Within this event,
+it is **safe to manipulate the Session's state**, that is, new objects
+can be attached to it, objects can be deleted, and indivual attributes
+on objects can be changed freely, and these changes will be pulled into
+the flush process when the event hook completes.
+
+The typical :meth:`.SessionEvents.before_flush` hook will be tasked with
+scanning the collections :attr:`.Session.new`, :attr:`.Session.dirty` and
+:attr:`.Session.deleted` in order to look for objects
+where something will be happening.
+
+For illustrations of :meth:`.SessionEvents.before_flush, see
+examples such as :ref:`examples_versioned_history` and
+:ref:`examples_versioned_rows`.
+
+``after_flush()``
+^^^^^^^^^^^^^^^^^
+
+The :meth:`.SessionEvents.after_flush` hook is called after the SQL has been
+emitted for a flush process, but **before* the state of the objects that
+were flushed has been altered.  That is, you can still inspect
+the :attr:`.Session.new`, :attr:`.Session.dirty` and
+:attr:`.Session.deleted` collections to see what was just flushed, and
+you can also use history tracking features like the ones provided
+by :class:`.AttributeState` to see what changes were just persisted.
+In the :meth:`.SessionEvents.after_flush` event, additional SQL can be emitted
+to the database based on what's observed to have changed.
+
+``after_flush_postexec()``
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+:meth:`.SessionEvents.after_flush_postexec` is called soon after
+:meth:`.SessionEvents.after_flush`, but is invoked **after** the state of
+the objects has been modified to account for the flush that just took place.
+The :attr:`.Session.new`, :attr:`.Session.dirty` and
+:attr:`.Session.deleted` collections are normally completely empty here.
+Use :meth:`.SessionEvents.after_flush_postexec` to inspect the identity map
+for finalized objects and possibly emit additional SQL.   In this hook,
+there is the ability to make new changes on objects, which means the
+:class:`.Session` will again go into a "dirty" state; the mechanics of the
+:class:`.Session` here will cause it to flush **again** if new changes
+are detected in this hook.  A counter ensures that an endless loop in this
+regard is stopped after 100 iterations, in the case that an
+:meth:`.SessionEvents.after_flush_postexec`
+hook continually adds new state to be flushed each time it is called.
+
+.. _session_persistence_mapper:
+
+Mapper-level Events
+^^^^^^^^^^^^^^^^^^^
+
+In addition to the flush-level hooks, there is also a suite of hooks
+that are more fine-grained, in that they are called on a per-object
+basis and are broken out based on INSERT, UPDATE or DELETE.   These
+are the mapper persistence hooks, and they too are very popular,
+however these events need to be approached more cautiously, as they
+proceed within the context of the flush process that is already
+ongoing; many operations are not safe to proceed here.
+
+The events are:
+
+* :meth:`.MapperEvents.before_insert`
+* :meth:`.MapperEvents.after_insert`
+* :meth:`.MapperEvents.before_update`
+* :meth:`.MapperEvents.after_update`
+* :meth:`.MapperEvents.before_delete`
+* :meth:`.MapperEvents.after_delete`
+
+Each event is passed the :class:`.Mapper`,
+the mapped object itself, and the :class:`.Connection` which is being
+used to emit an INSERT, UPDATE or DELETE statement.     The appeal of these
+events is clear, in that if an application wants to tie some activity to
+when a specific type of object is persisted with an INSERT, the hook is
+very specific; unlike the :meth:`.SessionEvents.before_flush` event,
+there's no need to search through collections like :attr:`.Session.new`
+in order to find targets.  However, the flush plan which
+represents the full list of every single INSERT, UPDATE, DELETE statement
+to be emitted has *already been decided* when these events are called,
+and no changes may be made at this stage.  Therefore the only changes that are
+even possible to the given objects are upon attributes **local** to the
+object's row.   Any other change to the object or other objects will
+impact the state of the :class:`.Session`, which will fail to function
+properly.
+
+Operations that are not supported within these mapper-level persistence
+events include:
+
+* :meth:`.Session.add`
+* :meth:`.Session.delete`
+* Mapped collection append, add, remove, delete, discard, etc.
+* Mapped relationship attribute set/del events,
+  i.e. ``someobject.related = someotherobject``
+
+The reason the :class:`.Connection` is passed is that it is encouraged that
+**simple SQL operations take place here**, directly on the :class:`.Connection`,
+such as incrementing counters or inserting extra rows within log tables.
+When dealing with the :class:`.Connection`, it is expected that Core-level
+SQL operations will be used; e.g. those described in :ref:`sqlexpression_toplevel`.
+
+There are also many per-object operations that don't need to be handled
+within a flush event at all.   The most common alternative is to simply
+establish additional state along with an object inside its ``__init__()``
+method, such as creating additional objects that are to be associated with
+the new object.  Using validators as described in :ref:`simple_validators` is
+another approach; these functions can intercept changes to attributes and
+establish additional state changes on the target object in response to the
+attribute change.   With both of these approaches, the object is in
+the correct state before it ever gets to the flush step.
+
+.. _session_lifecycle_events:
+
+Object Lifecycle Events
+-----------------------
+
+Another use case for events is to track the lifecycle of objects.  This
+refers to the states first introduced at :ref:`session_object_states`.
+
+As of SQLAlchemy 1.0, there is no direct event interface for tracking of
+these states.  Events that can be used at the moment to track the state of
+objects include:
+
+* :meth:`.InstanceEvents.init`
+
+* :meth:`.InstanceEvents.load`
+
+* :meth:`.SessionEvents.before_attach`
+
+* :meth:`.SessionEvents.after_attach`
+
+* :meth:`.SessionEvents.before_flush` - by scanning the session's collections
+
+* :meth:`.SessionEvents.after_flush` - by scanning the session's collections
+
+SQLAlchemy 1.1 will introduce a comprehensive event system to track
+the object persistence states fully and unambiguously.
+
+.. _session_transaction_events:
+
+Transaction Events
+------------------
+
+Transaction events allow an application to be notifed when transaction
+boundaries occur at the :class:`.Session` level as well as when the
+:class:`.Session` changes the transactional state on :class:`.Connection`
+objects.
+
+* :meth:`.SessionEvents.after_transaction_create`,
+  :meth:`.SessionEvents.after_transaction_end` - these events track the
+  logical transaction scopes of the :class:`.Session` in a way that is
+  not specific to individual database connections.  These events are
+  intended to help with integration of transaction-tracking systems such as
+  ``zope.sqlalchemy``.  Use these
+  events when the application needs to align some external scope with the
+  transactional scope of the :class:`.Session`.  These hooks mirror
+  the "nested" transactional behavior of the :class:`.Session`, in that they
+  track logical "subtransactions" as well as "nested" (e.g. SAVEPOINT)
+  transactions.
+
+* :meth:`.SessionEvents.before_commit`, :meth:`.SessionEvents.after_commit`,
+  :meth:`.SessionEvents.after_begin`,
+  :meth:`.SessionEvents.after_rollback`, :meth:`.SessionEvents.after_soft_rollback` -
+  These events allow tracking of transaction events from the perspective
+  of database connections.   :meth:`.SessionEvents.after_begin` in particular
+  is a per-connection event; a :class:`.Session` that maintains more than
+  one connection will emit this event for each connection individually
+  as those connections become used within the current transaction.
+  The rollback and commit events then refer to when the DBAPI connections
+  themselves have received rollback or commit instructions directly.
+
+Attribute Change Events
+-----------------------
+
+The attribute change events allow interception of when specific attributes
+on an object are modified.  These events include :meth:`.AttributeEvents.set`,
+:meth:`.AttributeEvents.append`, and :meth:`.AttributeEvents.remove`.  These
+events are extremely useful, particularly for per-object validation operations;
+however, it is often much more convenient to use a "validator" hook, which
+uses these hooks behind the scenes; see :ref:`simple_validators` for
+background on this.  The attribute events are also behind the mechanics
+of backreferences.   An example illustrating use of attribute events
+is in :ref:`examples_instrumentation`.
+
+
+
+
index 1a8bb3dee0025bf8b904587e6ab685d8dd8e33b0..18673fde1892a42273a4b3d6faec1c59892ee30c 100644 (file)
@@ -23,11 +23,25 @@ It's helpful to know the states which an instance can have within a session:
   existing instances (or moving persistent instances from other sessions into
   your local session).
 
-* **Detached** - an instance which has a record in the database, but is not in
-  any session. There's nothing wrong with this, and you can use objects
-  normally when they're detached, **except** they will not be able to issue
-  any SQL in order to load collections or attributes which are not yet loaded,
-  or were marked as "expired".
+  .. note::
+
+    An object that is marked as deleted, e.g. via the
+    :meth:`.Session.delete` method, is still considered persistent.  The
+    object remains in the identity map until the flush proceeds and a DELETE
+    state is emitted, at which point the object moves to the state that is
+    for most practical purposes "detached" - after the session's transaction
+    is committed, the object becomes fully detached.   SQLAlchemy 1.1 will
+    introduce a new object state called "deleted" which represents
+    this "deleted but not quite detached" state explicitly.
+
+* **Detached** - an instance which corresponds, or previously corresponded,
+  to a record in the database, but is not currently in any session.
+  The detached object will contain a database identity marker, however
+  because it is not associated with a session, it is unknown whether or not
+  this database identity actually exists in a target database.  Detached
+  objects are safe to use normally, except that they have no ability to
+  load unloaded attributes or attributes that were previously marked
+  as "expired".
 
 Knowing these states is important, since the
 :class:`.Session` tries to be strict about ambiguous
@@ -55,6 +69,8 @@ the :func:`.inspect` system::
 
     :attr:`.InstanceState.detached`
 
+.. _session_attributes:
+
 Session Attributes
 ------------------
 
@@ -121,8 +137,9 @@ referenced.
   or into lists or sets for the span of time that they need to remain referenced.
   These collections can be associated with a :class:`.Session`, if desired,
   by placing them into the :attr:`.Session.info` dictionary.  Events such
-  as the :meth:`.SessionEvents.after_attach` event may also be of use for
-  intercepting objects as they are associated with a :class:`.Session`.
+  as the :meth:`.SessionEvents.after_attach` and :meth:`.MapperEvents.load`
+  event may also be of use for intercepting objects as they are associated
+  with a :class:`.Session`.
 
 .. _unitofwork_merging:
 
index bca3e944fe304aa3a0cefce9e19e2901a216f64a..e197eb715cfeae579163beee5eb07c903c0fc1a1 100644 (file)
@@ -414,6 +414,12 @@ on the target connection, a warning is emitted::
     :paramref:`.Session.connection.execution_options`
     parameter to :meth:`.Session.connection`.
 
+Tracking Transaction State with Events
+--------------------------------------
+
+See the section :ref:`session_transaction_events` for an overview
+of the available event hooks for session transaction state changes.
+
 .. _session_external_transaction:
 
 Joining a Session into an External Transaction (such as for test suites)
@@ -513,3 +519,4 @@ everything is rolled back.
                       session.begin_nested()
 
           # ... the tearDown() method stays the same
+
index 6d4aa039bae1edd56da882753291c5e60658875f..29bdbaa8cbe5bb75496716d89b4bd67acae0042d 100644 (file)
@@ -825,30 +825,14 @@ class MapperEvents(event.Events):
         steps.
 
         .. warning::
-            Mapper-level flush events are designed to operate **on attributes
-            local to the immediate object being handled
-            and via SQL operations with the given**
-            :class:`.Connection` **only.** Handlers here should **not** make
-            alterations to the state of the :class:`.Session` overall, and
-            in general should not affect any :func:`.relationship` -mapped
-            attributes, as session cascade rules will not function properly,
-            nor is it always known if the related class has already been
-            handled. Operations that **are not supported in mapper
-            events** include:
-
-            * :meth:`.Session.add`
-            * :meth:`.Session.delete`
-            * Mapped collection append, add, remove, delete, discard, etc.
-            * Mapped relationship attribute set/del events,
-              i.e. ``someobject.related = someotherobject``
-
-            Operations which manipulate the state of the object
-            relative to other objects are better handled:
-
-            * In the ``__init__()`` method of the mapped object itself, or
-              another method designed to establish some particular state.
-            * In a ``@validates`` handler, see :ref:`simple_validators`
-            * Within the  :meth:`.SessionEvents.before_flush` event.
+
+            Mapper-level flush events only allow **very limited operations**,
+            on attributes local to the row being operated upon only,
+            as well as allowing any SQL to be emitted on the given
+            :class:`.Connection`.  **Please read fully** the notes
+            at :ref:`session_persistence_mapper` for guidelines on using
+            these methods; generally, the :meth:`.SessionEvents.before_flush`
+            method should be preferred for general on-flush changes.
 
         :param mapper: the :class:`.Mapper` which is the target
          of this event.
@@ -862,6 +846,10 @@ class MapperEvents(event.Events):
          object associated with the instance.
         :return: No return value is supported by this event.
 
+        .. seealso::
+
+            :ref:`session_persistence_events`
+
         """
 
     def after_insert(self, mapper, connection, target):
@@ -883,30 +871,14 @@ class MapperEvents(event.Events):
         event->persist->event steps.
 
         .. warning::
-            Mapper-level flush events are designed to operate **on attributes
-            local to the immediate object being handled
-            and via SQL operations with the given**
-            :class:`.Connection` **only.** Handlers here should **not** make
-            alterations to the state of the :class:`.Session` overall, and in
-            general should not affect any :func:`.relationship` -mapped
-            attributes, as session cascade rules will not function properly,
-            nor is it always known if the related class has already been
-            handled. Operations that **are not supported in mapper
-            events** include:
-
-            * :meth:`.Session.add`
-            * :meth:`.Session.delete`
-            * Mapped collection append, add, remove, delete, discard, etc.
-            * Mapped relationship attribute set/del events,
-              i.e. ``someobject.related = someotherobject``
-
-            Operations which manipulate the state of the object
-            relative to other objects are better handled:
-
-            * In the ``__init__()`` method of the mapped object itself,
-              or another method designed to establish some particular state.
-            * In a ``@validates`` handler, see :ref:`simple_validators`
-            * Within the  :meth:`.SessionEvents.before_flush` event.
+
+            Mapper-level flush events only allow **very limited operations**,
+            on attributes local to the row being operated upon only,
+            as well as allowing any SQL to be emitted on the given
+            :class:`.Connection`.  **Please read fully** the notes
+            at :ref:`session_persistence_mapper` for guidelines on using
+            these methods; generally, the :meth:`.SessionEvents.before_flush`
+            method should be preferred for general on-flush changes.
 
         :param mapper: the :class:`.Mapper` which is the target
          of this event.
@@ -920,6 +892,10 @@ class MapperEvents(event.Events):
          object associated with the instance.
         :return: No return value is supported by this event.
 
+        .. seealso::
+
+            :ref:`session_persistence_events`
+
         """
 
     def before_update(self, mapper, connection, target):
@@ -960,29 +936,14 @@ class MapperEvents(event.Events):
         steps.
 
         .. warning::
-            Mapper-level flush events are designed to operate **on attributes
-            local to the immediate object being handled
-            and via SQL operations with the given** :class:`.Connection`
-            **only.** Handlers here should **not** make alterations to the
-            state of the :class:`.Session` overall, and in general should not
-            affect any :func:`.relationship` -mapped attributes, as
-            session cascade rules will not function properly, nor is it
-            always known if the related class has already been handled.
-            Operations that **are not supported in mapper events** include:
-
-            * :meth:`.Session.add`
-            * :meth:`.Session.delete`
-            * Mapped collection append, add, remove, delete, discard, etc.
-            * Mapped relationship attribute set/del events,
-              i.e. ``someobject.related = someotherobject``
-
-            Operations which manipulate the state of the object
-            relative to other objects are better handled:
-
-            * In the ``__init__()`` method of the mapped object itself,
-              or another method designed to establish some particular state.
-            * In a ``@validates`` handler, see :ref:`simple_validators`
-            * Within the  :meth:`.SessionEvents.before_flush` event.
+
+            Mapper-level flush events only allow **very limited operations**,
+            on attributes local to the row being operated upon only,
+            as well as allowing any SQL to be emitted on the given
+            :class:`.Connection`.  **Please read fully** the notes
+            at :ref:`session_persistence_mapper` for guidelines on using
+            these methods; generally, the :meth:`.SessionEvents.before_flush`
+            method should be preferred for general on-flush changes.
 
         :param mapper: the :class:`.Mapper` which is the target
          of this event.
@@ -995,6 +956,11 @@ class MapperEvents(event.Events):
          instead be the :class:`.InstanceState` state-management
          object associated with the instance.
         :return: No return value is supported by this event.
+
+        .. seealso::
+
+            :ref:`session_persistence_events`
+
         """
 
     def after_update(self, mapper, connection, target):
@@ -1034,29 +1000,14 @@ class MapperEvents(event.Events):
         steps.
 
         .. warning::
-            Mapper-level flush events are designed to operate **on attributes
-            local to the immediate object being handled
-            and via SQL operations with the given** :class:`.Connection`
-            **only.** Handlers here should **not** make alterations to the
-            state of the :class:`.Session` overall, and in general should not
-            affect any :func:`.relationship` -mapped attributes, as
-            session cascade rules will not function properly, nor is it
-            always known if the related class has already been handled.
-            Operations that **are not supported in mapper events** include:
-
-            * :meth:`.Session.add`
-            * :meth:`.Session.delete`
-            * Mapped collection append, add, remove, delete, discard, etc.
-            * Mapped relationship attribute set/del events,
-              i.e. ``someobject.related = someotherobject``
-
-            Operations which manipulate the state of the object
-            relative to other objects are better handled:
-
-            * In the ``__init__()`` method of the mapped object itself,
-              or another method designed to establish some particular state.
-            * In a ``@validates`` handler, see :ref:`simple_validators`
-            * Within the  :meth:`.SessionEvents.before_flush` event.
+
+            Mapper-level flush events only allow **very limited operations**,
+            on attributes local to the row being operated upon only,
+            as well as allowing any SQL to be emitted on the given
+            :class:`.Connection`.  **Please read fully** the notes
+            at :ref:`session_persistence_mapper` for guidelines on using
+            these methods; generally, the :meth:`.SessionEvents.before_flush`
+            method should be preferred for general on-flush changes.
 
         :param mapper: the :class:`.Mapper` which is the target
          of this event.
@@ -1070,6 +1021,10 @@ class MapperEvents(event.Events):
          object associated with the instance.
         :return: No return value is supported by this event.
 
+        .. seealso::
+
+            :ref:`session_persistence_events`
+
         """
 
     def before_delete(self, mapper, connection, target):
@@ -1085,29 +1040,14 @@ class MapperEvents(event.Events):
         once in a later step.
 
         .. warning::
-            Mapper-level flush events are designed to operate **on attributes
-            local to the immediate object being handled
-            and via SQL operations with the given** :class:`.Connection`
-            **only.** Handlers here should **not** make alterations to the
-            state of the :class:`.Session` overall, and in general should not
-            affect any :func:`.relationship` -mapped attributes, as
-            session cascade rules will not function properly, nor is it
-            always known if the related class has already been handled.
-            Operations that **are not supported in mapper events** include:
-
-            * :meth:`.Session.add`
-            * :meth:`.Session.delete`
-            * Mapped collection append, add, remove, delete, discard, etc.
-            * Mapped relationship attribute set/del events,
-              i.e. ``someobject.related = someotherobject``
-
-            Operations which manipulate the state of the object
-            relative to other objects are better handled:
-
-            * In the ``__init__()`` method of the mapped object itself,
-              or another method designed to establish some particular state.
-            * In a ``@validates`` handler, see :ref:`simple_validators`
-            * Within the  :meth:`.SessionEvents.before_flush` event.
+
+            Mapper-level flush events only allow **very limited operations**,
+            on attributes local to the row being operated upon only,
+            as well as allowing any SQL to be emitted on the given
+            :class:`.Connection`.  **Please read fully** the notes
+            at :ref:`session_persistence_mapper` for guidelines on using
+            these methods; generally, the :meth:`.SessionEvents.before_flush`
+            method should be preferred for general on-flush changes.
 
         :param mapper: the :class:`.Mapper` which is the target
          of this event.
@@ -1121,6 +1061,10 @@ class MapperEvents(event.Events):
          object associated with the instance.
         :return: No return value is supported by this event.
 
+        .. seealso::
+
+            :ref:`session_persistence_events`
+
         """
 
     def after_delete(self, mapper, connection, target):
@@ -1136,29 +1080,14 @@ class MapperEvents(event.Events):
         once in a previous step.
 
         .. warning::
-            Mapper-level flush events are designed to operate **on attributes
-            local to the immediate object being handled
-            and via SQL operations with the given** :class:`.Connection`
-            **only.** Handlers here should **not** make alterations to the
-            state of the :class:`.Session` overall, and in general should not
-            affect any :func:`.relationship` -mapped attributes, as
-            session cascade rules will not function properly, nor is it
-            always known if the related class has already been handled.
-            Operations that **are not supported in mapper events** include:
-
-            * :meth:`.Session.add`
-            * :meth:`.Session.delete`
-            * Mapped collection append, add, remove, delete, discard, etc.
-            * Mapped relationship attribute set/del events,
-              i.e. ``someobject.related = someotherobject``
-
-            Operations which manipulate the state of the object
-            relative to other objects are better handled:
-
-            * In the ``__init__()`` method of the mapped object itself,
-              or another method designed to establish some particular state.
-            * In a ``@validates`` handler, see :ref:`simple_validators`
-            * Within the  :meth:`.SessionEvents.before_flush` event.
+
+            Mapper-level flush events only allow **very limited operations**,
+            on attributes local to the row being operated upon only,
+            as well as allowing any SQL to be emitted on the given
+            :class:`.Connection`.  **Please read fully** the notes
+            at :ref:`session_persistence_mapper` for guidelines on using
+            these methods; generally, the :meth:`.SessionEvents.before_flush`
+            method should be preferred for general on-flush changes.
 
         :param mapper: the :class:`.Mapper` which is the target
          of this event.
@@ -1172,6 +1101,10 @@ class MapperEvents(event.Events):
          object associated with the instance.
         :return: No return value is supported by this event.
 
+        .. seealso::
+
+            :ref:`session_persistence_events`
+
         """
 
 
@@ -1412,6 +1345,8 @@ class SessionEvents(event.Events):
 
             :meth:`~.SessionEvents.after_flush_postexec`
 
+            :ref:`session_persistence_events`
+
         """
 
     def after_flush(self, session, flush_context):
@@ -1432,6 +1367,8 @@ class SessionEvents(event.Events):
 
             :meth:`~.SessionEvents.after_flush_postexec`
 
+            :ref:`session_persistence_events`
+
         """
 
     def after_flush_postexec(self, session, flush_context):
@@ -1454,6 +1391,8 @@ class SessionEvents(event.Events):
 
             :meth:`~.SessionEvents.after_flush`
 
+            :ref:`session_persistence_events`
+
         """
 
     def after_begin(self, session, transaction, connection):
@@ -1491,6 +1430,8 @@ class SessionEvents(event.Events):
 
             :meth:`~.SessionEvents.after_attach`
 
+            :ref:`session_lifecycle_events`
+
         """
 
     def after_attach(self, session, instance):
@@ -1513,6 +1454,8 @@ class SessionEvents(event.Events):
 
             :meth:`~.SessionEvents.before_attach`
 
+            :ref:`session_lifecycle_events`
+
         """
 
     @event._legacy_signature("0.9",