diff options
| -rw-r--r-- | doc/build/changelog/changelog_10.rst | 12 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/mysql/base.py | 9 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/base.py | 4 | ||||
| -rw-r--r-- | lib/sqlalchemy/events.py | 10 | ||||
| -rw-r--r-- | test/engine/test_execute.py | 28 |
5 files changed, 59 insertions, 4 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 57ca3c863..e4671ed9b 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -18,6 +18,18 @@ .. 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 Changed the default value of "raise_on_warnings" to False for diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index 0c00cf530..06c3b0c50 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -2310,7 +2310,8 @@ class MySQLDialect(default.DefaultDialect): 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 @@ -2616,7 +2617,8 @@ class MySQLDialect(default.DefaultDialect): 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) @@ -2640,7 +2642,8 @@ class MySQLDialect(default.DefaultDialect): 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) diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index cf0689626..f9dfea32b 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -1112,7 +1112,9 @@ class Connection(Connectable): 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, diff --git a/lib/sqlalchemy/events.py b/lib/sqlalchemy/events.py index 42bbbfc0f..1ecec51b6 100644 --- a/lib/sqlalchemy/events.py +++ b/lib/sqlalchemy/events.py @@ -733,6 +733,16 @@ class ConnectionEvents(event.Events): .. 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): diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py index 33e116cdf..50cf41311 100644 --- a/test/engine/test_execute.py +++ b/test/engine/test_execute.py @@ -1659,6 +1659,34 @@ class HandleErrorTest(fixtures.TestBase): 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() |
