Merge branch 'release/3.17.0' into master

This commit is contained in:
Roman Mogylatov 2020-06-23 12:14:28 -04:00
commit 827f9b57bb
9 changed files with 5641 additions and 2760 deletions

View File

@ -7,6 +7,11 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_ follows `Semantic versioning`_
3.17.0
------
- Add ``Container`` provider.
- Add ``Configuration`` providers linking.
3.16.1 3.16.1
------ ------
- Update ``singleton_thread_locals.py`` to support Python 3 (thanks to - Update ``singleton_thread_locals.py`` to support Python 3 (thanks to

View File

@ -1,6 +1,6 @@
"""Dependency injector top-level package.""" """Dependency injector top-level package."""
__version__ = '3.16.1' __version__ = '3.17.0'
"""Version number that follows semantic versioning. """Version number that follows semantic versioning.
:type: str :type: str

File diff suppressed because it is too large Load Diff

View File

@ -54,6 +54,19 @@ class DynamicContainer(object):
self.overridden = tuple() self.overridden = tuple()
super(DynamicContainer, self).__init__() super(DynamicContainer, self).__init__()
def __deepcopy__(self, memo):
"""Create and return full copy of container."""
copied = memo.get(id(self))
if copied is not None:
return copied
copied = self.__class__()
copied.provider_type = Provider
copied.providers = deepcopy(self.providers, memo)
copied.overridden = deepcopy(self.overridden, memo)
return copied
def __setattr__(self, str name, object value): def __setattr__(self, str name, object value):
"""Set instance attribute. """Set instance attribute.

File diff suppressed because it is too large Load Diff

View File

@ -92,6 +92,7 @@ cdef class CoroutineDelegate(Delegate):
cdef class Configuration(Object): cdef class Configuration(Object):
cdef str __name cdef str __name
cdef dict __children cdef dict __children
cdef list __linked
# Factory providers # Factory providers
@ -175,6 +176,14 @@ cdef class List(Provider):
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class Container(Provider):
cdef object container_cls
cdef dict overriding_providers
cdef object container
cpdef object _provide(self, tuple args, dict kwargs)
# Injections # Injections
cdef class Injection(object): cdef class Injection(object):
cdef object __value cdef object __value

View File

@ -1029,6 +1029,7 @@ cdef class Configuration(Object):
self.__name = name self.__name = name
self.__children = self._create_children(default) self.__children = self._create_children(default)
self.__linked = list()
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
"""Create and return full copy of provider.""" """Create and return full copy of provider."""
@ -1041,6 +1042,7 @@ cdef class Configuration(Object):
copied = self.__class__(self.__name) copied = self.__class__(self.__name)
copied.__provides = deepcopy(self.__provides, memo) copied.__provides = deepcopy(self.__provides, memo)
copied.__children = deepcopy(self.__children, memo) copied.__children = deepcopy(self.__children, memo)
copied.__linked = deepcopy(self.__linked, memo)
self._copy_overridings(copied, memo) self._copy_overridings(copied, memo)
@ -1093,11 +1095,17 @@ cdef class Configuration(Object):
""" """
overriding_context = super(Configuration, self).override(provider) overriding_context = super(Configuration, self).override(provider)
for linked in self.__linked:
linked.override(provider)
if isinstance(provider, Configuration):
provider.link_provider(self)
value = self.__call__() value = self.__call__()
if not isinstance(value, dict): if not isinstance(value, dict):
return return
for name in value: for name in value.keys():
child_provider = self.__children.get(name) child_provider = self.__children.get(name)
if child_provider is None: if child_provider is None:
continue continue
@ -1129,6 +1137,10 @@ cdef class Configuration(Object):
child.reset_override() child.reset_override()
super(Configuration, self).reset_override() super(Configuration, self).reset_override()
def link_provider(self, provider):
"""Configuration link two configuration providers."""
self.__linked.append(<Configuration?>provider)
def update(self, value): def update(self, value):
"""Set configuration options. """Set configuration options.
@ -2077,6 +2089,58 @@ cdef class List(Provider):
return list(__provide_positional_args(args, self.__args, self.__args_len)) return list(__provide_positional_args(args, self.__args, self.__args_len))
cdef class Container(Provider):
"""Container provider provides an instance of declarative container.
.. warning::
Provider is experimental. Its interface may change.
"""
def __init__(self, container_cls, container=None, **overriding_providers):
"""Initialize provider."""
self.container_cls = container_cls
self.overriding_providers = overriding_providers
if container is None:
container = container_cls()
container.override_providers(**overriding_providers)
self.container = container
super(Container, self).__init__()
def __deepcopy__(self, memo):
"""Create and return full copy of provider."""
copied = memo.get(id(self))
if copied is not None:
return copied
copied = self.__class__(
self.container_cls,
deepcopy(self.container, memo),
**deepcopy(self.overriding_providers, memo),
)
# self._copy_overridings(copied, memo)
return copied
def __getattr__(self, name):
"""Return dependency provider."""
if name.startswith('__') and name.endswith('__'):
raise AttributeError(
'\'{cls}\' object has no attribute '
'\'{attribute_name}\''.format(cls=self.__class__.__name__,
attribute_name=name))
return getattr(self.container, name)
def override(self, provider):
"""Override provider with another provider."""
raise Error('Provider {0} can not be overridden'.format(self))
cpdef object _provide(self, tuple args, dict kwargs):
"""Return single instance."""
return self.container
cdef class Injection(object): cdef class Injection(object):
"""Abstract injection class.""" """Abstract injection class."""

View File

@ -2,7 +2,7 @@
import unittest2 as unittest import unittest2 as unittest
from dependency_injector import providers from dependency_injector import containers, providers
class ConfigTests(unittest.TestCase): class ConfigTests(unittest.TestCase):
@ -177,3 +177,72 @@ class ConfigTests(unittest.TestCase):
'Configuration({0}) at {1}>'.format( 'Configuration({0}) at {1}>'.format(
repr('config.a.b.c'), repr('config.a.b.c'),
hex(id(self.config.a.b.c)))) hex(id(self.config.a.b.c))))
class ConfigLinkingTests(unittest.TestCase):
class TestCore(containers.DeclarativeContainer):
config = providers.Configuration('core')
value_getter = providers.Callable(lambda _: _, config.value)
class TestServices(containers.DeclarativeContainer):
config = providers.Configuration('services')
value_getter = providers.Callable(lambda _: _, config.value)
def test(self):
root_config = providers.Configuration('main')
core = self.TestCore(config=root_config.core)
services = self.TestServices(config=root_config.services)
root_config.override(
{
'core': {
'value': 'core',
},
'services': {
'value': 'services',
},
},
)
self.assertEqual(core.config(), {'value': 'core'})
self.assertEqual(core.config.value(), 'core')
self.assertEqual(core.value_getter(), 'core')
self.assertEqual(services.config(), {'value': 'services'})
self.assertEqual(services.config.value(), 'services')
self.assertEqual(services.value_getter(), 'services')
def test_double_override(self):
root_config = providers.Configuration('main')
core = self.TestCore(config=root_config.core)
services = self.TestServices(config=root_config.services)
root_config.override(
{
'core': {
'value': 'core1',
},
'services': {
'value': 'services1',
},
},
)
root_config.override(
{
'core': {
'value': 'core2',
},
'services': {
'value': 'services2',
},
},
)
self.assertEqual(core.config(), {'value': 'core2'})
self.assertEqual(core.config.value(), 'core2')
self.assertEqual(core.value_getter(), 'core2')
self.assertEqual(services.config(), {'value': 'services2'})
self.assertEqual(services.config.value(), 'services2')
self.assertEqual(services.value_getter(), 'services2')

