diff options
Diffstat (limited to 'lib/sqlalchemy/engine')
-rw-r--r-- | lib/sqlalchemy/engine/__init__.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/base.py | 91 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/interfaces.py | 102 |
3 files changed, 165 insertions, 29 deletions
diff --git a/lib/sqlalchemy/engine/__init__.py b/lib/sqlalchemy/engine/__init__.py index fcb38b09c..9c6460858 100644 --- a/lib/sqlalchemy/engine/__init__.py +++ b/lib/sqlalchemy/engine/__init__.py @@ -54,6 +54,7 @@ from .interfaces import ( Connectable, Dialect, ExecutionContext, + ExceptionContext, # backwards compat Compiled, diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index 67772f131..6da41927f 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -12,8 +12,8 @@ from __future__ import with_statement import sys from .. import exc, util, log, interfaces -from ..sql import expression, util as sql_util, schema, ddl -from .interfaces import Connectable, Compiled +from ..sql import util as sql_util +from .interfaces import Connectable, ExceptionContext from .util import _distill_params import contextlib @@ -1096,28 +1096,51 @@ class Connection(Connectable): should_wrap = isinstance(e, self.dialect.dbapi.Error) or \ (statement is not None and context is None) + if should_wrap: + sqlalchemy_exception = exc.DBAPIError.instance( + statement, + parameters, + e, + self.dialect.dbapi.Error, + connection_invalidated=self._is_disconnect) + else: + sqlalchemy_exception = None + newraise = None - if should_wrap and context: - if self._has_events or self.engine._has_events: - for fn in self.dispatch.dbapi_error: - try: - # handler returns an exception; - # call next handler in a chain - per_fn = fn(self, - cursor, - statement, - parameters, - context, - newraise - if newraise - is not None else e) - if per_fn is not None: - newraise = per_fn - except Exception as _raised: - # handler raises an exception - stop processing - newraise = _raised + if self._has_events or self.engine._has_events: + # legacy dbapi_error event + if should_wrap and context: + self.dispatch.dbapi_error(self, + cursor, + statement, + parameters, + context, + e) + + # new handle_error event + ctx = ExceptionContextImpl( + e, sqlalchemy_exception, self, cursor, statement, + parameters, context, self._is_disconnect) + + for fn in self.dispatch.handle_error: + try: + # handler returns an exception; + # call next handler in a chain + per_fn = fn(ctx) + if per_fn is not None: + ctx.chained_exception = newraise = per_fn + except Exception as _raised: + # handler raises an exception - stop processing + newraise = _raised + break + + if sqlalchemy_exception and \ + self._is_disconnect != ctx.is_disconnect: + sqlalchemy_exception.connection_invalidated = \ + self._is_disconnect = ctx.is_disconnect + if should_wrap and context: context.handle_dbapi_exception(e) if not self._is_disconnect: @@ -1129,16 +1152,11 @@ class Connection(Connectable): util.raise_from_cause(newraise, exc_info) elif should_wrap: util.raise_from_cause( - exc.DBAPIError.instance( - statement, - parameters, - e, - self.dialect.dbapi.Error, - connection_invalidated=self._is_disconnect), + sqlalchemy_exception, exc_info ) - - util.reraise(*exc_info) + else: + util.reraise(*exc_info) finally: del self._reentrant_error @@ -1224,6 +1242,21 @@ class Connection(Connectable): **kwargs).traverse_single(element) +class ExceptionContextImpl(ExceptionContext): + """Implement the :class:`.ExceptionContext` interface.""" + + def __init__(self, exception, sqlalchemy_exception, + connection, cursor, statement, parameters, + context, is_disconnect): + self.connection = connection + self.sqlalchemy_exception = sqlalchemy_exception + self.original_exception = exception + self.execution_context = context + self.statement = statement + self.parameters = parameters + self.is_disconnect = is_disconnect + + class Transaction(object): """Represent a database transaction in progress. diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py index 230d00fc0..1807e4283 100644 --- a/lib/sqlalchemy/engine/interfaces.py +++ b/lib/sqlalchemy/engine/interfaces.py @@ -882,3 +882,105 @@ class Connectable(object): def _execute_clauseelement(self, elem, multiparams=None, params=None): raise NotImplementedError() + +class ExceptionContext(object): + """Encapsulate information about an error condition in progress. + + This object exists solely to be passed to the + :meth:`.ConnectionEvents.handle_error` event, supporting an interface that + can be extended without backwards-incompatibility. + + .. versionadded:: 0.9.7 + + """ + + connection = None + """The :class:`.Connection` in use during the exception. + + This member is always present. + + """ + + cursor = None + """The DBAPI cursor object. + + May be None. + + """ + + statement = None + """String SQL statement that was emitted directly to the DBAPI. + + May be None. + + """ + + parameters = None + """Parameter collection that was emitted directly to the DBAPI. + + May be None. + + """ + + original_exception = None + """The exception object which was caught. + + This member is always present. + + """ + + sqlalchemy_exception = None + """The :class:`sqlalchemy.exc.StatementError` which wraps the original, + and will be raised if exception handling is not circumvented by the event. + + May be None, as not all exception types are wrapped by SQLAlchemy. + For DBAPI-level exceptions that subclass the dbapi's Error class, this + field will always be present. + + """ + + chained_exception = None + """The exception that was returned by the previous handler in the + exception chain, if any. + + If present, this exception will be the one ultimately raised by + SQLAlchemy unless a subsequent handler replaces it. + + May be None. + + """ + + execution_context = None + """The :class:`.ExecutionContext` corresponding to the execution + operation in progress. + + This is present for statement execution operations, but not for + operations such as transaction begin/end. It also is not present when + the exception was raised before the :class:`.ExecutionContext` + could be constructed. + + Note that the :attr:`.ExceptionContext.statement` and + :attr:`.ExceptionContext.parameters` members may represent a + different value than that of the :class:`.ExecutionContext`, + potentially in the case where a + :meth:`.ConnectionEvents.before_cursor_execute` event or similar + modified the statement/parameters to be sent. + + May be None. + + """ + + is_disconnect = None + """Represent whether the exception as occurred represents a "disconnect" + condition. + + This flag will always be True or False within the scope of the + :meth:`.ConnectionEvents.handle_error` handler. + + SQLAlchemy will defer to this flag in order to determine whether or not + the connection should be invalidated subsequently. That is, by + assigning to this flag, a "disconnect" event which then results in + a connection and pool invalidation can be invoked or prevented by + changing this flag. + + """ |