Merge branch 'release/4.18.0' into master

This commit is contained in:
Roman Mogylatov 2021-02-05 17:24:00 -05:00
commit 78f623c05b
14 changed files with 1233 additions and 838 deletions

View File

@ -52,7 +52,7 @@ test-py2: build
coverage report --rcfile=./.coveragerc
coverage html --rcfile=./.coveragerc
test-py3: build
test: build
# Unit tests with coverage report
coverage erase
coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*py3*.py

View File

@ -23,4 +23,5 @@ Containers module API docs - :py:mod:`dependency_injector.containers`.
dynamic
specialization
overriding
reset_singletons
traversal

View File

@ -0,0 +1,19 @@
Reset container singletons
--------------------------
To reset all container singletons use method ``.reset_singletons()``.
.. literalinclude:: ../../examples/containers/reset_singletons.py
:language: python
:lines: 3-
:emphasize-lines: 16
Method ``.reset_singletons()`` also resets singletons in sub-containers: ``providers.Container`` and
``providers.DependenciesContainer.``
.. literalinclude:: ../../examples/containers/reset_singletons_subcontainers.py
:language: python
:lines: 3-
:emphasize-lines: 21
.. disqus::

View File

@ -7,6 +7,15 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
4.18.0
------
- Add ``container.reset_singleton()`` method to reset container singletons.
- Refactor ``container.apply_container_providers_overridings()`` to use ``container.traverse()``.
This enables deep lazy initialization of ``Container`` providers.
- Add tests for ``Selector`` provider.
- Add tests for ``ProvidedInstance`` and ``MethodCaller`` providers.
- Update Makefile to make Python 3 tests to be a default test command: ``make test``.
4.17.0
------
- Add ``FastAPI`` + ``SQLAlchemy`` example.

View File

@ -0,0 +1,21 @@
"""Container reset singletons example."""
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
service1 = providers.Singleton(object)
service2 = providers.Singleton(object)
if __name__ == '__main__':
container = Container()
service1 = container.service1()
service2 = container.service2()
container.reset_singletons()
assert service1 is not container.service1()
assert service2 is not container.service2()

View File

@ -0,0 +1,26 @@
"""Container reset singletons in subcontainer example."""
from dependency_injector import containers, providers
class SubContainer(containers.DeclarativeContainer):
service = providers.Singleton(object)
class Container(containers.DeclarativeContainer):
service = providers.Singleton(object)
sub = providers.Container(SubContainer)
if __name__ == '__main__':
container = Container()
service1 = container.service()
service2 = container.sub().service()
container.reset_singletons()
assert service1 is not container.service()
assert service2 is not container.sub().service()

View File

@ -1,6 +1,6 @@
"""Top-level package."""
__version__ = '4.17.0'
__version__ = '4.18.0'
"""Version number.
:type: str

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,8 @@ class Container:
def unwire(self) -> None: ...
def init_resources(self) -> Optional[Awaitable]: ...
def shutdown_resources(self) -> Optional[Awaitable]: ...
def apply_container_providers_overridings(self) -> None: ...
def reset_singletons(self) -> None: ...
@overload
def traverse(self, types: Optional[Sequence[Type]] = None) -> Iterator[Provider]: ...
@classmethod

View File

@ -268,11 +268,14 @@ class DynamicContainer(Container):
def apply_container_providers_overridings(self):
"""Apply container providers' overridings."""
for provider in self.providers.values():
if not isinstance(provider, providers.Container):
continue
for provider in self.traverse(types=[providers.Container]):
provider.apply_overridings()
def reset_singletons(self):
"""Reset all container singletons."""
for provider in self.traverse(types=[providers.Singleton]):
provider.reset()
class DeclarativeContainerMetaClass(type):
"""Declarative inversion of control container meta class."""

View File

@ -140,7 +140,7 @@ class DeclarativeContainerInstanceTests(unittest.TestCase):
with self.assertRaises(AttributeError):
container_a.override_providers(unknown=providers.Provider())
def test_reset_last_overridding(self):
def test_reset_last_overriding(self):
class _Container(containers.DeclarativeContainer):
p11 = providers.Provider()
@ -164,7 +164,7 @@ class DeclarativeContainerInstanceTests(unittest.TestCase):
self.assertEqual(container.p11.overridden,
(overriding_container1.p11,))
def test_reset_last_overridding_when_not_overridden(self):
def test_reset_last_overriding_when_not_overridden(self):
container = ContainerA()
with self.assertRaises(errors.Error):
@ -287,3 +287,51 @@ class DeclarativeContainerInstanceTests(unittest.TestCase):
self.assertEqual(_init1.shutdown_counter, 2)
self.assertEqual(_init2.init_counter, 2)
self.assertEqual(_init2.shutdown_counter, 2)
def test_reset_singletons(self):
class SubSubContainer(containers.DeclarativeContainer):
singleton = providers.Singleton(object)
class SubContainer(containers.DeclarativeContainer):
singleton = providers.Singleton(object)
sub_sub_container = providers.Container(SubSubContainer)
class Container(containers.DeclarativeContainer):
singleton = providers.Singleton(object)
sub_container = providers.Container(SubContainer)
container = Container()
obj11 = container.singleton()
obj12 = container.sub_container().singleton()
obj13 = container.sub_container().sub_sub_container().singleton()
obj21 = container.singleton()
obj22 = container.sub_container().singleton()
obj23 = container.sub_container().sub_sub_container().singleton()
self.assertIs(obj11, obj21)
self.assertIs(obj12, obj22)
self.assertIs(obj13, obj23)
container.reset_singletons()
obj31 = container.singleton()
obj32 = container.sub_container().singleton()
obj33 = container.sub_container().sub_sub_container().singleton()
obj41 = container.singleton()
obj42 = container.sub_container().singleton()
obj43 = container.sub_container().sub_sub_container().singleton()
self.assertIsNot(obj11, obj31)
self.assertIsNot(obj12, obj32)
self.assertIsNot(obj13, obj33)
self.assertIsNot(obj21, obj31)
self.assertIsNot(obj22, obj32)
self.assertIsNot(obj23, obj33)
self.assertIs(obj31, obj41)
self.assertIs(obj32, obj42)
self.assertIs(obj33, obj43)

