python-dependency-injector/dependency_injector/injections.py

290 lines
8.2 KiB
Python
Raw Normal View History

2015-03-09 01:01:39 +03:00
"""Injections module."""
2015-01-10 12:24:25 +03:00
import sys
import itertools
import six
from .utils import is_provider
from .utils import is_delegated_provider
from .utils import is_injection
from .utils import is_arg_injection
from .utils import is_kwarg_injection
from .errors import Error
2015-01-10 12:24:25 +03:00
_IS_PYPY = '__pypy__' in sys.builtin_module_names
if _IS_PYPY or six.PY3: # pragma: no cover
_OBJECT_INIT = six.get_unbound_function(object.__init__)
else: # pragma: no cover
_OBJECT_INIT = None
2015-01-10 12:24:25 +03:00
2015-12-07 15:31:51 +03:00
@six.python_2_unicode_compatible
2015-01-10 12:24:25 +03:00
class Injection(object):
2015-11-24 11:33:10 +03:00
"""Base injection class.
All injections extend this class.
2015-12-14 11:22:15 +03:00
.. py:attribute:: injectable
Injectable value, could be provider or any other object.
:type: object | :py:class:`dependency_injector.providers.Provider`
.. py:attribute:: call_injectable
2015-12-14 11:22:15 +03:00
Flag that is set to ``True`` if it is needed to call injectable.
Injectable needs to be called if it is not delegated provider.
2015-12-14 11:22:15 +03:00
:type: bool
2015-11-24 11:33:10 +03:00
"""
2015-01-10 12:24:25 +03:00
2015-07-22 10:53:16 +03:00
__IS_INJECTION__ = True
__slots__ = ('injectable', 'call_injectable')
def __init__(self, injectable):
2015-11-24 11:33:10 +03:00
"""Initializer.
:param injectable: Injectable value, could be provider or any
other object.
:type injectable: object |
:py:class:`dependency_injector.providers.Provider`
"""
2015-01-10 12:24:25 +03:00
self.injectable = injectable
self.call_injectable = (is_provider(injectable) and
not is_delegated_provider(injectable))
2015-11-24 11:33:10 +03:00
super(Injection, self).__init__()
2015-01-10 12:24:25 +03:00
2015-01-11 16:03:45 +03:00
@property
def value(self):
2015-11-24 11:33:10 +03:00
"""Read-only property that represents injectable value.
Injectable values and delegated providers are provided "as is".
Other providers will be called every time, when injection needs to
be done.
2015-11-24 11:33:10 +03:00
:rtype: object
"""
if self.call_injectable:
2015-01-11 16:03:45 +03:00
return self.injectable()
return self.injectable
2015-01-10 12:24:25 +03:00
2015-12-09 20:28:52 +03:00
def __str__(self):
2015-12-07 15:31:51 +03:00
"""Return string representation of provider.
2015-01-10 12:24:25 +03:00
2015-12-07 15:31:51 +03:00
:rtype: str
"""
2015-12-09 20:28:52 +03:00
return '<{injection}({injectable}) at {address}>'.format(
injection='.'.join((self.__class__.__module__,
self.__class__.__name__)),
injectable=repr(self.injectable),
address=hex(id(self)))
2015-12-07 15:31:51 +03:00
__repr__ = __str__
class Arg(Injection):
"""Positional argument injection."""
__IS_ARG_INJECTION__ = True
@six.python_2_unicode_compatible
2015-11-16 14:28:27 +03:00
class _NamedInjection(Injection):
2015-12-14 11:22:15 +03:00
"""Base class of named injections.
.. py:attribute:: name
Injection target's name (keyword argument, attribute, method).
:type: str
"""
__slots__ = ('name',)
def __init__(self, name, injectable):
"""Initializer.
:param name: Injection target's name.
:type name: str
:param injectable: Injectable value, could be provider or any
other object.
:type injectable: object |
:py:class:`dependency_injector.providers.Provider`
"""
self.name = name
2015-11-16 14:28:27 +03:00
super(_NamedInjection, self).__init__(injectable)
2015-12-09 20:28:52 +03:00
def __str__(self):
2015-12-07 15:31:51 +03:00
"""Return string representation of provider.
2015-12-07 15:31:51 +03:00
:rtype: str
"""
2015-12-09 20:28:52 +03:00
return '<{injection}({name}, {injectable}) at {address}>'.format(
2015-12-07 15:31:51 +03:00
name=repr(self.name),
2015-12-09 20:28:52 +03:00
injection='.'.join((self.__class__.__module__,
self.__class__.__name__)),
injectable=repr(self.injectable),
address=hex(id(self)))
2015-12-07 15:31:51 +03:00
__repr__ = __str__
2015-11-16 14:28:27 +03:00
class KwArg(_NamedInjection):
2015-12-14 11:22:15 +03:00
"""Keyword argument injection.
.. py:attribute:: name
2015-01-10 12:24:25 +03:00
2015-12-14 11:22:15 +03:00
Keyword argument's name.
2015-12-14 11:22:15 +03:00
:type: str
"""
2015-07-22 10:53:16 +03:00
__IS_KWARG_INJECTION__ = True
2015-01-10 12:24:25 +03:00
2015-11-16 14:28:27 +03:00
class Attribute(_NamedInjection):
2015-12-14 11:22:15 +03:00
"""Attribute injection.
.. py:attribute:: name
2015-01-10 12:24:25 +03:00
2015-12-14 11:22:15 +03:00
Attribute's name.
2015-12-14 11:22:15 +03:00
:type: str
"""
2015-07-22 10:53:16 +03:00
__IS_ATTRIBUTE_INJECTION__ = True
2015-01-10 12:24:25 +03:00
2015-11-16 14:28:27 +03:00
class Method(_NamedInjection):
2015-12-14 11:22:15 +03:00
"""Method injection.
.. py:attribute:: name
2015-12-14 11:22:15 +03:00
Method's name.
2015-12-14 11:22:15 +03:00
:type: str
"""
2015-07-22 10:53:16 +03:00
__IS_METHOD_INJECTION__ = True
def inject(*args, **kwargs):
"""Dependency injection decorator.
2015-11-24 11:33:10 +03:00
:py:func:`inject` decorator can be used for making inline dependency
injections. It patches decorated callable in such way that dependency
injection will be done during every call of decorated callable.
:py:func:`inject` decorator supports different syntaxes of passing
injections:
.. code-block:: python
# Positional arguments injections (simplified syntax):
@inject(1, 2)
def some_function(arg1, arg2):
pass
# Keyword arguments injections (simplified syntax):
@inject(arg1=1)
@inject(arg2=2)
def some_function(arg1, arg2):
pass
# Keyword arguments injections (extended (full) syntax):
@inject(KwArg('arg1', 1))
@inject(KwArg('arg2', 2))
def some_function(arg1, arg2):
pass
# Keyword arguments injections into class init (simplified syntax):
@inject(arg1=1)
@inject(arg2=2)
class SomeClass(object):
def __init__(self, arg1, arg2):
pass
2015-11-25 16:02:20 +03:00
:param args: Tuple of context positional arguments.
:type args: tuple[object]
:param kwargs: Dictionary of context keyword arguments.
:type kwargs: dict[str, object]
2015-11-24 11:33:10 +03:00
:return: Class / callable decorator
:rtype: (callable) -> (type | callable)
"""
arg_injections = _parse_args_injections(args)
kwarg_injections = _parse_kwargs_injections(args, kwargs)
def decorator(callback_or_cls):
"""Dependency injection decorator."""
if isinstance(callback_or_cls, six.class_types):
cls = callback_or_cls
try:
cls_init = six.get_unbound_function(cls.__init__)
assert cls_init is not _OBJECT_INIT
except (AttributeError, AssertionError):
raise Error(
'Class {0}.{1} has no __init__() '.format(cls.__module__,
cls.__name__) +
'method and could not be decorated with @inject decorator')
cls.__init__ = decorator(cls_init)
return cls
callback = callback_or_cls
2015-09-14 10:53:24 +03:00
if hasattr(callback, 'injections'):
callback.args += arg_injections
callback.kwargs += kwarg_injections
callback.injections += arg_injections + kwarg_injections
return callback
@six.wraps(callback)
def decorated(*args, **kwargs):
"""Decorated with dependency injection callback."""
return callback(*_get_injectable_args(args, decorated.args),
**_get_injectable_kwargs(kwargs, decorated.kwargs))
decorated.args = arg_injections
decorated.kwargs = kwarg_injections
decorated.injections = arg_injections + kwarg_injections
return decorated
return decorator
def _parse_args_injections(args):
"""Parse positional argument injections according to current syntax."""
return tuple(Arg(arg) if not is_injection(arg) else arg
for arg in args
if not is_injection(arg) or is_arg_injection(arg))
def _parse_kwargs_injections(args, kwargs):
"""Parse keyword argument injections according to current syntax."""
kwarg_injections = tuple(injection
for injection in args
if is_kwarg_injection(injection))
if kwargs:
kwarg_injections += tuple(KwArg(name, value)
for name, value in six.iteritems(kwargs))
return kwarg_injections
def _get_injectable_args(context_args, arg_injections):
"""Return tuple of positional arguments, patched with injections."""
return itertools.chain((arg.value for arg in arg_injections), context_args)
def _get_injectable_kwargs(context_kwargs, kwarg_injections):
"""Return dictionary of keyword arguments, patched with injections."""
injectable_kwargs = dict((kwarg.name, kwarg.value)
for kwarg in kwarg_injections)
injectable_kwargs.update(context_kwargs)
return injectable_kwargs