aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/common/dependency.py
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/common/dependency.py')
-rw-r--r--keystone-moon/keystone/common/dependency.py311
1 files changed, 311 insertions, 0 deletions
diff --git a/keystone-moon/keystone/common/dependency.py b/keystone-moon/keystone/common/dependency.py
new file mode 100644
index 00000000..14a68f19
--- /dev/null
+++ b/keystone-moon/keystone/common/dependency.py
@@ -0,0 +1,311 @@
+# Copyright 2012 OpenStack Foundation
+#
+# 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.
+
+"""This module provides support for dependency injection.
+
+Providers are registered via the ``@provider()`` decorator, and dependencies on
+them are registered with ``@requires()`` or ``@optional()``. Providers are
+available to their consumers via an attribute. See the documentation for the
+individual functions for more detail.
+
+See also:
+
+ https://en.wikipedia.org/wiki/Dependency_injection
+
+"""
+
+import traceback
+
+import six
+
+from keystone.i18n import _
+from keystone import notifications
+
+
+_REGISTRY = {}
+
+_future_dependencies = {}
+_future_optionals = {}
+_factories = {}
+
+
+def _set_provider(name, provider):
+ _original_provider, where_registered = _REGISTRY.get(name, (None, None))
+ if where_registered:
+ raise Exception('%s already has a registered provider, at\n%s' %
+ (name, ''.join(where_registered)))
+ _REGISTRY[name] = (provider, traceback.format_stack())
+
+
+GET_REQUIRED = object()
+GET_OPTIONAL = object()
+
+
+def get_provider(name, optional=GET_REQUIRED):
+ if optional is GET_REQUIRED:
+ return _REGISTRY[name][0]
+ return _REGISTRY.get(name, (None, None))[0]
+
+
+class UnresolvableDependencyException(Exception):
+ """Raised when a required dependency is not resolvable.
+
+ See ``resolve_future_dependencies()`` for more details.
+
+ """
+ def __init__(self, name, targets):
+ msg = _('Unregistered dependency: %(name)s for %(targets)s') % {
+ 'name': name, 'targets': targets}
+ super(UnresolvableDependencyException, self).__init__(msg)
+
+
+def provider(name):
+ """A class decorator used to register providers.
+
+ When ``@provider()`` is used to decorate a class, members of that class
+ will register themselves as providers for the named dependency. As an
+ example, In the code fragment::
+
+ @dependency.provider('foo_api')
+ class Foo:
+ def __init__(self):
+ ...
+
+ ...
+
+ foo = Foo()
+
+ The object ``foo`` will be registered as a provider for ``foo_api``. No
+ more than one such instance should be created; additional instances will
+ replace the previous ones, possibly resulting in different instances being
+ used by different consumers.
+
+ """
+ def wrapper(cls):
+ def wrapped(init):
+ def register_event_callbacks(self):
+ # NOTE(morganfainberg): A provider who has an implicit
+ # dependency on other providers may utilize the event callback
+ # mechanism to react to any changes in those providers. This is
+ # performed at the .provider() mechanism so that we can ensure
+ # that the callback is only ever called once and guaranteed
+ # to be on the properly configured and instantiated backend.
+ if not hasattr(self, 'event_callbacks'):
+ return
+
+ if not isinstance(self.event_callbacks, dict):
+ msg = _('event_callbacks must be a dict')
+ raise ValueError(msg)
+
+ for event in self.event_callbacks:
+ if not isinstance(self.event_callbacks[event], dict):
+ msg = _('event_callbacks[%s] must be a dict') % event
+ raise ValueError(msg)
+ for resource_type in self.event_callbacks[event]:
+ # Make sure we register the provider for each event it
+ # cares to call back.
+ callbacks = self.event_callbacks[event][resource_type]
+ if not callbacks:
+ continue
+ if not hasattr(callbacks, '__iter__'):
+ # ensure the callback information is a list
+ # allowing multiple callbacks to exist
+ callbacks = [callbacks]
+ notifications.register_event_callback(event,
+ resource_type,
+ callbacks)
+
+ def __wrapped_init__(self, *args, **kwargs):
+ """Initialize the wrapped object and add it to the registry."""
+ init(self, *args, **kwargs)
+ _set_provider(name, self)
+ register_event_callbacks(self)
+
+ resolve_future_dependencies(__provider_name=name)
+
+ return __wrapped_init__
+
+ cls.__init__ = wrapped(cls.__init__)
+ _factories[name] = cls
+ return cls
+ return wrapper
+
+
+def _process_dependencies(obj):
+ # Any dependencies that can be resolved immediately are resolved.
+ # Dependencies that cannot be resolved immediately are stored for
+ # resolution in resolve_future_dependencies.
+
+ def process(obj, attr_name, unresolved_in_out):
+ for dependency in getattr(obj, attr_name, []):
+ if dependency not in _REGISTRY:
+ # We don't know about this dependency, so save it for later.
+ unresolved_in_out.setdefault(dependency, []).append(obj)
+ continue
+
+ setattr(obj, dependency, get_provider(dependency))
+
+ process(obj, '_dependencies', _future_dependencies)
+ process(obj, '_optionals', _future_optionals)
+
+
+def requires(*dependencies):
+ """A class decorator used to inject providers into consumers.
+
+ The required providers will be made available to instances of the decorated
+ class via an attribute with the same name as the provider. For example, in
+ the code fragment::
+
+ @dependency.requires('foo_api', 'bar_api')
+ class FooBarClient:
+ def __init__(self):
+ ...
+
+ ...
+
+ client = FooBarClient()
+
+ The object ``client`` will have attributes named ``foo_api`` and
+ ``bar_api``, which are instances of the named providers.
+
+ Objects must not rely on the existence of these attributes until after
+ ``resolve_future_dependencies()`` has been called; they may not exist
+ beforehand.
+
+ Dependencies registered via ``@required()`` must have providers; if not,
+ an ``UnresolvableDependencyException`` will be raised when
+ ``resolve_future_dependencies()`` is called.
+
+ """
+ def wrapper(self, *args, **kwargs):
+ """Inject each dependency from the registry."""
+ self.__wrapped_init__(*args, **kwargs)
+ _process_dependencies(self)
+
+ def wrapped(cls):
+ """Note the required dependencies on the object for later injection.
+
+ The dependencies of the parent class are combined with that of the
+ child class to create a new set of dependencies.
+
+ """
+ existing_dependencies = getattr(cls, '_dependencies', set())
+ cls._dependencies = existing_dependencies.union(dependencies)
+ if not hasattr(cls, '__wrapped_init__'):
+ cls.__wrapped_init__ = cls.__init__
+ cls.__init__ = wrapper
+ return cls
+
+ return wrapped
+
+
+def optional(*dependencies):
+ """Similar to ``@requires()``, except that the dependencies are optional.
+
+ If no provider is available, the attributes will be set to ``None``.
+
+ """
+ def wrapper(self, *args, **kwargs):
+ """Inject each dependency from the registry."""
+ self.__wrapped_init__(*args, **kwargs)
+ _process_dependencies(self)
+
+ def wrapped(cls):
+ """Note the optional dependencies on the object for later injection.
+
+ The dependencies of the parent class are combined with that of the
+ child class to create a new set of dependencies.
+
+ """
+ existing_optionals = getattr(cls, '_optionals', set())
+ cls._optionals = existing_optionals.union(dependencies)
+ if not hasattr(cls, '__wrapped_init__'):
+ cls.__wrapped_init__ = cls.__init__
+ cls.__init__ = wrapper
+ return cls
+
+ return wrapped
+
+
+def resolve_future_dependencies(__provider_name=None):
+ """Forces injection of all dependencies.
+
+ Before this function is called, circular dependencies may not have been
+ injected. This function should be called only once, after all global
+ providers are registered. If an object needs to be created after this
+ call, it must not have circular dependencies.
+
+ If any required dependencies are unresolvable, this function will raise an
+ ``UnresolvableDependencyException``.
+
+ Outside of this module, this function should be called with no arguments;
+ the optional argument, ``__provider_name`` is used internally, and should
+ be treated as an implementation detail.
+
+ """
+ new_providers = dict()
+ if __provider_name:
+ # A provider was registered, so take care of any objects depending on
+ # it.
+ targets = _future_dependencies.pop(__provider_name, [])
+ targets.extend(_future_optionals.pop(__provider_name, []))
+
+ for target in targets:
+ setattr(target, __provider_name, get_provider(__provider_name))
+
+ return
+
+ # Resolve optional dependencies, sets the attribute to None if there's no
+ # provider registered.
+ for dependency, targets in six.iteritems(_future_optionals.copy()):
+ provider = get_provider(dependency, optional=GET_OPTIONAL)
+ if provider is None:
+ factory = _factories.get(dependency)
+ if factory:
+ provider = factory()
+ new_providers[dependency] = provider
+ for target in targets:
+ setattr(target, dependency, provider)
+
+ # Resolve future dependencies, raises UnresolvableDependencyException if
+ # there's no provider registered.
+ try:
+ for dependency, targets in six.iteritems(_future_dependencies.copy()):
+ if dependency not in _REGISTRY:
+ # a Class was registered that could fulfill the dependency, but
+ # it has not yet been initialized.
+ factory = _factories.get(dependency)
+ if factory:
+ provider = factory()
+ new_providers[dependency] = provider
+ else:
+ raise UnresolvableDependencyException(dependency, targets)
+
+ for target in targets:
+ setattr(target, dependency, get_provider(dependency))
+ finally:
+ _future_dependencies.clear()
+ return new_providers
+
+
+def reset():
+ """Reset the registry of providers.
+
+ This is useful for unit testing to ensure that tests don't use providers
+ from previous tests.
+ """
+
+ _REGISTRY.clear()
+ _future_dependencies.clear()
+ _future_optionals.clear()