python-dependency-injector/dependency_injector/containers.py

178 lines
5.7 KiB
Python
Raw Normal View History

2016-05-27 14:37:15 +03:00
"""IoC containers module."""
import six
from dependency_injector import (
providers,
utils,
errors,
)
2016-05-27 14:37:15 +03:00
class DeclarativeContainerMetaClass(type):
"""Declarative inversion of control container meta class."""
def __new__(mcs, class_name, bases, attributes):
"""Declarative container class factory."""
cls_providers = tuple((name, provider)
for name, provider in six.iteritems(attributes)
if utils.is_provider(provider))
inherited_providers = tuple((name, provider)
for base in bases if utils.is_container(
base)
2016-05-27 14:37:15 +03:00
for name, provider in six.iteritems(
base.cls_providers))
attributes['cls_providers'] = dict(cls_providers)
attributes['inherited_providers'] = dict(inherited_providers)
attributes['providers'] = dict(cls_providers + inherited_providers)
2016-05-27 14:37:15 +03:00
cls = type.__new__(mcs, class_name, bases, attributes)
for provider in six.itervalues(cls.providers):
_check_provider_type(cls, provider)
return cls
2016-05-27 14:37:15 +03:00
def __setattr__(cls, name, value):
"""Set class attribute.
If value of attribute is provider, it will be added into providers
dictionary.
"""
if utils.is_provider(value):
_check_provider_type(cls, value)
2016-05-27 14:37:15 +03:00
cls.providers[name] = value
cls.cls_providers[name] = value
2016-05-27 14:37:15 +03:00
super(DeclarativeContainerMetaClass, cls).__setattr__(name, value)
def __delattr__(cls, name):
"""Delete class attribute.
2016-05-27 14:37:15 +03:00
If value of attribute is provider, it will be deleted from providers
2016-05-27 14:37:15 +03:00
dictionary.
"""
if name in cls.providers and name in cls.cls_providers:
del cls.providers[name]
del cls.cls_providers[name]
super(DeclarativeContainerMetaClass, cls).__delattr__(name)
2016-05-27 14:37:15 +03:00
@six.add_metaclass(DeclarativeContainerMetaClass)
class DeclarativeContainer(object):
"""Declarative inversion of control container."""
__IS_CONTAINER__ = True
provider_type = providers.Provider
providers = dict()
2016-05-27 14:37:15 +03:00
cls_providers = dict()
inherited_providers = dict()
overridden_by = tuple()
@classmethod
def override(cls, overriding):
"""Override current container by overriding container.
:param overriding: Overriding container.
:type overriding: :py:class:`DeclarativeContainer`
:raise: :py:exc:`dependency_injector.errors.Error` if trying to
override container by itself or its subclasses
:rtype: None
"""
if issubclass(cls, overriding):
raise errors.Error('Container {0} could not be overridden '
'with itself or its subclasses'.format(cls))
cls.overridden_by += (overriding,)
for name, provider in six.iteritems(overriding.cls_providers):
try:
getattr(cls, name).override(provider)
except AttributeError:
pass
2016-05-27 14:37:15 +03:00
@classmethod
def reset_last_overriding(cls):
"""Reset last overriding provider for each container providers.
:rtype: None
"""
if not cls.overridden_by:
raise errors.Error('Container {0} is not overridden'.format(cls))
cls.overridden_by = cls.overridden_by[:-1]
for provider in six.itervalues(cls.providers):
provider.reset_last_overriding()
@classmethod
def reset_override(cls):
"""Reset all overridings for each container providers.
:rtype: None
"""
cls.overridden_by = tuple()
for provider in six.itervalues(cls.providers):
provider.reset_override()
2016-05-27 14:37:15 +03:00
def override(container):
2016-05-27 14:37:15 +03:00
""":py:class:`DeclarativeContainer` overriding decorator.
2016-05-29 17:17:27 +03:00
:param container: Container that should be overridden by decorated
container.
:type container: :py:class:`DeclarativeContainer`
2016-05-27 14:37:15 +03:00
:return: Declarative container's overriding decorator.
:rtype: callable(:py:class:`DeclarativeContainer`)
"""
2016-05-29 17:17:27 +03:00
def _decorator(overriding_container):
2016-05-27 14:37:15 +03:00
"""Overriding decorator."""
container.override(overriding_container)
2016-05-27 14:37:15 +03:00
return overriding_container
2016-05-29 17:17:27 +03:00
return _decorator
def copy(container):
""":py:class:`DeclarativeContainer` copying decorator.
This decorator copy all providers from provided container to decorated one.
If one of the decorated container providers matches to source container
providers by name, it would be replaced by reference.
:param container: Container that should be copied by decorated container.
:type container :py:class:`DeclarativeContainer`
:return: Declarative container's copying decorator.
:rtype: callable(:py:class:`DeclarativeContainer`)
"""
def _decorator(copied_container):
memo = dict()
for name, provider in six.iteritems(copied_container.cls_providers):
try:
source_provider = getattr(container, name)
except AttributeError:
pass
else:
memo[id(source_provider)] = provider
providers_copy = utils._copy_providers(container.providers, memo)
for name, provider in six.iteritems(providers_copy):
setattr(copied_container, name, provider)
return copied_container
return _decorator
def _check_provider_type(cls, provider):
if not isinstance(provider, cls.provider_type):
raise errors.Error('{0} can contain only {1} '
'instances'.format(cls, cls.provider_type))