diff --git a/oslo/db/sqlalchemy/compat/__init__.py b/oslo/db/sqlalchemy/compat/__init__.py index 86436ec..4cd5ba6 100644 --- a/oslo/db/sqlalchemy/compat/__init__.py +++ b/oslo/db/sqlalchemy/compat/__init__.py @@ -12,6 +12,5 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_db.sqlalchemy.compat import engine_connect # noqa from oslo_db.sqlalchemy.compat import handle_error # noqa from oslo_db.sqlalchemy.compat import utils # noqa diff --git a/oslo_db/sqlalchemy/compat/__init__.py b/oslo_db/sqlalchemy/compat/__init__.py index 3dc29ac..0a054d8 100644 --- a/oslo_db/sqlalchemy/compat/__init__.py +++ b/oslo_db/sqlalchemy/compat/__init__.py @@ -16,13 +16,10 @@ added at some point but for which oslo.db provides a compatible versions for previous SQLAlchemy versions. """ -from oslo_db.sqlalchemy.compat import engine_connect as _e_conn from oslo_db.sqlalchemy.compat import handle_error as _h_err # trying to get: "from oslo_db.sqlalchemy import compat; compat.handle_error" # flake8 won't let me import handle_error directly -engine_connect = _e_conn.engine_connect handle_error = _h_err.handle_error -__all__ = [ - 'engine_connect', 'handle_error'] +__all__ = ['handle_error'] diff --git a/oslo_db/sqlalchemy/compat/engine_connect.py b/oslo_db/sqlalchemy/compat/engine_connect.py deleted file mode 100644 index 6b50fc6..0000000 --- a/oslo_db/sqlalchemy/compat/engine_connect.py +++ /dev/null @@ -1,60 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -"""Provide forwards compatibility for the engine_connect event. - -See the "engine_connect" event at -http://docs.sqlalchemy.org/en/rel_0_9/core/events.html. - - -""" - -from sqlalchemy.engine import Engine -from sqlalchemy import event - -from oslo_db.sqlalchemy.compat import utils - - -def engine_connect(engine, listener): - """Add an engine_connect listener for the given :class:`.Engine`. - - This listener uses the SQLAlchemy - :meth:`sqlalchemy.event.ConnectionEvents.engine_connect` - event for 0.9.0 and above, and implements an interim listener - for 0.8 versions. - - """ - if utils.sqla_090: - event.listen(engine, "engine_connect", listener) - return - - assert isinstance(engine, Engine), \ - "engine argument must be an Engine instance, not a Connection" - - if not getattr(engine._connection_cls, - '_oslo_engine_connect_wrapper', False): - engine._oslo_engine_connect_events = [] - - class Connection(engine._connection_cls): - _oslo_engine_connect_wrapper = True - - def __init__(self, *arg, **kw): - super(Connection, self).__init__(*arg, **kw) - - _oslo_engine_connect_events = getattr( - self.engine, - '_oslo_engine_connect_events', - False) - if _oslo_engine_connect_events: - for fn in _oslo_engine_connect_events: - fn(self, kw.get('_branch', False)) - engine._connection_cls = Connection - engine._oslo_engine_connect_events.append(listener) diff --git a/oslo_db/sqlalchemy/compat/handle_error.py b/oslo_db/sqlalchemy/compat/handle_error.py index 5169857..a7efcdc 100644 --- a/oslo_db/sqlalchemy/compat/handle_error.py +++ b/oslo_db/sqlalchemy/compat/handle_error.py @@ -32,8 +32,7 @@ def handle_error(engine, listener): This listener uses the SQLAlchemy :meth:`sqlalchemy.event.ConnectionEvents.handle_error` - event, however augments the listener for pre-0.9.7 versions of SQLAlchemy - in order to support safe re-raise of the exception. + event. """ if utils.sqla_100: @@ -43,122 +42,17 @@ def handle_error(engine, listener): assert isinstance(engine, Engine), \ "engine argument must be an Engine instance, not a Connection" - if not utils.sqla_097: - _rework_handle_exception_for_events(engine) - engine._oslo_handle_error_events.append(listener) + assert utils.sqla_097 _rework_connect_and_revalidate_for_events(engine) - if utils.sqla_097: - # ctx.engine added per - # https://bitbucket.org/zzzeek/sqlalchemy/issue/3266/ - def wrap_listener(ctx): - if isinstance(ctx, engine_base.ExceptionContextImpl): - ctx.engine = ctx.connection.engine - return listener(ctx) - event.listen(engine, "handle_error", wrap_listener) - - -def _rework_handle_exception_for_events(engine): - """Patch the _handle_dbapi_error() system on Connection. - - This allows the 0.9.7-style handle_error() event to be available on - the Connection object. - - """ - engine._oslo_handle_error_events = [] - - class Connection(engine._connection_cls): - def _handle_dbapi_exception(self, e, statement, parameters, - cursor, context): - - try: - super(Connection, self)._handle_dbapi_exception( - e, statement, parameters, cursor, context) - except Exception as reraised_exception: - # all versions: - # _handle_dbapi_exception reraises all DBAPI errors - # 0.8 and above: - # reraises all errors unconditionally - pass - else: - # 0.7.8: - # _handle_dbapi_exception does not unconditionally - # re-raise - reraised_exception = e - - _oslo_handle_error_events = getattr( - self.engine, - '_oslo_handle_error_events', - False) - - newraise = None - if _oslo_handle_error_events: - if isinstance(reraised_exception, - sqla_exc.StatementError): - sqlalchemy_exception = reraised_exception - original_exception = sqlalchemy_exception.orig - self._is_disconnect = is_disconnect = ( - isinstance(sqlalchemy_exception, - sqla_exc.DBAPIError) - and sqlalchemy_exception.connection_invalidated) - else: - sqlalchemy_exception = None - original_exception = reraised_exception - is_disconnect = False - - # new handle_error event - ctx = ExceptionContextImpl( - original_exception, sqlalchemy_exception, - self.engine, self, cursor, statement, - parameters, context, is_disconnect) - - for fn in _oslo_handle_error_events: - 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: - - if not ctx.is_disconnect: - raise NotImplementedError( - "Can't reset 'disconnect' status of exception " - "once it is set with this version of " - "SQLAlchemy") - - sqlalchemy_exception.connection_invalidated = \ - self._is_disconnect = ctx.is_disconnect - if self._is_disconnect: - self._do_disconnect(e) - - if newraise: - six.reraise(type(newraise), newraise, sys.exc_info()[2]) - else: - six.reraise(type(reraised_exception), - reraised_exception, sys.exc_info()[2]) - - def _do_disconnect(self, e): - del self._is_disconnect - if utils.sqla_094: - dbapi_conn_wrapper = self.connection - self.engine.pool._invalidate(dbapi_conn_wrapper, e) - self.invalidate(e) - else: - dbapi_conn_wrapper = self.connection - self.invalidate(e) - if not hasattr(dbapi_conn_wrapper, '_pool') or \ - dbapi_conn_wrapper._pool is self.engine.pool: - self.engine.dispose() - - engine._connection_cls = Connection + # ctx.engine added per + # https://bitbucket.org/zzzeek/sqlalchemy/issue/3266/ + def wrap_listener(ctx): + if isinstance(ctx, engine_base.ExceptionContextImpl): + ctx.engine = ctx.connection.engine + return listener(ctx) + event.listen(engine, "handle_error", wrap_listener) def _rework_connect_and_revalidate_for_events(engine): @@ -335,6 +229,8 @@ class ExceptionContextImpl(object): This is for forwards compatibility with the ExceptionContext interface introduced in SQLAlchemy 0.9.7. + It also provides for the "engine" argument added in SQLAlchemy 1.0.0. + """ def __init__(self, exception, sqlalchemy_exception, diff --git a/oslo_db/sqlalchemy/session.py b/oslo_db/sqlalchemy/session.py index ce347ce..f243652 100644 --- a/oslo_db/sqlalchemy/session.py +++ b/oslo_db/sqlalchemy/session.py @@ -286,6 +286,7 @@ import time from oslo_utils import timeutils import six +from sqlalchemy import event from sqlalchemy import exc import sqlalchemy.orm from sqlalchemy import pool @@ -295,7 +296,6 @@ from sqlalchemy.sql.expression import select from oslo_db._i18n import _LW from oslo_db import exception from oslo_db import options -from oslo_db.sqlalchemy import compat from oslo_db.sqlalchemy import exc_filters from oslo_db.sqlalchemy import update_match from oslo_db.sqlalchemy import utils @@ -408,7 +408,7 @@ def create_engine(sql_connection, sqlite_fk=False, mysql_sql_mode=None, exc_filters.register_engine(engine) # register engine connect handler - compat.engine_connect(engine, _connect_ping_listener) + event.listen(engine, "engine_connect", _connect_ping_listener) # initial connect + test _test_connection(engine, max_retries, retry_interval) diff --git a/oslo_db/tests/old_import_api/sqlalchemy/test_engine_connect.py b/oslo_db/tests/old_import_api/sqlalchemy/test_engine_connect.py deleted file mode 100644 index 54e359f..0000000 --- a/oslo_db/tests/old_import_api/sqlalchemy/test_engine_connect.py +++ /dev/null @@ -1,68 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Test the compatibility layer for the engine_connect() event. - -This event is added as of SQLAlchemy 0.9.0; oslo.db provides a compatibility -layer for prior SQLAlchemy versions. - -""" - -import mock -from oslotest import base as test_base -import sqlalchemy as sqla - -from oslo.db.sqlalchemy import compat - - -class EngineConnectTest(test_base.BaseTestCase): - - def setUp(self): - super(EngineConnectTest, self).setUp() - - self.engine = engine = sqla.create_engine("sqlite://") - self.addCleanup(engine.dispose) - - def test_connect_event(self): - engine = self.engine - - listener = mock.Mock() - compat.engine_connect(engine, listener) - - conn = engine.connect() - self.assertEqual( - listener.mock_calls, - [mock.call(conn, False)] - ) - - conn.close() - - conn2 = engine.connect() - conn2.close() - self.assertEqual( - listener.mock_calls, - [mock.call(conn, False), mock.call(conn2, False)] - ) - - def test_branch(self): - engine = self.engine - - listener = mock.Mock() - compat.engine_connect(engine, listener) - - conn = engine.connect() - branched = conn.connect() - conn.close() - self.assertEqual( - listener.mock_calls, - [mock.call(conn, False), mock.call(branched, True)] - ) diff --git a/oslo_db/tests/old_import_api/sqlalchemy/test_exc_filters.py b/oslo_db/tests/old_import_api/sqlalchemy/test_exc_filters.py index 77670be..0b1bb89 100644 --- a/oslo_db/tests/old_import_api/sqlalchemy/test_exc_filters.py +++ b/oslo_db/tests/old_import_api/sqlalchemy/test_exc_filters.py @@ -19,10 +19,10 @@ import mock from oslotest import base as oslo_test_base import six import sqlalchemy as sqla +from sqlalchemy import event from sqlalchemy.orm import mapper from oslo.db import exception -from oslo.db.sqlalchemy import compat from oslo.db.sqlalchemy import exc_filters from oslo.db.sqlalchemy import test_base from oslo_db.sqlalchemy import session as private_session @@ -719,7 +719,8 @@ class TestDBDisconnected(TestsExceptionFilter): dialect_name, exception, num_disconnects, is_disconnect=True): engine = self.engine - compat.engine_connect(engine, private_session._connect_ping_listener) + event.listen( + engine, "engine_connect", private_session._connect_ping_listener) real_do_execute = engine.dialect.do_execute counter = itertools.count(1) diff --git a/oslo_db/tests/sqlalchemy/test_engine_connect.py b/oslo_db/tests/sqlalchemy/test_engine_connect.py deleted file mode 100644 index c75511f..0000000 --- a/oslo_db/tests/sqlalchemy/test_engine_connect.py +++ /dev/null @@ -1,68 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Test the compatibility layer for the engine_connect() event. - -This event is added as of SQLAlchemy 0.9.0; oslo_db provides a compatibility -layer for prior SQLAlchemy versions. - -""" - -import mock -from oslotest import base as test_base -import sqlalchemy as sqla - -from oslo_db.sqlalchemy.compat import engine_connect - - -class EngineConnectTest(test_base.BaseTestCase): - - def setUp(self): - super(EngineConnectTest, self).setUp() - - self.engine = engine = sqla.create_engine("sqlite://") - self.addCleanup(engine.dispose) - - def test_connect_event(self): - engine = self.engine - - listener = mock.Mock() - engine_connect(engine, listener) - - conn = engine.connect() - self.assertEqual( - listener.mock_calls, - [mock.call(conn, False)] - ) - - conn.close() - - conn2 = engine.connect() - conn2.close() - self.assertEqual( - listener.mock_calls, - [mock.call(conn, False), mock.call(conn2, False)] - ) - - def test_branch(self): - engine = self.engine - - listener = mock.Mock() - engine_connect(engine, listener) - - conn = engine.connect() - branched = conn.connect() - conn.close() - self.assertEqual( - listener.mock_calls, - [mock.call(conn, False), mock.call(branched, True)] - ) diff --git a/oslo_db/tests/sqlalchemy/test_exc_filters.py b/oslo_db/tests/sqlalchemy/test_exc_filters.py index dc5de84..bafb59e 100644 --- a/oslo_db/tests/sqlalchemy/test_exc_filters.py +++ b/oslo_db/tests/sqlalchemy/test_exc_filters.py @@ -19,10 +19,10 @@ import mock from oslotest import base as oslo_test_base import six import sqlalchemy as sqla +from sqlalchemy import event from sqlalchemy.orm import mapper from oslo_db import exception -from oslo_db.sqlalchemy import compat from oslo_db.sqlalchemy import exc_filters from oslo_db.sqlalchemy import session from oslo_db.sqlalchemy import test_base @@ -784,7 +784,7 @@ class TestDBDisconnected(TestsExceptionFilter): dialect_name, exception, num_disconnects, is_disconnect=True): engine = self.engine - compat.engine_connect(engine, session._connect_ping_listener) + event.listen(engine, "engine_connect", session._connect_ping_listener) real_do_execute = engine.dialect.do_execute counter = itertools.count(1) @@ -966,7 +966,8 @@ class TestDBConnectPingWrapping(TestsExceptionFilter): def setUp(self): super(TestDBConnectPingWrapping, self).setUp() - compat.engine_connect(self.engine, session._connect_ping_listener) + event.listen( + self.engine, "engine_connect", session._connect_ping_listener) @contextlib.contextmanager def _fixture(