+.. _event_toplevel:
+
Events
======
-.. automodule:: sqlalchemy.event
+SQLAlchemy includes an event API which publishes a wide variety of hooks into
+the internals of both SQLAlchemy Core and ORM. The system is all new
+as of version 0.7 and supercedes the previous system of "extension", "proxy",
+and "listener" classes.
-.. autofunction:: sqlalchemy.event.listen
+Core events are described in :ref:`core_event_toplevel` and ORM events in :ref:`orm_event_toplevel`.
+
+Event Registration
+------------------
+
+Subscribing to an event occurs through a single API point, the :func:`.listen` function. This function
+accepts a user-defined listening function, a string identifier which identifies the event to be
+intercepted, and a target. Additional positional and keyword arguments may be supported by
+specific types of events, which may specify alternate interfaces for the given event function, or provide
+instructions regarding secondary event targets based on the given target.
+
+The name of an event and the argument signature of a corresponding listener function is derived from
+a class bound specification method, which exists bound to a marker class that's described in the documentation.
+For example, the documentation for :ref:`.PoolEvents.on_connect` indicates that the event name is ``"on_connect"``
+and that a user-defined listener function should receive two positional arguments::
+
+ from sqlalchemy.event import listen
+ from sqlalchemy.pool import Pool
+
+ def my_on_connect(dbapi_con, connection_record):
+ print "New DBAPI connection:", dbapi_con
+
+ listen(my_on_connect, 'on_connect', Pool)
+
+Targets
+-------
-Connection Pool Events
-----------------------
+The :func:`.listen` function is very flexible regarding targets. It generally accepts classes, instances of those
+classes, and related classes or objects from which the appropriate target can be derived. For example,
+the above mentioned ``"on_connect"`` event accepts :class:`.Engine` classes and objects as well as :class:`.Pool`
+classes and objects::
-.. autoclass:: sqlalchemy.pool.PoolEvents
- :members:
+ from sqlalchemy.event import listen
+ from sqlalchemy.pool import Pool, QueuePool
+ from sqlalchemy import create_engine
+ from sqlalchemy.engine import Engine
+ import psycopg2
+
+ def connect():
+ return psycopg2.connect(username='ed', host='127.0.0.1', dbname='test')
-Connection Events
-------------------------
+ my_pool = QueuePool(connect)
+ my_engine = create_engine('postgresql://ed@localhost/test')
+
+ # associate listener with all instances of Pool
+ listen(my_on_connect, 'on_connect', Pool)
-.. autoclass:: sqlalchemy.engine.base.EngineEvents
- :members:
+ # associate listener with all instances of Pool
+ # via the Engine class
+ listen(my_on_connect, 'on_connect', Engine)
+
+ # associate listener with my_pool
+ listen(my_on_connect, 'on_connect', my_pool)
+
+ # associate listener with my_engine.pool
+ listen(my_on_connect, 'on_connect', my_engine)
+
+Modifiers
+----------
+
+Some listeners allow modifiers to be passed to :func:`.listen`. These modifiers sometimes provide alternate
+calling signatures for listeners. Such as with ORM events, some event listeners can have a return value
+which modifies the subsequent handling. By default, no listener ever requires a return value, but by passing
+``retval=True`` this value can be supported::
+
+ 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)
+
+
+.. autofunction:: sqlalchemy.event.listen
--- /dev/null
+.. _core_event_toplevel:
+
+Core Events
+============
+
+This section describes the event interfaces provided in SQLAlchemy Core. For an introduction
+to the event listening API, see :ref:`event_toplevel`. ORM events are described in :ref:`orm_event_toplevel`.
+
+Connection Pool Events
+-----------------------
+
+.. autoclass:: sqlalchemy.events.PoolEvents
+ :members:
+
+Connection Events
+-----------------------
+
+.. autoclass:: sqlalchemy.events.EngineEvents
+ :members:
+
+Schema Events
+-----------------------
+
+.. autoclass:: sqlalchemy.events.DDLEvents
+ :members:
+
pooling
schema
types
+ event
+ events
interfaces
exceptions
compiler
-. _interfaces_core_toplevel:
+.. _interfaces_core_toplevel:
-Core Event Interfaces
-======================
+Deprecated Event Interfaces
+============================
.. module:: sqlalchemy.interfaces
-This section describes the various categories of events which can be intercepted
-in SQLAlchemy core, including execution and connection pool events.
-
-For ORM event documentation, see :ref:`interfaces_orm_toplevel`.
-
-A new version of this API with a significantly more flexible and consistent
-interface will be available in version 0.7.
+This section describes the class-based event interface introduced in
+SQLAlchemy 0.5. As of SQLAlchemy 0.7, the new event system described in
+:ref:`event_toplevel` should be used.
Execution, Connection and Cursor Events
---------------------------------------
--- /dev/null
+.. _orm_event_toplevel:
+
+ORM Events
+==========
+
+The ORM includes a wide variety of hooks available for subscription. The event
+system in 0.7 is all new and supercedes the previous system of "extension" classes.
+For an introduction to the event API, see :ref:`core_event_toplevel`.
+
+Attribute Events
+----------------
+
+.. autoclass:: sqlalchemy.orm.events.AttributeEvents
+ :members:
+
+Instrumentation Events
+-----------------------
+
+.. autoclass:: sqlalchemy.orm.events.InstrumentationEvents
+ :members:
+
+Mapper Events
+---------------
+
+.. autoclass:: sqlalchemy.orm.events.MapperEvents
+ :members:
+
+Instance Events
+---------------
+
+.. autoclass:: sqlalchemy.orm.events.InstanceEvents
+ :members:
+
+Session Events
+--------------
+
+TODO
+
.. _interfaces_orm_toplevel:
-.. _events_orm_toplevel:
-ORM Event Interfaces
-====================
+Deprecated ORM Event Interfaces
+================================
.. module:: sqlalchemy.orm.interfaces
-"""
-The event system handles all events throughout the :mod:`sqlalchemy`
-and :mod:`sqlalchemy.orm` packages.
-
-
-"""
+"""Base event API."""
from sqlalchemy import util, exc
"""
def on_before_create(self, target, connection, **kw):
- pass
+ """ """
def on_after_create(self, target, connection, **kw):
- pass
+ """ """
def on_before_drop(self, target, connection, **kw):
- pass
+ """ """
def on_after_drop(self, target, connection, **kw):
- pass
+ """ """
class PoolEvents(event.Events):
events.listen(my_on_checkout, 'on_checkout', Pool)
+ In addition to the :class:`.Pool` class and :class:`.Pool` instances,
+ :class:`.PoolEvents` also accepts :class:`.Engine` objects and
+ the :class:`.Engine` class as targets, which will be resolved
+ to the ``.pool`` attribute of the given engine or the :class:`.Pool`
+ class.
+
"""
+ @classmethod
+ def accept_with(cls, target):
+ from sqlalchemy.engine import Engine
+ from sqlalchemy.pool import Pool
+
+ if isinstance(target, type):
+ if issubclass(target, Engine):
+ return Pool
+ elif issubclass(target, Pool):
+ return target
+ elif isinstance(target, Engine):
+ return target.pool
+ else:
+ return target
+
def on_connect(self, dbapi_connection, connection_record):
"""Called once for each new DB-API connection or Pool's ``creator()``.
Several modifiers are available to the listen() function.
:param propagate=False: When True, the event listener should
- be applied to all inheriting mappers as well.
+ be applied to all inheriting mappers as well.
:param raw=False: When True, the "target" argument to the
- event, if applicable will be the :class:`.InstanceState` management
- object, rather than the mapped instance itself.
+ event, if applicable will be the :class:`.InstanceState` management
+ object, rather than the mapped instance itself.
:param retval=False: when True, the user-defined event listening
- 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 values
- here are::
+ 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 values
+ here 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 "translate_row" or "create_instance".
+ * ``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 "translate_row" or "create_instance".
"""
self.assert_(c.connection is not c2.connection)
self.assert_(not c2.info)
self.assert_('foo2' in c.info)
+
+class PoolEventsTest(PoolTestBase):
@testing.uses_deprecated(r".*Use event.listen")
def test_listeners(self):
def test_listener_after_oninit(self):
"""Test that listeners are called after OnInit is removed"""
+
called = []
def listener(*args):
called.append(True)
engine.execute(select([1])).close()
assert called, "Listener not called on connect"
+ def test_targets(self):
+ canary = []
+ def listen_one(*args):
+ canary.append("listen_one")
+ def listen_two(*args):
+ canary.append("listen_two")
+ def listen_three(*args):
+ canary.append("listen_three")
+ def listen_four(*args):
+ canary.append("listen_four")
+
+ engine = create_engine(testing.db.url)
+ event.listen(listen_one, 'on_connect', pool.Pool)
+ event.listen(listen_two, 'on_connect', engine.pool)
+ event.listen(listen_three, 'on_connect', engine)
+ event.listen(listen_four, 'on_connect', engine.__class__)
+
+ engine.execute(select([1])).close()
+ eq_(
+ canary, ["listen_one","listen_four", "listen_two","listen_three"]
+ )
+
+ def teardown(self):
+ # TODO: need to get remove() functionality
+ # going
+ pool.Pool.dispatch.clear()
+
class QueuePoolTest(PoolTestBase):