View File

@ -135,6 +135,8 @@ class ContainerTests(unittest.TestCase):
provider.override(providers.Object('foo'))
def test_lazy_overriding(self):
# See: https://github.com/ets-labs/python-dependency-injector/issues/354
class D(containers.DeclarativeContainer):
foo = providers.Object("foo")
@ -150,4 +152,26 @@ class ContainerTests(unittest.TestCase):
b = B(d=D())
result = b.a().bar()
self.assertEqual(result, 'foo++')
# See: https://github.com/ets-labs/python-dependency-injector/issues/354
def test_lazy_overriding_deep(self):
# Extended version of test_lazy_overriding()
class D(containers.DeclarativeContainer):
foo = providers.Object("foo")
class C(containers.DeclarativeContainer):
d = providers.DependenciesContainer()
bar = providers.Callable(lambda f: f + "++", d.foo.provided)
class A(containers.DeclarativeContainer):
d = providers.DependenciesContainer()
c = providers.Container(C, d=d)
class B(containers.DeclarativeContainer):
d = providers.DependenciesContainer()
a = providers.Container(A, d=d)
b = B(d=D())
result = b.a().c().bar()
self.assertEqual(result, 'foo++')

View File

@ -10,12 +10,20 @@ class Service:
self.value = value
self.values = [self.value]
def get_value(self):
def __call__(self):
return self.value
def __getitem__(self, item):
return self.values[item]
def get_value(self):
return self.value
def get_closure(self):
def closure():
return self.value
return closure
class Client:
def __init__(self, value):
@ -45,6 +53,15 @@ class Container(containers.DeclarativeContainer):
Client,
value=service.provided.get_value.call(),
)
client_method_closure_call = providers.Factory(
Client,
value=service.provided.get_closure.call().call(),
)
client_provided_call = providers.Factory(
Client,
value=service.provided.call(),
)
class ProvidedInstanceTests(unittest.TestCase):
@ -71,6 +88,14 @@ class ProvidedInstanceTests(unittest.TestCase):
client = self.container.client_method_call()
self.assertEqual(client.value, 'foo')
def test_method_closure_call(self):
client = self.container.client_method_closure_call()
self.assertEqual(client.value, 'foo')
def test_provided_call(self):
client = self.container.client_provided_call()
self.assertEqual(client.value, 'foo')
def test_call_overridden(self):
value = 'bar'
with self.container.service.override(Service(value)):

View File

@ -6,7 +6,7 @@ import sys
import unittest2 as unittest
from dependency_injector import providers
from dependency_injector import providers, errors
class SelectorTests(unittest.TestCase):
@ -33,6 +33,28 @@ class SelectorTests(unittest.TestCase):
with self.selector.override('two'):
self.assertEqual(provider(), 2)
def test_call_undefined_provider(self):
provider = providers.Selector(
self.selector,
one=providers.Object(1),
two=providers.Object(2),
)
with self.selector.override('three'):
with self.assertRaises(errors.Error):
provider()
def test_call_selector_is_none(self):
provider = providers.Selector(
self.selector,
one=providers.Object(1),
two=providers.Object(2),
)
with self.selector.override(None):
with self.assertRaises(errors.Error):
provider()
def test_call_any_callable(self):
provider = providers.Selector(
functools.partial(next, itertools.cycle(['one', 'two'])),
@ -70,6 +92,19 @@ class SelectorTests(unittest.TestCase):
self.assertIs(provider.one, provider_one)
self.assertIs(provider.two, provider_two)
def test_getattr_attribute_error(self):
provider_one = providers.Object(1)
provider_two = providers.Object(2)
provider = providers.Selector(
self.selector,
one=provider_one,
two=provider_two,
)
with self.assertRaises(AttributeError):
_ = provider.provider_three
def test_call_overridden(self):
provider = providers.Selector(self.selector, sample=providers.Object(1))
overriding_provider1 = providers.Selector(self.selector, sample=providers.Object(2))
@ -81,6 +116,18 @@ class SelectorTests(unittest.TestCase):
with self.selector.override('sample'):
self.assertEqual(provider(), 3)
def test_providers_attribute(self):
provider_one = providers.Object(1)
provider_two = providers.Object(2)
provider = providers.Selector(
self.selector,
one=provider_one,
two=provider_two,
)
self.assertEqual(provider.providers, {'one': provider_one, 'two': provider_two})
def test_deepcopy(self):
provider = providers.Selector(self.selector)