.. changelog::
:version: 1.0.0
+ .. change::
+ :tags: mysql, bug
+
+ The MySQL dialect will now disable :meth:`.ConnectionEvents.handle_error`
+ events from firing for those statements which it uses internally
+ to detect if a table exists or not. This is achieved using an
+ execution option ``skip_user_error_events`` that disables the handle
+ error event for the scope of that execution. In this way, user code
+ that rewrites exceptions doesn't need to worry about the MySQL
+ dialect or other dialects that occasionally need to catch
+ SQLAlchemy specific exceptions.
+
.. change::
:tags: mysql, bug
:tickets: 2515
rs = None
try:
try:
- rs = connection.execute(st)
+ rs = connection.execution_options(
+ skip_user_error_events=True).execute(st)
have = rs.fetchone() is not None
rs.close()
return have
rp = None
try:
- rp = connection.execute(st)
+ rp = connection.execution_options(
+ skip_user_error_events=True).execute(st)
except exc.DBAPIError as e:
if self._extract_error_code(e.orig) == 1146:
raise exc.NoSuchTableError(full_name)
rp, rows = None, None
try:
try:
- rp = connection.execute(st)
+ rp = connection.execution_options(
+ skip_user_error_events=True).execute(st)
except exc.DBAPIError as e:
if self._extract_error_code(e.orig) == 1146:
raise exc.NoSuchTableError(full_name)
newraise = None
- if self._has_events or self.engine._has_events:
+ if (self._has_events or self.engine._has_events) and \
+ not self._execution_options.get(
+ 'skip_user_error_events', False):
# legacy dbapi_error event
if should_wrap and context:
self.dispatch.dbapi_error(self,
.. versionadded:: 0.9.7 Added the
:meth:`.ConnectionEvents.handle_error` hook.
+ .. versionchanged:: 1.0.0 The :meth:`.handle_error` event is
+ not fired off when a dialect makes use of the
+ ``skip_user_error_events`` execution option. This is used
+ by dialects which intend to catch SQLAlchemy-specific exceptions
+ within specific operations, such as when the MySQL dialect detects
+ a table not present within the ``has_table()`` dialect method.
+ Prior to 1.0.0, code which implements :meth:`.handle_error` needs
+ to ensure that exceptions thrown in these scenarios are re-raised
+ without modification.
+
"""
def engine_connect(self, conn, branch):
is_(ctx.is_disconnect, False)
is_(ctx.original_exception, nope)
+ def test_exception_event_disable_handlers(self):
+ engine = engines.testing_engine()
+
+ class MyException1(Exception):
+ pass
+
+ @event.listens_for(engine, 'handle_error')
+ def err1(context):
+ stmt = context.statement
+
+ if "ERROR_ONE" in str(stmt):
+ raise MyException1("my exception short circuit")
+
+ with engine.connect() as conn:
+ assert_raises(
+ tsa.exc.DBAPIError,
+ conn.execution_options(
+ skip_user_error_events=True
+ ).execute, "SELECT ERROR_ONE FROM I_DONT_EXIST"
+ )
+
+ assert_raises(
+ MyException1,
+ conn.execution_options(
+ skip_user_error_events=False
+ ).execute, "SELECT ERROR_ONE FROM I_DONT_EXIST"
+ )
+
def _test_alter_disconnect(self, orig_error, evt_value):
engine = engines.testing_engine()