View File

@ -0,0 +1,54 @@
"""Dependency injector container provider unit tests."""
import copy
import unittest2 as unittest
from dependency_injector import containers, providers
TEST_VALUE_1 = 'core_section_value1'
TEST_CONFIG_1 = {
'core': {
'section': {
'value': TEST_VALUE_1,
},
},
}
TEST_VALUE_2 = 'core_section_value2'
TEST_CONFIG_2 = {
'core': {
'section': {
'value': TEST_VALUE_2,
},
},
}
def _copied(value):
return copy.deepcopy(value)
class TestCore(containers.DeclarativeContainer):
config = providers.Configuration('core')
value_getter = providers.Callable(lambda _: _, config.section.value)
class TestApplication(containers.DeclarativeContainer):
config = providers.Configuration('config')
core = providers.Container(TestCore, config=config.core)
dict_factory = providers.Factory(dict, value=core.value_getter)
class ContainerTests(unittest.TestCase):
def test(self):
application = TestApplication(config=_copied(TEST_CONFIG_1))
self.assertEqual(application.dict_factory(), {'value': TEST_VALUE_1})
def test_double_override(self):
application = TestApplication()
application.config.override(_copied(TEST_CONFIG_1))
application.config.override(_copied(TEST_CONFIG_2))
self.assertEqual(application.dict_factory(), {'value': TEST_VALUE_2})