diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/sqlalchemy/engine/base.py | 72 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/default.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/event.py | 171 | ||||
-rw-r--r-- | lib/sqlalchemy/events.py | 229 |
4 files changed, 352 insertions, 122 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index 83ed551af..bf3131994 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -63,7 +63,10 @@ class Connection(Connectable): self.__invalid = False if _dispatch: self.dispatch = _dispatch + elif engine._has_events: + self.dispatch = self.dispatch._join(engine.dispatch) self._has_events = _has_events or engine._has_events + self._echo = self.engine._should_log_info() if _execution_options: self._execution_options =\ @@ -429,7 +432,6 @@ class Connection(Connectable): if self._has_events: self.dispatch.begin(self) - self.engine.dispatch.begin(self) try: self.engine.dialect.do_begin(self.connection) @@ -440,7 +442,6 @@ class Connection(Connectable): def _rollback_impl(self): if self._has_events: self.dispatch.rollback(self) - self.engine.dispatch.rollback(self) if self._still_open_and_connection_is_valid: if self._echo: @@ -457,7 +458,6 @@ class Connection(Connectable): def _commit_impl(self): if self._has_events: self.dispatch.commit(self) - self.engine.dispatch.commit(self) if self._echo: self.engine.logger.info("COMMIT") @@ -471,7 +471,6 @@ class Connection(Connectable): def _savepoint_impl(self, name=None): if self._has_events: self.dispatch.savepoint(self, name) - self.engine.dispatch.savepoint(self, name) if name is None: self.__savepoint_seq += 1 @@ -483,7 +482,6 @@ class Connection(Connectable): def _rollback_to_savepoint_impl(self, name, context): if self._has_events: self.dispatch.rollback_savepoint(self, name, context) - self.engine.dispatch.rollback_savepoint(self, name, context) if self._still_open_and_connection_is_valid: self.engine.dialect.do_rollback_to_savepoint(self, name) @@ -492,7 +490,6 @@ class Connection(Connectable): def _release_savepoint_impl(self, name, context): if self._has_events: self.dispatch.release_savepoint(self, name, context) - self.engine.dispatch.release_savepoint(self, name, context) if self._still_open_and_connection_is_valid: self.engine.dialect.do_release_savepoint(self, name) @@ -501,7 +498,6 @@ class Connection(Connectable): def _begin_twophase_impl(self, xid): if self._has_events: self.dispatch.begin_twophase(self, xid) - self.engine.dispatch.begin_twophase(self, xid) if self._still_open_and_connection_is_valid: self.engine.dialect.do_begin_twophase(self, xid) @@ -509,7 +505,6 @@ class Connection(Connectable): def _prepare_twophase_impl(self, xid): if self._has_events: self.dispatch.prepare_twophase(self, xid) - self.engine.dispatch.prepare_twophase(self, xid) if self._still_open_and_connection_is_valid: assert isinstance(self.__transaction, TwoPhaseTransaction) @@ -518,7 +513,6 @@ class Connection(Connectable): def _rollback_twophase_impl(self, xid, is_prepared): if self._has_events: self.dispatch.rollback_twophase(self, xid, is_prepared) - self.engine.dispatch.rollback_twophase(self, xid, is_prepared) if self._still_open_and_connection_is_valid: assert isinstance(self.__transaction, TwoPhaseTransaction) @@ -528,7 +522,6 @@ class Connection(Connectable): def _commit_twophase_impl(self, xid, is_prepared): if self._has_events: self.dispatch.commit_twophase(self, xid, is_prepared) - self.engine.dispatch.commit_twophase(self, xid, is_prepared) if self._still_open_and_connection_is_valid: assert isinstance(self.__transaction, TwoPhaseTransaction) @@ -658,10 +651,7 @@ class Connection(Connectable): """Execute a schema.ColumnDefault object.""" if self._has_events: - for fn in chain( - self.dispatch.before_execute, - self.engine.dispatch.before_execute - ): + for fn in self.dispatch.before_execute: default, multiparams, params = \ fn(self, default, multiparams, params) @@ -685,8 +675,6 @@ class Connection(Connectable): if self._has_events: self.dispatch.after_execute(self, default, multiparams, params, ret) - self.engine.dispatch.after_execute(self, - default, multiparams, params, ret) return ret @@ -694,10 +682,7 @@ class Connection(Connectable): """Execute a schema.DDL object.""" if self._has_events: - for fn in chain( - self.dispatch.before_execute, - self.engine.dispatch.before_execute - ): + for fn in self.dispatch.before_execute: ddl, multiparams, params = \ fn(self, ddl, multiparams, params) @@ -712,7 +697,7 @@ class Connection(Connectable): compiled ) if self._has_events: - self.engine.dispatch.after_execute(self, + self.dispatch.after_execute(self, ddl, multiparams, params, ret) return ret @@ -720,10 +705,7 @@ class Connection(Connectable): """Execute a sql.ClauseElement object.""" if self._has_events: - for fn in chain( - self.dispatch.before_execute, - self.engine.dispatch.before_execute - ): + for fn in self.dispatch.before_execute: elem, multiparams, params = \ fn(self, elem, multiparams, params) @@ -759,18 +741,13 @@ class Connection(Connectable): if self._has_events: self.dispatch.after_execute(self, elem, multiparams, params, ret) - self.engine.dispatch.after_execute(self, - elem, multiparams, params, ret) return ret def _execute_compiled(self, compiled, multiparams, params): """Execute a sql.Compiled object.""" if self._has_events: - for fn in chain( - self.dispatch.before_execute, - self.engine.dispatch.before_execute - ): + for fn in self.dispatch.before_execute: compiled, multiparams, params = \ fn(self, compiled, multiparams, params) @@ -786,18 +763,13 @@ class Connection(Connectable): if self._has_events: self.dispatch.after_execute(self, compiled, multiparams, params, ret) - self.engine.dispatch.after_execute(self, - compiled, multiparams, params, ret) return ret def _execute_text(self, statement, multiparams, params): """Execute a string SQL statement.""" if self._has_events: - for fn in chain( - self.dispatch.before_execute, - self.engine.dispatch.before_execute - ): + for fn in self.dispatch.before_execute: statement, multiparams, params = \ fn(self, statement, multiparams, params) @@ -813,8 +785,6 @@ class Connection(Connectable): if self._has_events: self.dispatch.after_execute(self, statement, multiparams, params, ret) - self.engine.dispatch.after_execute(self, - statement, multiparams, params, ret) return ret def _execute_context(self, dialect, constructor, @@ -847,10 +817,7 @@ class Connection(Connectable): parameters = parameters[0] if self._has_events: - for fn in chain( - self.dispatch.before_cursor_execute, - self.engine.dispatch.before_cursor_execute - ): + for fn in self.dispatch.before_cursor_execute: statement, parameters = \ fn(self, cursor, statement, parameters, context, context.executemany) @@ -893,11 +860,6 @@ class Connection(Connectable): parameters, context, context.executemany) - self.engine.dispatch.after_cursor_execute(self, cursor, - statement, - parameters, - context, - context.executemany) if context.compiled: context.post_exec() @@ -929,7 +891,7 @@ class Connection(Connectable): return result - def _cursor_execute(self, cursor, statement, parameters): + def _cursor_execute(self, cursor, statement, parameters, context=None): """Execute a statement + params on the given cursor. Adds appropriate logging and exception handling. @@ -940,6 +902,12 @@ class Connection(Connectable): terminates at _execute_context(). """ + if self._has_events: + for fn in self.dispatch.before_cursor_execute: + statement, parameters = \ + fn(self, cursor, statement, parameters, + context, context.executemany) + if self._echo: self.engine.logger.info(statement) self.engine.logger.info("%r", parameters) @@ -1006,12 +974,6 @@ class Connection(Connectable): parameters, context, e) - self.engine.dispatch.dbapi_error(self, - cursor, - statement, - parameters, - context, - e) context.handle_dbapi_exception(e) is_disconnect = isinstance(e, self.dialect.dbapi.Error) and \ diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 8518cf0da..1c74dab43 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -597,7 +597,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext): else: default_params = {} - conn._cursor_execute(self.cursor, stmt, default_params) + conn._cursor_execute(self.cursor, stmt, default_params, context=self) r = self.cursor.fetchone()[0] if type_ is not None: # apply type post processors to the result diff --git a/lib/sqlalchemy/event.py b/lib/sqlalchemy/event.py index 8e9064cfc..ee4c0ad32 100644 --- a/lib/sqlalchemy/event.py +++ b/lib/sqlalchemy/event.py @@ -7,6 +7,7 @@ """Base event API.""" from . import util, exc +from itertools import chain CANCEL = util.symbol('CANCEL') NO_RETVAL = util.symbol('NO_RETVAL') @@ -37,7 +38,7 @@ def listen(target, identifier, fn, *args, **kw): tgt.dispatch._listen(tgt, identifier, fn, *args, **kw) return raise exc.InvalidRequestError("No such event '%s' for target '%s'" % - (identifier,target)) + (identifier, target)) def listens_for(target, identifier, *args, **kw): """Decorate a function as a listener for the given target + identifier. @@ -69,7 +70,7 @@ def remove(target, identifier, fn): """ for evt_cls in _registrars[identifier]: for tgt in evt_cls._accept_with(target): - tgt.dispatch._remove(identifier, tgt, fn, *args, **kw) + tgt.dispatch._remove(identifier, tgt, fn) return _registrars = util.defaultdict(list) @@ -112,6 +113,30 @@ class _Dispatch(object): def __init__(self, _parent_cls): self._parent_cls = _parent_cls + def _join(self, other): + """Create a 'join' of this :class:`._Dispatch` and another. + + This new dispatcher will dispatch events to both + :class:`._Dispatch` objects. + + Once constructed, the joined dispatch will respond to new events + added to this dispatcher, but may not be aware of events + added to the other dispatcher after creation of the join. This is + currently for performance reasons so that both dispatchers need + not be "evaluated" fully on each call. + + """ + if '_joined_dispatch_cls' not in self.__class__.__dict__: + cls = type( + "Joined%s" % self.__class__.__name__, + (_JoinedDispatcher, self.__class__), {} + ) + for ls in _event_descriptors(self): + setattr(cls, ls.name, _JoinedDispatchDescriptor(ls.name)) + + self.__class__._joined_dispatch_cls = cls + return self._joined_dispatch_cls(self, other) + def __reduce__(self): return _UnpickleDispatch(), (self._parent_cls, ) @@ -276,6 +301,7 @@ class _DispatchDescriptor(object): obj.__dict__[self.__name__] = ret return ret + class _EmptyListener(object): """Serves as a class-level interface to the events served by a _DispatchDescriptor, when there are no @@ -303,8 +329,9 @@ class _EmptyListener(object): and returns it. """ - obj.__dict__[self.name] = result = _ListenerCollection( - self.parent, obj._parent_cls) + result = _ListenerCollection(self.parent, obj._parent_cls) + if obj.__dict__[self.name] is self: + obj.__dict__[self.name] = result return result def _needs_modify(self, *args, **kw): @@ -324,14 +351,47 @@ class _EmptyListener(object): def __iter__(self): return iter(self.parent_listeners) - def __getitem__(self, index): - return (self.parent_listeners)[index] + def __nonzero__(self): + return False + +class _CompoundListener(object): + _exec_once = False + + def exec_once(self, *args, **kw): + """Execute this event, but only if it has not been + executed already for this collection.""" + + if not self._exec_once: + self(*args, **kw) + self._exec_once = True + + # I'm not entirely thrilled about the overhead here, + # but this allows class-level listeners to be added + # at any point. + # + # In the absense of instance-level listeners, + # we stay with the _EmptyListener object when called + # at the instance level. + + def __call__(self, *args, **kw): + """Execute this event.""" + + for fn in self.parent_listeners: + fn(*args, **kw) + for fn in self.listeners: + fn(*args, **kw) + + def __len__(self): + return len(self.parent_listeners) + len(self.listeners) + + def __iter__(self): + return chain(self.parent_listeners, self.listeners) def __nonzero__(self): - return bool(self.listeners) + return bool(self.listeners or self.parent_listeners) -class _ListenerCollection(object): +class _ListenerCollection(_CompoundListener): """Instance-level attributes on instances of :class:`._Dispatch`. Represents a collection of listeners. @@ -341,8 +401,6 @@ class _ListenerCollection(object): """ - _exec_once = False - def __init__(self, parent, target_cls): if target_cls not in parent._clslevel: parent.update_subclass(target_cls) @@ -360,42 +418,6 @@ class _ListenerCollection(object): """ return self - def exec_once(self, *args, **kw): - """Execute this event, but only if it has not been - executed already for this collection.""" - - if not self._exec_once: - self(*args, **kw) - self._exec_once = True - - def __call__(self, *args, **kw): - """Execute this event.""" - - for fn in self.parent_listeners: - fn(*args, **kw) - for fn in self.listeners: - fn(*args, **kw) - - # I'm not entirely thrilled about the overhead here, - # but this allows class-level listeners to be added - # at any point. - # - # In the absense of instance-level listeners, - # we stay with the _EmptyListener object when called - # at the instance level. - - def __len__(self): - return len(self.parent_listeners + self.listeners) - - def __iter__(self): - return iter(self.parent_listeners + self.listeners) - - def __getitem__(self, index): - return (self.parent_listeners + self.listeners)[index] - - def __nonzero__(self): - return bool(self.listeners or self.parent_listeners) - def _update(self, other, only_propagate=True): """Populate from the listeners in another :class:`_Dispatch` object.""" @@ -430,6 +452,62 @@ class _ListenerCollection(object): self.listeners[:] = [] self.propagate.clear() + +class _JoinedDispatcher(object): + """Represent a connection between two _Dispatch objects.""" + + def __init__(self, local, parent): + self.local = local + self.parent = parent + self._parent_cls = local._parent_cls + + +class _JoinedDispatchDescriptor(object): + def __init__(self, name): + self.name = name + + def __get__(self, obj, cls): + if obj is None: + return self + else: + obj.__dict__[self.name] = ret = _JoinedListener( + obj.parent, self.name, + getattr(obj.local, self.name) + ) + return ret + +class _JoinedListener(_CompoundListener): + _exec_once = False + def __init__(self, parent, name, local): + self.parent = parent + self.name = name + self.local = local + self.parent_listeners = self.local + + # fix .listeners for the parent. This means + # new events added to the parent won't be picked + # up here. Alternatively, the listeners can + # be via @property to just return getattr(self.parent, self.name) + # each time. less performant. + self.listeners = list(getattr(self.parent, self.name)) + + def for_modify(self, obj): + self.local = self.parent_listeners = self.local.for_modify(obj) + return self + + def insert(self, obj, target, propagate): + self.local.insert(obj, target, propagate) + + def append(self, obj, target, propagate): + self.local.append(obj, target, propagate) + + def remove(self, obj, target): + self.local.remove(obj, target) + + def clear(self): + raise NotImplementedError() + + class dispatcher(object): """Descriptor used by target classes to deliver the _Dispatch class at the class level @@ -446,3 +524,4 @@ class dispatcher(object): return self.dispatch_cls obj.__dict__['dispatch'] = disp = self.dispatch_cls(cls) return disp + diff --git a/lib/sqlalchemy/events.py b/lib/sqlalchemy/events.py index 10717ee50..61392ea62 100644 --- a/lib/sqlalchemy/events.py +++ b/lib/sqlalchemy/events.py @@ -341,18 +341,56 @@ class ConnectionEvents(event.Events): The methods here define the name of an event as well as the names of members that are passed to listener functions. - An event listener can be associated with any :class:`.Connectable`, - such as an :class:`.Engine`, e.g.:: + An event listener can be associated with any :class:`.Connectable` + class or instance, such as an :class:`.Engine`, e.g.:: from sqlalchemy import event, create_engine - def before_execute(conn, clauseelement, multiparams, params): - log.info("Received statement: %s" % clauseelement) + def before_cursor_execute(conn, cursor, statement, parameters, context, + executemany): + log.info("Received statement: %s" % statement) engine = create_engine('postgresql://scott:tiger@localhost/test') - event.listen(engine, "before_execute", before_execute) + event.listen(engine, "before_cursor_execute", before_cursor_execute) - Some events allow modifiers to the :func:`.event.listen` function. + or with a specific :class:`.Connection`:: + + with engine.begin() as conn: + @event.listens_for(conn, 'before_cursor_execute') + def before_cursor_execute(conn, cursor, statement, parameters, + context, executemany): + log.info("Received statement: %s" % statement) + + The :meth:`.before_execute` and :meth:`.before_cursor_execute` + events can also be established with the ``retval=True`` flag, which + allows modification of the statement and parameters to be sent + to the database. The :meth:`.before_cursor_execute` event is + particularly useful here to add ad-hoc string transformations, such + as comments, to all executions:: + + from sqlalchemy.engine import Engine + from sqlalchemy import event + + @event.listens_for(Engine, "before_cursor_execute", retval=True) + def comment_sql_calls(conn, cursor, statement, parameters, + context, executemany): + statement = statement + " -- some comment" + return statement, parameters + + .. note:: :class:`.ConnectionEvents` can be established on any + combination of :class:`.Engine`, :class:`.Connection`, as well + as instances of each of those classes. Events across all + four scopes will fire off for a given instance of + :class:`.Connection`. However, for performance reasons, the + :class:`.Connection` object determines at instantiation time + whether or not its parent :class:`.Engine` has event listeners + established. Event listeners added to the :class:`.Engine` + class or to an instance of :class:`.Engine` *after* the instantiation + of a dependent :class:`.Connection` instance will usually + *not* be available on that :class:`.Connection` instance. The newly + added listeners will instead take effect for :class:`.Connection` + instances created subsequent to those event listeners being + established on the parent :class:`.Engine` class or instance. :param retval=False: Applies to the :meth:`.before_execute` and :meth:`.before_cursor_execute` events only. When True, the @@ -400,18 +438,107 @@ class ConnectionEvents(event.Events): event.Events._listen(target, identifier, fn) def before_execute(self, conn, clauseelement, multiparams, params): - """Intercept high level execute() events.""" + """Intercept high level execute() events, receiving uncompiled + SQL constructs and other objects prior to rendering into SQL. + + This event is good for debugging SQL compilation issues as well + as early manipulation of the parameters being sent to the database, + as the parameter lists will be in a consistent format here. + + This event can be optionally established with the ``retval=True`` + flag. The ``clauseelement``, ``multiparams``, and ``params`` + arguments should be returned as a three-tuple in this case:: + + @event.listens_for(Engine, "before_execute", retval=True) + def before_execute(conn, conn, clauseelement, multiparams, params): + # do something with clauseelement, multiparams, params + return clauseelement, multiparams, params + + :param conn: :class:`.Connection` object + :param clauseelement: SQL expression construct, :class:`.Compiled` + instance, or string statement passed to :meth:`.Connection.execute`. + :param multiparams: Multiple parameter sets, a list of dictionaries. + :param params: Single parameter set, a single dictionary. + + See also: + + :meth:`.before_cursor_execute` + + """ def after_execute(self, conn, clauseelement, multiparams, params, result): - """Intercept high level execute() events.""" + """Intercept high level execute() events after execute. + + + :param conn: :class:`.Connection` object + :param clauseelement: SQL expression construct, :class:`.Compiled` + instance, or string statement passed to :meth:`.Connection.execute`. + :param multiparams: Multiple parameter sets, a list of dictionaries. + :param params: Single parameter set, a single dictionary. + :param result: :class:`.ResultProxy` generated by the execution. + + """ def before_cursor_execute(self, conn, cursor, statement, parameters, context, executemany): - """Intercept low-level cursor execute() events.""" + """Intercept low-level cursor execute() events before execution, + receiving the string + SQL statement and DBAPI-specific parameter list to be invoked + against a cursor. + + This event is a good choice for logging as well as late modifications + to the SQL string. It's less ideal for parameter modifications except + for those which are specific to a target backend. + + This event can be optionally established with the ``retval=True`` + flag. The ``statement`` and ``parameters`` arguments should be + returned as a two-tuple in this case:: + + @event.listens_for(Engine, "before_cursor_execute", retval=True) + def before_cursor_execute(conn, cursor, statement, + parameters, context, executemany): + # do something with statement, parameters + return statement, parameters + + See the example at :class:`.ConnectionEvents`. + + :param conn: :class:`.Connection` object + :param cursor: DBAPI cursor object + :param statement: string SQL statement + :param parameters: Dictionary, tuple, or list of parameters being + passed to the ``execute()`` or ``executemany()`` method of the + DBAPI ``cursor``. In some cases may be ``None``. + :param context: :class:`.ExecutionContext` object in use. May + be ``None``. + :param executemany: boolean, if ``True``, this is an ``executemany()`` + call, if ``False``, this is an ``execute()`` call. + + See also: + + :meth:`.before_execute` + + :meth:`.after_cursor_execute` + + """ def after_cursor_execute(self, conn, cursor, statement, parameters, context, executemany): - """Intercept low-level cursor execute() events.""" + """Intercept low-level cursor execute() events after execution. + + :param conn: :class:`.Connection` object + :param cursor: DBAPI cursor object. Will have results pending + if the statement was a SELECT, but these should not be consumed + as they will be needed by the :class:`.ResultProxy`. + :param statement: string SQL statement + :param parameters: Dictionary, tuple, or list of parameters being + passed to the ``execute()`` or ``executemany()`` method of the + DBAPI ``cursor``. In some cases may be ``None``. + :param context: :class:`.ExecutionContext` object in use. May + be ``None``. + :param executemany: boolean, if ``True``, this is an ``executemany()`` + call, if ``False``, this is an ``execute()`` call. + + """ def dbapi_error(self, conn, cursor, statement, parameters, context, exception): @@ -439,37 +566,99 @@ class ConnectionEvents(event.Events): exception is then wrapped in a SQLAlchemy DBAPI exception wrapper and re-thrown. + :param conn: :class:`.Connection` object + :param cursor: DBAPI cursor object + :param statement: string SQL statement + :param parameters: Dictionary, tuple, or list of parameters being + passed to the ``execute()`` or ``executemany()`` method of the + DBAPI ``cursor``. In some cases may be ``None``. + :param context: :class:`.ExecutionContext` object in use. May + be ``None``. + :param exception: The **unwrapped** exception emitted directly from the + DBAPI. The class here is specific to the DBAPI module in use. + .. versionadded:: 0.7.7 """ def begin(self, conn): - """Intercept begin() events.""" + """Intercept begin() events. + + :param conn: :class:`.Connection` object + + """ def rollback(self, conn): - """Intercept rollback() events.""" + """Intercept rollback() events. + + :param conn: :class:`.Connection` object + + """ def commit(self, conn): - """Intercept commit() events.""" + """Intercept commit() events. + + :param conn: :class:`.Connection` object + """ def savepoint(self, conn, name=None): - """Intercept savepoint() events.""" + """Intercept savepoint() events. + + :param conn: :class:`.Connection` object + :param name: specified name used for the savepoint. + + """ def rollback_savepoint(self, conn, name, context): - """Intercept rollback_savepoint() events.""" + """Intercept rollback_savepoint() events. + + :param conn: :class:`.Connection` object + :param name: specified name used for the savepoint. + :param context: :class:`.ExecutionContext` in use. May be ``None``. + + """ def release_savepoint(self, conn, name, context): - """Intercept release_savepoint() events.""" + """Intercept release_savepoint() events. + + :param conn: :class:`.Connection` object + :param name: specified name used for the savepoint. + :param context: :class:`.ExecutionContext` in use. May be ``None``. + + """ def begin_twophase(self, conn, xid): - """Intercept begin_twophase() events.""" + """Intercept begin_twophase() events. + + :param conn: :class:`.Connection` object + :param xid: two-phase XID identifier + + """ def prepare_twophase(self, conn, xid): - """Intercept prepare_twophase() events.""" + """Intercept prepare_twophase() events. + + :param conn: :class:`.Connection` object + :param xid: two-phase XID identifier + """ def rollback_twophase(self, conn, xid, is_prepared): - """Intercept rollback_twophase() events.""" + """Intercept rollback_twophase() events. + + :param conn: :class:`.Connection` object + :param xid: two-phase XID identifier + :param is_prepared: boolean, indicates if + :meth:`.TwoPhaseTransaction.prepare` was called. + + """ def commit_twophase(self, conn, xid, is_prepared): - """Intercept commit_twophase() events.""" + """Intercept commit_twophase() events. + + :param conn: :class:`.Connection` object + :param xid: two-phase XID identifier + :param is_prepared: boolean, indicates if + :meth:`.TwoPhaseTransaction.prepare` was called. + + """ |