From: Mike Bayer Date: Thu, 11 Jul 2013 19:15:09 +0000 (-0400) Subject: Dialect.initialize() is not called a second time if an :class:`.Engine` X-Git-Tag: rel_0_8_3~101 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9a95a39b49a2fadb3e1674d083d7c3da2fd886a8;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Dialect.initialize() is not called a second time if an :class:`.Engine` is recreated, due to a disconnect error. This fixes a particular issue in the Oracle 8 dialect, but in general the dialect.initialize() phase should only be once per dialect. [ticket:2776] --- diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index d6a0b69621..145eb14971 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -6,6 +6,13 @@ .. changelog:: :version: 0.8.3 + :tickets: 2776 + + Dialect.initialize() is not called a second time if an :class:`.Engine` + is recreated, due to a disconnect error. This fixes a particular + issue in the Oracle 8 dialect, but in general the dialect.initialize() + phase should only be once per dialect. + .. change:: :tags: feature, sql :tickets: 722 diff --git a/lib/sqlalchemy/engine/strategies.py b/lib/sqlalchemy/engine/strategies.py index 4c81df8f06..8f30f1a2c7 100644 --- a/lib/sqlalchemy/engine/strategies.py +++ b/lib/sqlalchemy/engine/strategies.py @@ -155,6 +155,7 @@ class DefaultEngineStrategy(EngineStrategy): event.listen(pool, 'first_connect', on_connect) event.listen(pool, 'connect', on_connect) + @util.only_once def first_connect(dbapi_connection, connection_record): c = base.Connection(engine, connection=dbapi_connection) diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py index c2c5bddb85..8e36ea611d 100644 --- a/lib/sqlalchemy/util/__init__.py +++ b/lib/sqlalchemy/util/__init__.py @@ -28,7 +28,8 @@ from .langhelpers import iterate_attributes, class_hierarchy, \ duck_type_collection, assert_arg_type, symbol, dictlike_iteritems,\ classproperty, set_creation_order, warn_exception, warn, NoneType,\ constructor_copy, methods_equivalent, chop_traceback, asint,\ - generic_repr, counter, PluginLoader, hybridmethod, safe_reraise + generic_repr, counter, PluginLoader, hybridmethod, safe_reraise,\ + only_once from .deprecations import warn_deprecated, warn_pending_deprecation, \ deprecated, pending_deprecation diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index d82aefdeae..82822b086b 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -1042,6 +1042,20 @@ _SQLA_RE = re.compile(r'sqlalchemy/([a-z_]+/){0,2}[a-z_]+\.py') _UNITTEST_RE = re.compile(r'unit(?:2|test2?/)') +def only_once(fn): + """Decorate the given function to be a no-op after it is called exactly + once.""" + + once = [fn] + def go(*arg, **kw): + if once: + once_fn = once.pop() + return once_fn(*arg, **kw) + + return update_wrapper(go, fn) + + + def chop_traceback(tb, exclude_prefix=_UNITTEST_RE, exclude_suffix=_SQLA_RE): """Chop extraneous lines off beginning and end of a traceback. diff --git a/test/engine/test_reconnect.py b/test/engine/test_reconnect.py index dee2fd2169..1936cec081 100644 --- a/test/engine/test_reconnect.py +++ b/test/engine/test_reconnect.py @@ -348,6 +348,28 @@ class MockReconnectTest(fixtures.TestBase): list, result ) + def test_dialect_initialize_once(self): + from sqlalchemy.engine.base import Engine + from sqlalchemy.engine.url import URL + from sqlalchemy.engine.default import DefaultDialect + from sqlalchemy.pool import QueuePool + dbapi = self.dbapi + + mock_dialect = Mock() + class MyURL(URL): + def get_dialect(self): + return Dialect + class Dialect(DefaultDialect): + initialize = Mock() + + engine = create_engine(MyURL("foo://"), module=dbapi) + c1 = engine.connect() + engine.dispose() + c2 = engine.connect() + eq_(Dialect.initialize.call_count, 1) + + + class CursorErrTest(fixtures.TestBase): def setup(self): @@ -493,9 +515,15 @@ class RealReconnectTest(fixtures.TestBase): # raises a DBAPIError, not an AttributeError assert_raises(exc.DBAPIError, engine.connect) - # dispose connections so we get a new one on - # next go - engine.dispose() + @testing.skip_if( + [lambda: util.py3k, "oracle+cx_oracle"], + "Crashes on py3k+cx_oracle") + def test_explode_in_initializer_disconnect(self): + engine = engines.testing_engine() + def broken_initialize(connection): + connection.execute("select fake_stuff from _fake_table") + + engine.dialect.initialize = broken_initialize p1 = engine.pool