diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-02-29 14:40:45 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-03-02 17:24:19 -0500 |
| commit | 57dc36a01b2b334a996f73f6a78b3bfbe4d9f2ec (patch) | |
| tree | 77cbb0199ca91be3b0816e3a5bd4c217e36a7d1b /lib/sqlalchemy/engine | |
| parent | 649de79950dcf952d7a44069faf36925c23c4e63 (diff) | |
| download | sqlalchemy-57dc36a01b2b334a996f73f6a78b3bfbe4d9f2ec.tar.gz | |
Ensure all nested exception throws have a cause
Applied an explicit "cause" to most if not all internally raised exceptions
that are raised from within an internal exception catch, to avoid
misleading stacktraces that suggest an error within the handling of an
exception. While it would be preferable to suppress the internally caught
exception in the way that the ``__suppress_context__`` attribute would,
there does not as yet seem to be a way to do this without suppressing an
enclosing user constructed context, so for now it exposes the internally
caught exception as the cause so that full information about the context
of the error is maintained.
Fixes: #4849
Change-Id: I55a86b29023675d9e5e49bc7edc5a2dc0bcd4751
Diffstat (limited to 'lib/sqlalchemy/engine')
| -rw-r--r-- | lib/sqlalchemy/engine/base.py | 31 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/result.py | 70 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/row.py | 6 |
3 files changed, 64 insertions, 43 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index ce6c2e9c6..449f386ce 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -996,8 +996,10 @@ class Connection(Connectable): return self._execute_text(object_, multiparams, params) try: meth = object_._execute_on_connection - except AttributeError: - raise exc.ObjectNotExecutableError(object_) + except AttributeError as err: + util.raise_( + exc.ObjectNotExecutableError(object_), replace_context=err + ) else: return meth(self, multiparams, params) @@ -1400,7 +1402,7 @@ class Connection(Connectable): invalidate_pool_on_disconnect = not is_exit_exception if self._reentrant_error: - util.raise_from_cause( + util.raise_( exc.DBAPIError.instance( statement, parameters, @@ -1412,7 +1414,8 @@ class Connection(Connectable): if context is not None else None, ), - exc_info, + with_traceback=exc_info[2], + from_=e, ) self._reentrant_error = True try: @@ -1502,11 +1505,13 @@ class Connection(Connectable): self._autorollback() if newraise: - util.raise_from_cause(newraise, exc_info) + util.raise_(newraise, with_traceback=exc_info[2], from_=e) elif should_wrap: - util.raise_from_cause(sqlalchemy_exception, exc_info) + util.raise_( + sqlalchemy_exception, with_traceback=exc_info[2], from_=e + ) else: - util.reraise(*exc_info) + util.raise_(exc_info[1], with_traceback=exc_info[2]) finally: del self._reentrant_error @@ -1573,11 +1578,13 @@ class Connection(Connectable): ) = ctx.is_disconnect if newraise: - util.raise_from_cause(newraise, exc_info) + util.raise_(newraise, with_traceback=exc_info[2], from_=e) elif should_wrap: - util.raise_from_cause(sqlalchemy_exception, exc_info) + util.raise_( + sqlalchemy_exception, with_traceback=exc_info[2], from_=e + ) else: - util.reraise(*exc_info) + util.raise_(exc_info[1], with_traceback=exc_info[2]) def _run_ddl_visitor(self, visitorcallable, element, **kwargs): """run a DDL visitor. @@ -2329,7 +2336,9 @@ class Engine(Connectable, log.Identified): e, dialect, self ) else: - util.reraise(*sys.exc_info()) + util.raise_( + sys.exc_info()[1], with_traceback=sys.exc_info()[2] + ) def raw_connection(self, _connection=None): """Return a "raw" DBAPI connection from the connection pool. diff --git a/lib/sqlalchemy/engine/result.py b/lib/sqlalchemy/engine/result.py index 1a63c307b..7db9eecae 100644 --- a/lib/sqlalchemy/engine/result.py +++ b/lib/sqlalchemy/engine/result.py @@ -53,11 +53,11 @@ class ResultMetaData(object): def _has_key(self, key): return key in self._keymap - def _key_fallback(self, key): + def _key_fallback(self, key, err): if isinstance(key, int): - raise IndexError(key) + util.raise_(IndexError(key), replace_context=err) else: - raise KeyError(key) + util.raise_(KeyError(key), replace_context=err) class SimpleResultMetaData(ResultMetaData): @@ -546,11 +546,14 @@ class CursorResultMetaData(ResultMetaData): ) in self._colnames_from_description(context, cursor_description): yield idx, colname, sqltypes.NULLTYPE, coltype, None, untranslated - def _key_fallback(self, key, raiseerr=True): + def _key_fallback(self, key, err, raiseerr=True): if raiseerr: - raise exc.NoSuchColumnError( - "Could not locate column in row for column '%s'" - % util.string_or_unprintable(key) + util.raise_( + exc.NoSuchColumnError( + "Could not locate column in row for column '%s'" + % util.string_or_unprintable(key) + ), + replace_context=err, ) else: return None @@ -570,8 +573,8 @@ class CursorResultMetaData(ResultMetaData): def _getter(self, key, raiseerr=True): try: rec = self._keymap[key] - except KeyError: - rec = self._key_fallback(key, raiseerr) + except KeyError as ke: + rec = self._key_fallback(key, ke, raiseerr) if rec is None: return None @@ -598,8 +601,8 @@ class CursorResultMetaData(ResultMetaData): for key in keys: try: rec = self._keymap[key] - except KeyError: - rec = self._key_fallback(key, raiseerr) + except KeyError as ke: + rec = self._key_fallback(key, ke, raiseerr) if rec is None: return None @@ -656,9 +659,9 @@ class LegacyCursorResultMetaData(CursorResultMetaData): ) return True else: - return self._key_fallback(key, False) is not None + return self._key_fallback(key, None, False) is not None - def _key_fallback(self, key, raiseerr=True): + def _key_fallback(self, key, err, raiseerr=True): map_ = self._keymap result = None @@ -714,9 +717,12 @@ class LegacyCursorResultMetaData(CursorResultMetaData): ) if result is None: if raiseerr: - raise exc.NoSuchColumnError( - "Could not locate column in row for column '%s'" - % util.string_or_unprintable(key) + util.raise_( + exc.NoSuchColumnError( + "Could not locate column in row for column '%s'" + % util.string_or_unprintable(key) + ), + replace_context=err, ) else: return None @@ -736,7 +742,7 @@ class LegacyCursorResultMetaData(CursorResultMetaData): if key in self._keymap: return True else: - return self._key_fallback(key, False) is not None + return self._key_fallback(key, None, False) is not None class CursorFetchStrategy(object): @@ -807,9 +813,12 @@ class NoCursorDQLFetchStrategy(CursorFetchStrategy): def fetchall(self): return self._non_result([]) - def _non_result(self, default): + def _non_result(self, default, err=None): if self.closed: - raise exc.ResourceClosedError("This result object is closed.") + util.raise_( + exc.ResourceClosedError("This result object is closed."), + replace_context=err, + ) else: return default @@ -843,10 +852,13 @@ class NoCursorDMLFetchStrategy(CursorFetchStrategy): def fetchall(self): return self._non_result([]) - def _non_result(self, default): - raise exc.ResourceClosedError( - "This result object does not return rows. " - "It has been closed automatically." + def _non_result(self, default, err=None): + util.raise_( + exc.ResourceClosedError( + "This result object does not return rows. " + "It has been closed automatically." + ), + replace_context=err, ) @@ -1123,24 +1135,24 @@ class BaseResult(object): def _getter(self, key, raiseerr=True): try: getter = self._metadata._getter - except AttributeError: - return self.cursor_strategy._non_result(None) + except AttributeError as err: + return self.cursor_strategy._non_result(None, err) else: return getter(key, raiseerr) def _tuple_getter(self, key, raiseerr=True): try: getter = self._metadata._tuple_getter - except AttributeError: - return self.cursor_strategy._non_result(None) + except AttributeError as err: + return self.cursor_strategy._non_result(None, err) else: return getter(key, raiseerr) def _has_key(self, key): try: has_key = self._metadata._has_key - except AttributeError: - return self.cursor_strategy._non_result(None) + except AttributeError as err: + return self.cursor_strategy._non_result(None, err) else: return has_key(key) diff --git a/lib/sqlalchemy/engine/row.py b/lib/sqlalchemy/engine/row.py index 55d8c2249..b58b350e2 100644 --- a/lib/sqlalchemy/engine/row.py +++ b/lib/sqlalchemy/engine/row.py @@ -84,8 +84,8 @@ except ImportError: def _subscript_impl(self, key, ismapping): try: rec = self._keymap[key] - except KeyError: - rec = self._parent._key_fallback(key) + except KeyError as ke: + rec = self._parent._key_fallback(key, ke) except TypeError: # the non-C version detects a slice using TypeError. # this is pretty inefficient for the slice use case @@ -119,7 +119,7 @@ except ImportError: try: return self._get_by_key_impl_mapping(name) except KeyError as e: - raise AttributeError(e.args[0]) + util.raise_(AttributeError(e.args[0]), replace_context=e) class Row(BaseRow, collections_abc.Sequence): |
