From 7a3e091c1c9cc9ae6e34279c7c99c9989afd8894 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 24 Sep 2014 15:04:53 -0400 Subject: Unwrap DialectFunctionDispatcher from itself. The DialectFunctionDispatcher.dispatch_for() decorator method necessarily returns the dispatcher itself and not the decorated function, so that the object can continue to be re-used even if the function name is the same as that of the dispatcher. In order to support a single function being wrapped by the dispatcher multiple times with different criteria, dispatch_for() will now check for the last function wrapped and use that. Change-Id: I331670d9b76ae30e7a666648e7e2d4c72641c9ff Closes-Bug: #1373568 --- oslo/db/sqlalchemy/utils.py | 3 +++ tests/sqlalchemy/test_utils.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/oslo/db/sqlalchemy/utils.py b/oslo/db/sqlalchemy/utils.py index adc9bef..71cd1ea 100644 --- a/oslo/db/sqlalchemy/utils.py +++ b/oslo/db/sqlalchemy/utils.py @@ -899,6 +899,9 @@ class DialectFunctionDispatcher(object): def dispatch_for(self, expr): def decorate(fn): dbname, driver = self._parse_dispatch(expr) + if fn is self: + fn = fn._last + self._last = fn self._register(expr, dbname, driver, fn) return self return decorate diff --git a/tests/sqlalchemy/test_utils.py b/tests/sqlalchemy/test_utils.py index a4cd3f2..742cd38 100644 --- a/tests/sqlalchemy/test_utils.py +++ b/tests/sqlalchemy/test_utils.py @@ -990,6 +990,41 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase): callable_fn.mock_calls ) + def test_multiple_nesting(self): + callable_fn = mock.Mock( + default=mock.Mock(return_value=None), + mysql=mock.Mock(return_value=None) + ) + + dispatcher = utils.dispatch_for_dialect("*", multiple=True)( + callable_fn.default) + + dispatcher = dispatcher.dispatch_for("mysql+mysqlconnector")( + dispatcher.dispatch_for("mysql+mysqldb")( + callable_fn.mysql + ) + ) + + mysqldb_url = sqlalchemy.engine.url.make_url("mysql+mysqldb://") + mysqlconnector_url = sqlalchemy.engine.url.make_url( + "mysql+mysqlconnector://") + sqlite_url = sqlalchemy.engine.url.make_url("sqlite://") + + dispatcher(mysqldb_url, 1) + dispatcher(mysqlconnector_url, 2) + dispatcher(sqlite_url, 3) + + self.assertEqual( + [ + mock.call.mysql(mysqldb_url, 1), + mock.call.default(mysqldb_url, 1), + mock.call.mysql(mysqlconnector_url, 2), + mock.call.default(mysqlconnector_url, 2), + mock.call.default(sqlite_url, 3) + ], + callable_fn.mock_calls + ) + def test_single_retval(self): dispatcher, callable_fn = self._single_fixture() callable_fn.mysql_mysqldb.return_value = 5 -- cgit v1.2.1