diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-18 20:23:01 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-18 20:23:01 -0400 |
commit | 3e55ed778b38d1ee18e1317b431c53ac1f6b141f (patch) | |
tree | 5c2c113162d7823b1e0c61e83372245510244f82 /lib/sqlalchemy/events.py | |
parent | e8ff3047c6596d39bb38956eb5aba5651c104e63 (diff) | |
download | sqlalchemy-3e55ed778b38d1ee18e1317b431c53ac1f6b141f.tar.gz |
- [feature] Connection event listeners can
now be associated with individual
Connection objects, not just Engine
objects. [ticket:2511]
Diffstat (limited to 'lib/sqlalchemy/events.py')
-rw-r--r-- | lib/sqlalchemy/events.py | 148 |
1 files changed, 80 insertions, 68 deletions
diff --git a/lib/sqlalchemy/events.py b/lib/sqlalchemy/events.py index a5ebb031a..10717ee50 100644 --- a/lib/sqlalchemy/events.py +++ b/lib/sqlalchemy/events.py @@ -17,11 +17,11 @@ class DDLEvents(event.Events): that is, :class:`.SchemaItem` and :class:`.SchemaEvent` subclasses, including :class:`.MetaData`, :class:`.Table`, :class:`.Column`. - + :class:`.MetaData` and :class:`.Table` support events specifically regarding when CREATE and DROP - DDL is emitted to the database. - + DDL is emitted to the database. + Attachment events are also provided to customize behavior whenever a child schema element is associated with a parent, such as, when a :class:`.Column` is associated @@ -37,14 +37,14 @@ class DDLEvents(event.Events): some_table = Table('some_table', m, Column('data', Integer)) def after_create(target, connection, **kw): - connection.execute("ALTER TABLE %s SET name=foo_%s" % + connection.execute("ALTER TABLE %s SET name=foo_%s" % (target.name, target.name)) event.listen(some_table, "after_create", after_create) - DDL events integrate closely with the + DDL events integrate closely with the :class:`.DDL` class and the :class:`.DDLElement` hierarchy - of DDL clause constructs, which are themselves appropriate + of DDL clause constructs, which are themselves appropriate as listener callables:: from sqlalchemy import DDL @@ -81,7 +81,7 @@ class DDLEvents(event.Events): to the event. The contents of this dictionary may vary across releases, and include the list of tables being generated for a metadata-level - event, the checkfirst flag, and other + event, the checkfirst flag, and other elements used by internal events. """ @@ -97,7 +97,7 @@ class DDLEvents(event.Events): to the event. The contents of this dictionary may vary across releases, and include the list of tables being generated for a metadata-level - event, the checkfirst flag, and other + event, the checkfirst flag, and other elements used by internal events. """ @@ -113,7 +113,7 @@ class DDLEvents(event.Events): to the event. The contents of this dictionary may vary across releases, and include the list of tables being generated for a metadata-level - event, the checkfirst flag, and other + event, the checkfirst flag, and other elements used by internal events. """ @@ -129,52 +129,52 @@ class DDLEvents(event.Events): to the event. The contents of this dictionary may vary across releases, and include the list of tables being generated for a metadata-level - event, the checkfirst flag, and other + event, the checkfirst flag, and other elements used by internal events. """ def before_parent_attach(self, target, parent): - """Called before a :class:`.SchemaItem` is associated with + """Called before a :class:`.SchemaItem` is associated with a parent :class:`.SchemaItem`. - + :param target: the target object :param parent: the parent to which the target is being attached. - + :func:`.event.listen` also accepts a modifier for this event: - + :param propagate=False: When True, the listener function will be established for any copies made of the target object, i.e. those copies that are generated when :meth:`.Table.tometadata` is used. - + """ def after_parent_attach(self, target, parent): - """Called after a :class:`.SchemaItem` is associated with + """Called after a :class:`.SchemaItem` is associated with a parent :class:`.SchemaItem`. :param target: the target object :param parent: the parent to which the target is being attached. - + :func:`.event.listen` also accepts a modifier for this event: - + :param propagate=False: When True, the listener function will be established for any copies made of the target object, i.e. those copies that are generated when :meth:`.Table.tometadata` is used. - + """ def column_reflect(self, inspector, table, column_info): """Called for each unit of 'column info' retrieved when - a :class:`.Table` is being reflected. - + a :class:`.Table` is being reflected. + The dictionary of column information as returned by the dialect is passed, and can be modified. The dictionary - is that returned in each element of the list returned - by :meth:`.reflection.Inspector.get_columns`. - + is that returned in each element of the list returned + by :meth:`.reflection.Inspector.get_columns`. + The event is called before any action is taken against this dictionary, and the contents can be modified. The :class:`.Column` specific arguments ``info``, ``key``, @@ -182,45 +182,46 @@ class DDLEvents(event.Events): will be passed to the constructor of :class:`.Column`. Note that this event is only meaningful if either - associated with the :class:`.Table` class across the + associated with the :class:`.Table` class across the board, e.g.:: - + from sqlalchemy.schema import Table from sqlalchemy import event def listen_for_reflect(inspector, table, column_info): "receive a column_reflect event" # ... - + event.listen( - Table, - 'column_reflect', + Table, + 'column_reflect', listen_for_reflect) - + ...or with a specific :class:`.Table` instance using the ``listeners`` argument:: - + def listen_for_reflect(inspector, table, column_info): "receive a column_reflect event" # ... - + t = Table( - 'sometable', + 'sometable', autoload=True, listeners=[ ('column_reflect', listen_for_reflect) ]) - + This because the reflection process initiated by ``autoload=True`` completes within the scope of the constructor for :class:`.Table`. - + """ class SchemaEventTarget(object): - """Base class for elements that are the targets of :class:`.DDLEvents` events. - + """Base class for elements that are the targets of :class:`.DDLEvents` + events. + This includes :class:`.SchemaItem` as well as :class:`.SchemaType`. - + """ dispatch = event.dispatcher(DDLEvents) @@ -230,9 +231,9 @@ class SchemaEventTarget(object): raise NotImplementedError() def _set_parent_with_dispatch(self, parent): - self.dispatch.before_parent_attach(self, parent) - self._set_parent(parent) - self.dispatch.after_parent_attach(self, parent) + self.dispatch.before_parent_attach(self, parent) + self._set_parent(parent) + self.dispatch.after_parent_attach(self, parent) class PoolEvents(event.Events): """Available events for :class:`.Pool`. @@ -250,11 +251,11 @@ class PoolEvents(event.Events): event.listen(Pool, 'checkout', my_on_checkout) - In addition to accepting 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:: + In addition to accepting 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:: engine = create_engine("postgresql://scott:tiger@localhost/test") @@ -334,11 +335,14 @@ class PoolEvents(event.Events): """ class ConnectionEvents(event.Events): - """Available events for :class:`.Connection`. + """Available events for :class:`.Connectable`, which includes + :class:`.Connection` and :class:`.Engine`. - The methods here define the name of an event as well as the names of members that are passed to listener functions. + The methods here define the name of an event as well as the names of + members that are passed to listener functions. - e.g.:: + An event listener can be associated with any :class:`.Connectable`, + such as an :class:`.Engine`, e.g.:: from sqlalchemy import event, create_engine @@ -348,15 +352,19 @@ class ConnectionEvents(event.Events): engine = create_engine('postgresql://scott:tiger@localhost/test') event.listen(engine, "before_execute", before_execute) - Some events allow modifiers to the listen() function. + Some events allow modifiers to the :func:`.event.listen` function. - :param retval=False: Applies to the :meth:`.before_execute` and + :param retval=False: Applies to the :meth:`.before_execute` and :meth:`.before_cursor_execute` events only. When True, the user-defined event function must have a return value, which - is a tuple of parameters that replace the given statement + is a tuple of parameters that replace the given statement and parameters. See those methods for a description of specific return arguments. + .. versionchanged:: 0.8 :class:`.ConnectionEvents` can now be associated + with any :class:`.Connectable` including :class:`.Connection`, + in addition to the existing support for :class:`.Engine`. + """ @classmethod @@ -366,20 +374,24 @@ class ConnectionEvents(event.Events): if not retval: if identifier == 'before_execute': orig_fn = fn - def wrap(conn, clauseelement, multiparams, params): + + def wrap_before_execute(conn, clauseelement, + multiparams, params): orig_fn(conn, clauseelement, multiparams, params) return clauseelement, multiparams, params - fn = wrap + fn = wrap_before_execute elif identifier == 'before_cursor_execute': orig_fn = fn - def wrap(conn, cursor, statement, + + def wrap_before_cursor_execute(conn, cursor, statement, parameters, context, executemany): - orig_fn(conn, cursor, statement, + orig_fn(conn, cursor, statement, parameters, context, executemany) return statement, parameters - fn = wrap + fn = wrap_before_cursor_execute - elif retval and identifier not in ('before_execute', 'before_cursor_execute'): + elif retval and \ + identifier not in ('before_execute', 'before_cursor_execute'): raise exc.ArgumentError( "Only the 'before_execute' and " "'before_cursor_execute' engine " @@ -393,40 +405,40 @@ class ConnectionEvents(event.Events): def after_execute(self, conn, clauseelement, multiparams, params, result): """Intercept high level execute() events.""" - def before_cursor_execute(self, conn, cursor, statement, + def before_cursor_execute(self, conn, cursor, statement, parameters, context, executemany): """Intercept low-level cursor execute() events.""" - def after_cursor_execute(self, conn, cursor, statement, + def after_cursor_execute(self, conn, cursor, statement, parameters, context, executemany): """Intercept low-level cursor execute() events.""" - def dbapi_error(self, conn, cursor, statement, parameters, + def dbapi_error(self, conn, cursor, statement, parameters, context, exception): """Intercept a raw DBAPI error. - - This event is called with the DBAPI exception instance - received from the DBAPI itself, *before* SQLAlchemy wraps the + + This event is called with the DBAPI exception instance + received from the DBAPI itself, *before* SQLAlchemy wraps the exception with it's own exception wrappers, and before any other operations are performed on the DBAPI cursor; the existing transaction remains in effect as well as any state on the cursor. - + The use case here is to inject low-level exception handling into an :class:`.Engine`, typically for logging and debugging purposes. In general, user code should **not** modify any state or throw any exceptions here as this will interfere with SQLAlchemy's cleanup and error handling routines. - + Subsequent to this hook, SQLAlchemy may attempt any number of operations on the connection/cursor, including - closing the cursor, rolling back of the transaction in the + closing the cursor, rolling back of the transaction in the case of connectionless execution, and disposing of the entire connection pool if a "disconnect" was detected. The exception is then wrapped in a SQLAlchemy DBAPI exception wrapper and re-thrown. - + .. versionadded:: 0.7.7 """ |