summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-09-02 17:55:26 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2015-09-02 17:55:26 -0400
commit606135dd989f3f9b057559128a185e9f7e5364f0 (patch)
tree22653edc53d9936b90cbfe1eb75e44d337ca0a88 /doc
parent6d0c0994e9a08cf5d149af0314970d5f6e25b159 (diff)
parent108c60f460c723a0f48c47597928d938a3b0a42d (diff)
downloadsqlalchemy-606135dd989f3f9b057559128a185e9f7e5364f0.tar.gz
Merge branch 'ticket_2677'
Diffstat (limited to 'doc')
-rw-r--r--doc/build/changelog/changelog_11.rst44
-rw-r--r--doc/build/changelog/migration_11.rst90
-rw-r--r--doc/build/core/events.rst4
-rw-r--r--doc/build/glossary.rst23
-rw-r--r--doc/build/orm/events.rst10
-rw-r--r--doc/build/orm/examples.rst2
-rw-r--r--doc/build/orm/session.rst1
-rw-r--r--doc/build/orm/session_events.rst433
-rw-r--r--doc/build/orm/session_state_management.rst126
-rw-r--r--doc/build/orm/session_transaction.rst7
10 files changed, 691 insertions, 49 deletions
diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst
index 4fff4ab64..27dc8fd46 100644
--- a/doc/build/changelog/changelog_11.rst
+++ b/doc/build/changelog/changelog_11.rst
@@ -22,6 +22,50 @@
:version: 1.1.0b1
.. change::
+ :tags: feature, orm
+ :tickets: 2677
+
+ The :class:`.SessionEvents` suite now includes events to allow
+ unambiguous tracking of all object lifecycle state transitions
+ in terms of the :class:`.Session` itself, e.g. pending,
+ transient, persistent, detached. The state of the object
+ within each event is also defined.
+
+ .. seealso::
+
+ :ref:`change_2677`
+
+ .. change::
+ :tags: feature, orm
+ :tickets: 2677
+
+ Added a new session lifecycle state :term:`deleted`. This new state
+ represents an object that has been deleted from the :term:`persistent`
+ state and will move to the :term:`detached` state once the transaction
+ is committed. This resolves the long-standing issue that objects
+ which were deleted existed in a gray area between persistent and
+ detached. The :attr:`.InstanceState.persistent` accessor will
+ **no longer** report on a deleted object as persistent; the
+ :attr:`.InstanceState.deleted` accessor will instead be True for
+ these objects, until they become detached.
+
+ .. seealso::
+
+ :ref:`change_2677`
+
+ .. change::
+ :tags: change, orm
+ :tickets: 2677
+
+ The :paramref:`.Session.weak_identity_map` parameter is deprecated.
+ See the new recipe at :ref:`session_referencing_behavior` for
+ an event-based approach to maintaining strong identity map behavior.
+
+ .. seealso::
+
+ :ref:`change_2677`
+
+ .. change::
:tags: bug, sql
:tickets: 2919
diff --git a/doc/build/changelog/migration_11.rst b/doc/build/changelog/migration_11.rst
index 412f42d27..ff26a00bd 100644
--- a/doc/build/changelog/migration_11.rst
+++ b/doc/build/changelog/migration_11.rst
@@ -16,7 +16,7 @@ What's New in SQLAlchemy 1.1?
some issues may be moved to later milestones in order to allow
for a timely release.
- Document last updated: August 26, 2015
+ Document last updated: September 2, 2015
Introduction
============
@@ -66,6 +66,94 @@ as it relies on deprecated features of setuptools.
New Features and Improvements - ORM
===================================
+.. _change_2677:
+
+New Session lifecycle events
+----------------------------
+
+The :class:`.Session` has long supported events that allow some degree
+of tracking of state changes to objects, including
+:meth:`.SessionEvents.before_attach`, :meth:`.SessionEvents.after_attach`,
+and :meth:`.SessionEvents.before_flush`. The Session documentation also
+documents major object states at :ref:`session_object_states`. However,
+there has never been system of tracking objects specifically as they
+pass through these transitions. Additionally, the status of "deleted" objects
+has historically been murky as the objects act somewhere between
+the "persistent" and "detached" states.
+
+To clean up this area and allow the realm of session state transition
+to be fully transparent, a new series of events have been added that
+are intended to cover every possible way that an object might transition
+between states, and additionally the "deleted" status has been given
+its own official state name within the realm of session object states.
+
+New State Transition Events
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Transitions between all states of an object such as :term:`persistent`,
+:term:`pending` and others can now be intercepted in terms of a
+session-level event intended to cover a specific transition.
+Transitions as objects move into a :class:`.Session`, move out of a
+:class:`.Session`, and even all the transitions which occur when the
+transaction is rolled back using :meth:`.Session.rollback`
+are explicitly present in the interface of :class:`.SessionEvents`.
+
+In total, there are **ten new events**. A summary of these events is in a
+newly written documentation section :ref:`session_lifecycle_events`.
+
+
+New Object State "deleted" is added, deleted objects no longer "persistent"
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The :term:`persistent` state of an object in the :class:`.Session` has
+always been documented as an object that has a valid database identity;
+however in the case of objects that were deleted within a flush, they
+have always been in a grey area where they are not really "detached"
+from the :class:`.Session` yet, because they can still be restored
+within a rollback, but are not really "persistent" because their database
+identity has been deleted and they aren't present in the identity map.
+
+To resolve this grey area given the new events, a new object state
+:term:`deleted` is introduced. This state exists between the "persistent" and
+"detached" states. An object that is marked for deletion via
+:meth:`.Session.delete` remains in the "persistent" state until a flush
+proceeds; at that point, it is removed from the identity map, moves
+to the "deleted" state, and the :meth:`.SessionEvents.persistent_to_deleted`
+hook is invoked. If the :class:`.Session` object's transaction is rolled
+back, the object is restored as persistent; the
+:meth:`.SessionEvents.deleted_to_persistent` transition is called. Otherwise
+if the :class:`.Session` object's transaction is committed,
+the :meth:`.SessionEvents.deleted_to_detached` transition is invoked.
+
+Additionally, the :attr:`.InstanceState.persistent` accessor **no longer returns
+True** for an object that is in the new "deleted" state; instead, the
+:attr:`.InstanceState.deleted` accessor has been enhanced to reliably
+report on this new state. When the object is detached, the :attr:`.InstanceState.deleted`
+returns False and the :attr:`.InstanceState.detached` accessor is True
+instead. To determine if an object was deleted either in the current
+transaction or in a previous transaction, use the
+:attr:`.InstanceState.was_deleted` accessor.
+
+Strong Identity Map is Deprecated
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+One of the inspirations for the new series of transition events was to enable
+leak-proof tracking of objects as they move in and out of the identity map,
+so that a "strong reference" may be maintained mirroring the object
+moving in and out of this map. With this new capability, there is no longer
+any need for the :paramref:`.Session.weak_identity_map` parameter and the
+corresponding :class:`.StrongIdentityMap` object. This option has remained
+in SQLAlchemy for many years as the "strong-referencing" behavior used to be
+the only behavior available, and many applications were written to assume
+this behavior. It has long been recommended that strong-reference tracking
+of objects not be an intrinsic job of the :class:`.Session` and instead
+be an application-level construct built as needed by the application; the
+new event model allows even the exact behavior of the strong identity map
+to be replicated. See :ref:`session_referencing_behavior` for a new
+recipe illustrating how to replace the strong identity map.
+
+:ticket:`2677`
+
.. _change_3499:
Changes regarding "unhashable" types
diff --git a/doc/build/core/events.rst b/doc/build/core/events.rst
index d19b910b1..451cb9460 100644
--- a/doc/build/core/events.rst
+++ b/doc/build/core/events.rst
@@ -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
-----------------------
diff --git a/doc/build/glossary.rst b/doc/build/glossary.rst
index c0ecee84b..9c1395f14 100644
--- a/doc/build/glossary.rst
+++ b/doc/build/glossary.rst
@@ -1019,7 +1019,7 @@ Glossary
http://en.wikipedia.org/wiki/Unique_key#Defining_unique_keys
transient
- This describes one of the four major object states which
+ This describes one of the major object states which
an object can have within a :term:`session`; a transient object
is a new object that doesn't have any database identity
and has not been associated with a session yet. When the
@@ -1031,7 +1031,7 @@ Glossary
:ref:`session_object_states`
pending
- This describes one of the four major object states which
+ This describes one of the major object states which
an object can have within a :term:`session`; a pending object
is a new object that doesn't have any database identity,
but has been recently associated with a session. When
@@ -1042,8 +1042,23 @@ Glossary
:ref:`session_object_states`
+ deleted
+ This describes one of the major object states which
+ an object can have within a :term:`session`; a deleted object
+ is an object that was formerly persistent and has had a
+ DELETE statement emitted to the database within a flush
+ to delete its row. The object will move to the :term:`detached`
+ state once the session's transaction is committed; alternatively,
+ if the session's transaction is rolled back, the DELETE is
+ reverted and the object moves back to the :term:`persistent`
+ state.
+
+ .. seealso::
+
+ :ref:`session_object_states`
+
persistent
- This describes one of the four major object states which
+ This describes one of the major object states which
an object can have within a :term:`session`; a persistent object
is an object that has a database identity (i.e. a primary key)
and is currently associated with a session. Any object
@@ -1058,7 +1073,7 @@ Glossary
:ref:`session_object_states`
detached
- This describes one of the four major object states which
+ This describes one of the major object states which
an object can have within a :term:`session`; a detached object
is an object that has a database identity (i.e. a primary key)
but is not associated with any session. An object that
diff --git a/doc/build/orm/events.rst b/doc/build/orm/events.rst
index e9673bed0..470a9386b 100644
--- a/doc/build/orm/events.rst
+++ b/doc/build/orm/events.rst
@@ -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
----------------
diff --git a/doc/build/orm/examples.rst b/doc/build/orm/examples.rst
index 4db7c00dc..25d243022 100644
--- a/doc/build/orm/examples.rst
+++ b/doc/build/orm/examples.rst
@@ -93,6 +93,8 @@ Versioning with a History Table
.. automodule:: examples.versioned_history
+.. _examples_versioned_rows:
+
Versioning using Temporal Rows
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/doc/build/orm/session.rst b/doc/build/orm/session.rst
index 624ee9f75..79ea70137 100644
--- a/doc/build/orm/session.rst
+++ b/doc/build/orm/session.rst
@@ -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
index 000000000..dc7ad39e0
--- /dev/null
+++ b/doc/build/orm/session_events.rst
@@ -0,0 +1,433 @@
+.. _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`.
+
+.. versionadded:: 1.1 added a system of events that intercept all possible
+ state transitions of an object within the :class:`.Session`.
+
+All the states above can be tracked fully with events. Each event
+represents a distinct state transition, meaning, the starting state
+and the destination state are both part of what are tracked. With the
+exception of the initial transient event, all the events are in terms of
+the :class:`.Session` object or class, meaning they can be associated either
+with a specific :class:`.Session` object::
+
+ from sqlalchemy import event
+ from sqlalchemy.orm import Session
+
+ session = Session()
+
+ @event.listens_for(session, 'transient_to_pending')
+ def object_is_pending(session, obj):
+ print("new pending: %s" % obj)
+
+Or with the :class:`.Session` class itself, as well as with a specific
+:class:`.sessionmaker`, which is likely the most useful form::
+
+ from sqlalchemy import event
+ from sqlalchemy.orm import sessionmaker
+
+ maker = sessionmaker()
+
+ @event.listens_for(maker, 'transient_to_pending')
+ def object_is_pending(session, obj):
+ print("new pending: %s" % obj)
+
+The listeners can of course be stacked on top of one function, as is
+likely to be common. For example, to track all objects that are
+entering the persistent state::
+
+ @event.listens_for(maker, "pending_to_persistent")
+ @event.listens_for(maker, "deleted_to_persistent")
+ @event.listens_for(maker, "detached_to_persistent")
+ @event.listens_for(maker, "loaded_as_persistent")
+ def detect_all_persistent(session, instance):
+ print("object is now persistent: %s" % instance)
+
+Transient
+^^^^^^^^^
+
+All mapped objects when first constructed start out as :term:`transient`.
+In this state, the object exists alone and doesn't have an association with
+any :class:`.Session`. For this initial state, there's no specific
+"transition" event since there is no :class:`.Session`, however if one
+wanted to intercept when any transient object is created, the
+:meth:`.InstanceEvents.init` method is probably the best event. This
+event is applied to a specific class or superclass. For example, to
+intercept all new objects for a particular declarative base::
+
+ from sqlalchemy.ext.declarative import declarative_base
+ from sqlalchemy import event
+
+ Base = declarative_base()
+
+ @event.listens_for(Base, "init", propagate=True)
+ def intercept_init(instance, args, kwargs):
+ print("new transient: %s" % instance)
+
+
+Transient to Pending
+^^^^^^^^^^^^^^^^^^^^
+
+The transient object becomes :term:`pending` when it is first associated
+with a :class:`.Session` via the :meth:`.Session.add` or :meth:`.Session.add_all`
+method. An object may also become part of a :class:`.Session` as a result
+of a :ref:`"cascade" <unitofwork_cascades>` from a referencing object that was
+explicitly added. The transient to pending transition is detectable using
+the :meth:`.SessionEvents.transient_to_pending` event::
+
+ @event.listens_for(sessionmaker, "transient_to_pending")
+ def intercept_transient_to_pending(session, object_):
+ print("transient to pending: %s" % object_)
+
+
+Pending to Persistent
+^^^^^^^^^^^^^^^^^^^^^
+
+The :term:`pending` object becomes :term:`persistent` when a flush
+proceeds and an INSERT statement takes place for the instance. The object
+now has an identity key. Track pending to persistent with the
+:meth:`.SessionEvents.pending_to_persistent` event::
+
+ @event.listens_for(sessionmaker, "pending_to_persistent")
+ def intercept_pending_to_persistent(session, object_):
+ print("pending to persistent: %s" % object_)
+
+Pending to Transient
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The :term:`pending` object can revert back to :term:`transient` if the
+:meth:`.Session.rollback` method is called before the pending object
+has been flushed, or if the :meth:`.Session.expunge` method is called
+for the object before it is flushed. Track pending to transient with the
+:meth:`.SessionEvents.pending_to_transient` event::
+
+ @event.listens_for(sessionmaker, "pending_to_transient")
+ def intercept_pending_to_transient(session, object_):
+ print("transient to pending: %s" % object_)
+
+Loaded as Persistent
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Objects can appear in the :class:`.Session` directly in the :term:`persistent`
+state when they are loaded from the database. Tracking this state transition
+is synonymous with tracking objects as they are loaded, and is synonomous
+with using the :meth:`.InstanceEvents.load` instance-level event. However, the
+:meth:`.SessionEvents.loaded_as_persistent` event is provided as a
+session-centric hook for intercepting objects as they enter the persistent
+state via this particular avenue::
+
+ @event.listens_for(sessionmaker, "loaded_as_persistent")
+ def intercept_loaded_as_persistent(session, object_):
+ print("object loaded into persistent state: %s" % object_)
+
+
+Persistent to Transient
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The persistent object can revert to the transient state if the
+:meth:`.Session.rollback` method is called for a transaction where the
+object was first added as pending. In the case of the ROLLBACK, the
+INSERT statement that made this object persistent is rolled back, and
+the object is evicted from the :class:`.Session` to again become transient.
+Track objects that were reverted to transient from
+persistent using the :meth:`.SessionEvents.persistent_to_transient`
+event hook::
+
+ @event.listens_for(sessionmaker, "persistent_to_transient")
+ def intercept_persistent_to_transient(session, object_):
+ print("persistent to transient: %s" % object_)
+
+Persistent to Deleted
+^^^^^^^^^^^^^^^^^^^^^
+
+The persistent object enters the :term:`deleted` state when an object
+marked for deletion is deleted from the database within the flush
+process. Note that this is **not the same** as when the :meth:`.Session.delete`
+method is called for a target object. The :meth:`.Session.delete`
+method only **marks** the object for deletion; the actual DELETE statement
+is not emitted until the flush proceeds. It is subsequent to the flush
+that the "deleted" state is present for the target object.
+
+Within the "deleted" state, the object is only marginally associated
+with the :class:`.Session`. It is not present in the identity map
+nor is it present in the :attr:`.Session.deleted` collection that refers
+to when it was pending for deletion.
+
+From the "deleted" state, the object can go either to the detached state
+when the transaction is committed, or back to the persistent state
+if the transaction is instead rolled back.
+
+Track the persistent to deleted transition with
+:meth:`.SessionEvents.persistent_to_deleted`::
+
+ @event.listens_for(sessionmaker, "persistent_to_deleted")
+ def intercept_persistent_to_deleted(session, object_):
+ print("object was DELETEd, is now in deleted state: %s" % object_)
+
+
+Deleted to Detached
+^^^^^^^^^^^^^^^^^^^^
+
+The deleted object becomes :term:`detached` when the session's transaction
+is committed. After the :meth:`.Session.commit` method is called, the
+database transaction is final and the :class:`.Session` now fully discards
+the deleted object and removes all associations to it. Track
+the deleted to detached transition using :meth:`.SessionEvents.deleted_to_detached`::
+
+ @event.listens_for(sessionmaker, "deleted_to_detached")
+ def intercept_deleted_to_detached(session, object_):
+ print("deleted to detached: %s" % object_)
+
+
+.. note::
+
+ While the object is in the deleted state, the :attr:`.InstanceState.deleted`
+ attribute, accessible using ``inspect(object).deleted``, returns True. However
+ when the object is detached, :attr:`.InstanceState.deleted` will again
+ return False. To detect that an object was deleted, regardless of whether
+ or not it is detached, use the :attr:`.InstanceState.was_deleted`
+ accessor.
+
+
+Persistent to Detached
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The persistent object becomes :term:`detached` when the object is de-associated
+with the :class:`.Session`, via the :meth:`.Session.expunge`,
+:meth:`.Session.expunge_all`, or :meth:`.Session.close` methods.
+
+.. note::
+
+ An object may also become **implicitly detached** if its owning
+ :class:`.Session` is dereferenced by the application and discarded due to
+ garbage collection. In this case, **no event is emitted**.
+
+Track objects as they move from persistent to detached using the
+:meth:`.SessionEvents.persistent_to_detached` event::
+
+ @event.listens_for(sessionmaker, "persistent_to_detached")
+ def intecept_persistent_to_detached(session, object_):
+ print("object became detached: %s" % object_)
+
+Detached to Persistent
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The detached object becomes persistent when it is re-associated with a
+session using the :meth:`.Session.add` or equivalent method. Track
+objects moving back to persistent from detached using the
+:meth:`.SessionEvents.detached_to_persistent` event::
+
+ @event.listens_for(sessionmaker, "detached_to_persistent")
+ def intecept_detached_to_persistent(session, object_):
+ print("object became persistent again: %s" % object_)
+
+
+Deleted to Persistent
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The :term:`deleted` object can be reverted to the :term:`persistent`
+state when the transaction in which it was DELETEd was rolled back
+using the :meth:`.Session.rollback` method. Track deleted objects
+moving back to the persistent state using the
+:meth:`.SessionEvents.deleted_to_persistent` event::
+
+ @event.listens_for(sessionmaker, "transient_to_pending")
+ def intercept_transient_to_pending(session, object_):
+ print("transient to pending: %s" % object_)
+
+.. _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`.
+
+
+
+
diff --git a/doc/build/orm/session_state_management.rst b/doc/build/orm/session_state_management.rst
index 1a8bb3dee..090bf7674 100644
--- a/doc/build/orm/session_state_management.rst
+++ b/doc/build/orm/session_state_management.rst
@@ -23,16 +23,28 @@ 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".
-
-Knowing these states is important, since the
-:class:`.Session` tries to be strict about ambiguous
-operations (such as trying to save the same object to two different sessions
-at the same time).
+* **Deleted** - An instance which has been deleted within a flush, but
+ the transaction has not yet completed. Objects in this state are essentially
+ in the opposite of "pending" state; when the session's transaction is committed,
+ the object will move to the detached state. Alternatively, when
+ the session's transaction is rolled back, a deleted object moves
+ *back* to the persistent state.
+
+ .. versionchanged:: 1.1 The 'deleted' state is a newly added session
+ object state distinct from the 'persistent' state.
+
+* **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".
+
+For a deeper dive into all possible state transitions, see the
+section :ref:`session_lifecycle_events` which describes each transition
+as well as how to programmatically track each one.
Getting the Current State of an Object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -53,8 +65,12 @@ the :func:`.inspect` system::
:attr:`.InstanceState.persistent`
+ :attr:`.InstanceState.deleted`
+
:attr:`.InstanceState.detached`
+.. _session_attributes:
+
Session Attributes
------------------
@@ -91,7 +107,13 @@ all objects which have had changes since they were last loaded or saved (i.e.
(Documentation: :attr:`.Session.new`, :attr:`.Session.dirty`,
:attr:`.Session.deleted`, :attr:`.Session.identity_map`).
-Note that objects within the session are *weakly referenced*. This
+
+.. _session_referencing_behavior:
+
+Session Referencing Behavior
+----------------------------
+
+Objects within the session are *weakly referenced*. This
means that when they are dereferenced in the outside application, they fall
out of scope from within the :class:`~sqlalchemy.orm.session.Session` as well
and are subject to garbage collection by the Python interpreter. The
@@ -100,29 +122,65 @@ as deleted, or persistent objects which have pending changes on them. After a
full flush, these collections are all empty, and all objects are again weakly
referenced.
-.. note::
-
- To disable the weak referencing behavior and force all objects
- within the session to remain until explicitly expunged, configure
- :class:`.sessionmaker` with the ``weak_identity_map=False``
- setting. However note that this option is **deprecated**;
- it is present only to allow compatibility with older
- applications, typically those that were made back before SQLAlchemy
- had the ability to effectively weak-reference all objects.
- It is recommended that strong references to objects
- be maintained by the calling application externally to the
- :class:`.Session` itself, to the extent that is required by the application.
- This eliminates the
- :class:`.Session` as a possible source of unbounded memory growth in the case
- where large numbers of objects are being loaded and/or persisted.
-
- Simple examples of externally managed strong-referencing behavior
- include loading objects into a local dictionary keyed to their primary key,
- 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`.
+To cause objects in the :class:`.Session` to remain strongly
+referenced, usually a simple approach is all that's needed. Examples
+of externally managed strong-referencing behavior include loading
+objects into a local dictionary keyed to their primary key, 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.
+
+An event based approach is also feasable. A simple recipe that provides
+"strong referencing" behavior for all objects as they remain within
+the :term:`persistent` state is as follows::
+
+ from sqlalchemy import event
+
+ def strong_reference_session(session):
+ @event.listens_for(session, "pending_to_persistent")
+ @event.listens_for(session, "deleted_to_persistent")
+ @event.listens_for(session, "detached_to_persistent")
+ @event.listens_for(session, "loaded_as_persistent")
+ def strong_ref_object(sess, instance):
+ if 'refs' not in sess.info:
+ sess.info['refs'] = refs = set()
+ else:
+ refs = sess.info['refs']
+
+ refs.add(instance)
+
+
+ @event.listens_for(session, "persistent_to_detached")
+ @event.listens_for(session, "persistent_to_deleted")
+ @event.listens_for(session, "persistent_to_transient")
+ def deref_object(sess, instance):
+ sess.info['refs'].discard(instance)
+
+Above, we intercept the :meth:`.SessionEvents.pending_to_persistent`,
+:meth:`.SessionEvents.detached_to_persistent`,
+:meth:`.SessionEvents.deleted_to_persistent` and
+:meth:`.SessionEvents.loaded_as_persistent` event hooks in order to intercept
+objects as they enter the :term:`persistent` transition, and the
+:meth:`.SessionEvents.persistent_to_detached` and
+:meth:`.SessionEvents.persistent_to_deleted` hooks to intercept
+objects as they leave the persistent state.
+
+The above function may be called for any :class:`.Session` in order to
+provide strong-referencing behavior on a per-:class:`.Session` basis::
+
+ from sqlalchemy.orm import Session
+
+ my_session = Session()
+ strong_reference_session(my_session)
+
+It may also be called for any :class:`.sessionmaker`::
+
+ from sqlalchemy.orm import sessionmaker
+
+ maker = sessionmaker()
+ strong_reference_session(maker)
+
.. _unitofwork_merging:
diff --git a/doc/build/orm/session_transaction.rst b/doc/build/orm/session_transaction.rst
index bca3e944f..e197eb715 100644
--- a/doc/build/orm/session_transaction.rst
+++ b/doc/build/orm/session_transaction.rst
@@ -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
+