improve dependency injection doc strings

This also renames the provider_name argument to better convey it's
status as a kwarg for internal use only.

Change-Id: Ie1afee4e37cfa149ddb73a001985c98aa90b97a5
This commit is contained in:
Dolph Mathews
2014-09-10 14:31:05 +00:00
parent 8a5913be1f
commit 5e432fc528

View File

@@ -14,14 +14,15 @@
"""This module provides support for dependency injection. """This module provides support for dependency injection.
Providers are registered via the 'provider' decorator, and dependencies on them Providers are registered via the ``@provider()`` decorator, and dependencies on
are registered with 'requires' or 'optional'. Providers are available to their them are registered with ``@requires()`` or ``@optional()``. Providers are
consumers via an attribute. See the documentation for the individual functions available to their consumers via an attribute. See the documentation for the
for more detail. individual functions for more detail.
See also: See also:
https://en.wikipedia.org/wiki/Dependency_injection https://en.wikipedia.org/wiki/Dependency_injection
""" """
import six import six
@@ -38,8 +39,10 @@ _factories = {}
class UnresolvableDependencyException(Exception): class UnresolvableDependencyException(Exception):
"""An UnresolvableDependencyException is raised when a required dependency """Raised when a required dependency is not resolvable.
is not resolvable; see 'resolve_future_dependencies'.
See ``resolve_future_dependencies()`` for more details.
""" """
def __init__(self, name): def __init__(self, name):
msg = 'Unregistered dependency: %s' % name msg = 'Unregistered dependency: %s' % name
@@ -47,11 +50,11 @@ class UnresolvableDependencyException(Exception):
def provider(name): def provider(name):
"""'provider' is a class decorator used to register providers. """A class decorator used to register providers.
When 'provider' is used to decorate a class, members of that class will When ``@provider()`` is used to decorate a class, members of that class
register themselves as providers for the named dependency. As an example, will register themselves as providers for the named dependency. As an
In the code fragment:: example, In the code fragment::
@dependency.provider('foo_api') @dependency.provider('foo_api')
class Foo: class Foo:
@@ -62,10 +65,11 @@ def provider(name):
foo = Foo() foo = Foo()
The object 'foo' will be registered as a provider for 'foo_api'. No more The object ``foo`` will be registered as a provider for ``foo_api``. No
than one such instance should be created; additional instances will replace more than one such instance should be created; additional instances will
the previous ones, possibly resulting in different instances being used by replace the previous ones, possibly resulting in different instances being
different consumers. used by different consumers.
""" """
def wrapper(cls): def wrapper(cls):
def wrapped(init): def wrapped(init):
@@ -107,7 +111,7 @@ def provider(name):
REGISTRY[name] = self REGISTRY[name] = self
register_event_callbacks(self) register_event_callbacks(self)
resolve_future_dependencies(name) resolve_future_dependencies(__provider_name=name)
return __wrapped_init__ return __wrapped_init__
@@ -136,11 +140,11 @@ def _process_dependencies(obj):
def requires(*dependencies): def requires(*dependencies):
"""'requires' is a class decorator used to inject providers into consumers. """A class decorator used to inject providers into consumers.
The required providers will be made available to instances of the decorated 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, class via an attribute with the same name as the provider. For example, in
in the code fragment:: the code fragment::
@dependency.requires('foo_api', 'bar_api') @dependency.requires('foo_api', 'bar_api')
class FooBarClient: class FooBarClient:
@@ -151,15 +155,17 @@ def requires(*dependencies):
client = FooBarClient() client = FooBarClient()
The object 'client' will have attributes named 'foo_api' and 'bar_api', The object ``client`` will have attributes named ``foo_api`` and
which are instances of the named providers. ``bar_api``, which are instances of the named providers.
Objects must not rely on the existence of these attributes until after Objects must not rely on the existence of these attributes until after
'resolve_future_dependencies' has been called; they may not exist ``resolve_future_dependencies()`` has been called; they may not exist
beforehand. beforehand.
Dependencies registered via 'required' must have providers - if not, an Dependencies registered via ``@required()`` must have providers; if not,
exception will be raised when 'resolve_future_dependencies' is called. an ``UnresolvableDependencyException`` will be raised when
``resolve_future_dependencies()`` is called.
""" """
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
"""Inject each dependency from the registry.""" """Inject each dependency from the registry."""
@@ -171,6 +177,7 @@ def requires(*dependencies):
The dependencies of the parent class are combined with that of the The dependencies of the parent class are combined with that of the
child class to create a new set of dependencies. child class to create a new set of dependencies.
""" """
existing_dependencies = getattr(cls, '_dependencies', set()) existing_dependencies = getattr(cls, '_dependencies', set())
cls._dependencies = existing_dependencies.union(dependencies) cls._dependencies = existing_dependencies.union(dependencies)
@@ -183,8 +190,10 @@ def requires(*dependencies):
def optional(*dependencies): def optional(*dependencies):
"""'optional' is the same as 'requires', except that the dependencies are """Similar to ``@requires()``, except that the dependencies are optional.
optional - if no provider is available, the attributes will be set to None.
If no provider is available, the attributes will be set to ``None``.
""" """
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
"""Inject each dependency from the registry.""" """Inject each dependency from the registry."""
@@ -196,8 +205,8 @@ def optional(*dependencies):
The dependencies of the parent class are combined with that of the The dependencies of the parent class are combined with that of the
child class to create a new set of dependencies. child class to create a new set of dependencies.
"""
"""
existing_optionals = getattr(cls, '_optionals', set()) existing_optionals = getattr(cls, '_optionals', set())
cls._optionals = existing_optionals.union(dependencies) cls._optionals = existing_optionals.union(dependencies)
if not hasattr(cls, '__wrapped_init__'): if not hasattr(cls, '__wrapped_init__'):
@@ -208,8 +217,8 @@ def optional(*dependencies):
return wrapped return wrapped
def resolve_future_dependencies(provider_name=None): def resolve_future_dependencies(__provider_name=None):
"""'resolve_future_dependencies' forces injection of all dependencies. """Forces injection of all dependencies.
Before this function is called, circular dependencies may not have been Before this function is called, circular dependencies may not have been
injected. This function should be called only once, after all global injected. This function should be called only once, after all global
@@ -217,21 +226,22 @@ def resolve_future_dependencies(provider_name=None):
call, it must not have circular dependencies. call, it must not have circular dependencies.
If any required dependencies are unresolvable, this function will raise an If any required dependencies are unresolvable, this function will raise an
UnresolvableDependencyException. ``UnresolvableDependencyException``.
Outside of this module, this function should be called with no arguments; Outside of this module, this function should be called with no arguments;
the optional argument is used internally, and should be treated as an the optional argument, ``__provider_name`` is used internally, and should
implementation detail. be treated as an implementation detail.
""" """
new_providers = dict() new_providers = dict()
if provider_name: if __provider_name:
# A provider was registered, so take care of any objects depending on # A provider was registered, so take care of any objects depending on
# it. # it.
targets = _future_dependencies.pop(provider_name, []) targets = _future_dependencies.pop(__provider_name, [])
targets.extend(_future_optionals.pop(provider_name, [])) targets.extend(_future_optionals.pop(__provider_name, []))
for target in targets: for target in targets:
setattr(target, provider_name, REGISTRY[provider_name]) setattr(target, __provider_name, REGISTRY[__provider_name])
return return