diff options
author | Brant Knudson <bknudson@us.ibm.com> | 2013-07-19 16:40:35 -0500 |
---|---|---|
committer | Brant Knudson <bknudson@us.ibm.com> | 2013-07-31 12:03:41 -0500 |
commit | 3c6cc9e838cacd1f7c0a3cfc89b0f66b23851803 (patch) | |
tree | b56b1eb1685222bedd149adf1793e459f3c5cbbd | |
parent | 19081b834991d263d84c761dcf422a8c9faf40a1 (diff) | |
download | keystone-3c6cc9e838cacd1f7c0a3cfc89b0f66b23851803.tar.gz keystone-3c6cc9e838cacd1f7c0a3cfc89b0f66b23851803.tar.xz keystone-3c6cc9e838cacd1f7c0a3cfc89b0f66b23851803.zip |
Handle circular dependencies
The dependency injection code doesn't handle circular
dependencies. This change makes it so that the
dependency injection code allows circular dependencies.
Part of fix for bug 1204605
Change-Id: I8de166a352ac727c7ddf27bae420b7c7ab22415f
-rw-r--r-- | keystone/common/dependency.py | 33 | ||||
-rw-r--r-- | keystone/service.py | 3 | ||||
-rw-r--r-- | keystone/test.py | 2 | ||||
-rw-r--r-- | tests/test_injection.py | 27 |
4 files changed, 64 insertions, 1 deletions
diff --git a/keystone/common/dependency.py b/keystone/common/dependency.py index 3ed261cc..a640031d 100644 --- a/keystone/common/dependency.py +++ b/keystone/common/dependency.py @@ -16,6 +16,8 @@ REGISTRY = {} +_future_dependencies = {} + class UnresolvableDependencyException(Exception): def __init__(self, name): @@ -32,6 +34,8 @@ def provider(name): init(self, *args, **kwargs) REGISTRY[name] = self + resolve_future_dependencies(name) + return __wrapped_init__ cls.__init__ = wrapped(cls.__init__) @@ -48,7 +52,13 @@ def requires(*dependencies): for dependency in self._dependencies: if dependency not in REGISTRY: - raise UnresolvableDependencyException(dependency) + if dependency in _future_dependencies: + _future_dependencies[dependency] += [self] + else: + _future_dependencies[dependency] = [self] + + continue + setattr(self, dependency, REGISTRY[dependency]) def wrapped(cls): @@ -67,6 +77,26 @@ def requires(*dependencies): return wrapped +def resolve_future_dependencies(provider_name=None): + if provider_name: + targets = _future_dependencies.pop(provider_name, []) + + for target in targets: + setattr(target, provider_name, REGISTRY[provider_name]) + + return + + try: + for dependency, targets in _future_dependencies.iteritems(): + if dependency not in REGISTRY: + raise UnresolvableDependencyException(dependency) + + for target in targets: + setattr(target, dependency, REGISTRY[dependency]) + finally: + _future_dependencies.clear() + + def reset(): """Reset the registry of providers. @@ -75,3 +105,4 @@ def reset(): """ REGISTRY.clear() + _future_dependencies.clear() diff --git a/keystone/service.py b/keystone/service.py index 6b0c3708..775dfe5d 100644 --- a/keystone/service.py +++ b/keystone/service.py @@ -18,6 +18,7 @@ import routes from keystone import auth from keystone import catalog +from keystone.common import dependency from keystone.common import logging from keystone.common import wsgi from keystone import config @@ -44,6 +45,8 @@ DRIVERS = dict( trust_api=trust.Manager(), token_provider_api=token.provider.Manager()) +dependency.resolve_future_dependencies() + @logging.fail_gracefully def public_app_factory(global_conf, **local_conf): diff --git a/keystone/test.py b/keystone/test.py index d06ea4c5..55aca3c6 100644 --- a/keystone/test.py +++ b/keystone/test.py @@ -271,6 +271,8 @@ class TestCase(NoModule, unittest.TestCase): manager_name = '%s_api' % manager.__name__.split('.')[-1] setattr(self, manager_name, manager.Manager()) + dependency.resolve_future_dependencies() + def load_fixtures(self, fixtures): """Hacky basic and naive fixture loading based on a python module. diff --git a/tests/test_injection.py b/tests/test_injection.py index 08ccd7c7..36cd0126 100644 --- a/tests/test_injection.py +++ b/tests/test_injection.py @@ -21,6 +21,10 @@ from keystone.common import dependency class TestDependencyInjection(unittest.TestCase): + def tearDown(self): + dependency.reset() + super(TestDependencyInjection, self).tearDown() + def test_dependency_injection(self): class Interface(object): def do_work(self): @@ -165,6 +169,29 @@ class TestDependencyInjection(unittest.TestCase): with self.assertRaises(dependency.UnresolvableDependencyException): Consumer() + dependency.resolve_future_dependencies() + + def test_circular_dependency(self): + p1_name = uuid.uuid4().hex + p2_name = uuid.uuid4().hex + + @dependency.provider(p1_name) + @dependency.requires(p2_name) + class P1(object): + pass + + @dependency.provider(p2_name) + @dependency.requires(p1_name) + class P2(object): + pass + + p1 = P1() + p2 = P2() + + dependency.resolve_future_dependencies() + + self.assertIs(getattr(p1, p2_name), p2) + self.assertIs(getattr(p2, p1_name), p1) def test_reset(self): # Can reset the registry of providers. |