Container "self" injections (#392)

* Add implementation

* Add Self provider tests

* Add container tests

* Remove ellipsis from tests to make them pass on Python 2

* Add tests

* Add docs

* Improve traverse() typing stubs

* Update changelog
This commit is contained in:
Roman Mogylatov 2021-02-07 14:13:23 -05:00 committed by GitHub
parent ce6d3df72c
commit 674a6b0f9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 12504 additions and 8681 deletions

View File

@ -22,6 +22,7 @@ Containers module API docs - :py:mod:`dependency_injector.containers`.
declarative
dynamic
specialization
inject_self
overriding
reset_singletons
traversal

View File

@ -0,0 +1,20 @@
Injecting container "self"
==========================
You can inject container "self" into container providers.
.. literalinclude:: ../../examples/containers/inject_self.py
:language: python
:lines: 3-
:emphasize-lines: 20, 26
To inject container "self" you need to define ``Self`` provider. Container can have only one ``Self`` provider.
Usually you will use name ``__self__``.
You can also use different name. When you use different name container will also reference
defined ``Self`` provider in ``.__self__`` attribute.
Provider ``Self`` is not listed in container ``.providers`` attributes.
.. disqus::

View File

@ -7,6 +7,12 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
Development version
-------------------
- Add container "self" injections.
See issue: `#364 <https://github.com/ets-labs/python-dependency-injector/issues/364>`_.
Thanks to `@shaunc <https://github.com/shaunc>`_ for suggesting the feature.
4.19.0
------
- Add ``singleton.full_reset()`` method to reset all underlying singleton providers.

View File

@ -0,0 +1,36 @@
"""Container injecting ``self`` example."""
from dependency_injector import containers, providers
class Service:
def __init__(self, name: str):
self.name = name
class ServiceDispatcher:
def __init__(self, container: containers.Container):
self.container = container
def get_services(self):
for provider in self.container.traverse(types=[providers.Factory]):
yield provider()
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
service1 = providers.Factory(Service, name='Service 1')
service2 = providers.Factory(Service, name='Service 2')
service3 = providers.Factory(Service, name='Service 3')
dispatcher = providers.Singleton(ServiceDispatcher, __self__)
if __name__ == '__main__':
container = Container()
dispatcher = container.dispatcher()
for service in dispatcher.get_services():
print(service.name)

File diff suppressed because it is too large Load Diff

View File

@ -7,15 +7,13 @@ from typing import (
Union,
ClassVar,
Callable as _Callable,
Sequence,
Iterable,
Iterator,
TypeVar,
Awaitable,
overload,
)
from .providers import Provider
from .providers import Provider, Self
C_Base = TypeVar('C_Base', bound='Container')
@ -29,12 +27,13 @@ class Container:
providers: Dict[str, Provider]
dependencies: Dict[str, Provider]
overridden: Tuple[Provider]
__self__: Provider
__self__: Self
def __init__(self) -> None: ...
def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> Provider: ...
def __setattr__(self, name: str, value: Union[Provider, Any]) -> None: ...
def __delattr__(self, name: str) -> None: ...
def set_providers(self, **providers: Provider): ...
def set_provider(self, name: str, provider: Provider) -> None: ...
def override(self, overriding: C_Base) -> None: ...
def override_providers(self, **overriding_providers: Provider) -> None: ...
def reset_last_overriding(self) -> None: ...
@ -47,10 +46,10 @@ class Container:
def apply_container_providers_overridings(self) -> None: ...
def reset_singletons(self) -> None: ...
@overload
def traverse(self, types: Optional[Sequence[TT]] = None) -> Iterator[TT]: ...
def traverse(self, types: Optional[Iterable[Type[TT]]] = None) -> _Iterator[TT]: ...
@classmethod
@overload
def traverse(cls, types: Optional[Sequence[TT]] = None) -> Iterator[TT]: ...
def traverse(self, types: Optional[Iterable[Type[TT]]] = None) -> _Iterator[TT]: ...
class DynamicContainer(Container): ...

View File

@ -69,7 +69,7 @@ class DynamicContainer(Container):
self.declarative_parent = None
self.wired_to_modules = []
self.wired_to_packages = []
self.__self__ = providers.Object(self)
self.__self__ = providers.Self(self)
super(DynamicContainer, self).__init__()
def __deepcopy__(self, memo):
@ -79,12 +79,18 @@ class DynamicContainer(Container):
return copied
copied = self.__class__()
memo[id(self)] = copied
copied.provider_type = providers.Provider
copied.overridden = providers.deepcopy(self.overridden, memo)
copied.declarative_parent = self.declarative_parent
copied.__self__ = providers.deepcopy(self.__self__, memo)
for name in copied.__self__.alt_names:
copied.set_provider(name, copied.__self__)
for name, provider in providers.deepcopy(self.providers, memo).items():
setattr(copied, name, provider)
copied.set_provider(name, provider)
return copied
@ -102,7 +108,7 @@ class DynamicContainer(Container):
:rtype: None
"""
if isinstance(value, providers.Provider) and name != '__self__':
if isinstance(value, providers.Provider) and not isinstance(value, providers.Self):
_check_provider_type(self, value)
self.providers[name] = value
super(DynamicContainer, self).__setattr__(name, value)
@ -154,6 +160,19 @@ class DynamicContainer(Container):
for name, provider in six.iteritems(providers):
setattr(self, name, provider)
def set_provider(self, name, provider):
"""Set container provider.
:param name: Provider name
:type name: str
:param provider: Provider
:type provider: :py:class:`dependency_injector.providers.Provider`
:rtype: None
"""
setattr(self, name, provider)
def override(self, object overriding):
"""Override current container by overriding container.
@ -282,6 +301,10 @@ class DeclarativeContainerMetaClass(type):
def __new__(type mcs, str class_name, tuple bases, dict attributes):
"""Declarative container class factory."""
self = mcs.__fetch_self(attributes)
if self is None:
self = providers.Self()
containers = {
name: container
for name, container in six.iteritems(attributes)
@ -291,7 +314,7 @@ class DeclarativeContainerMetaClass(type):
cls_providers = {
name: provider
for name, provider in six.iteritems(attributes)
if isinstance(provider, providers.Provider)
if isinstance(provider, providers.Provider) and not isinstance(provider, providers.Self)
}
inherited_providers = {
@ -312,7 +335,8 @@ class DeclarativeContainerMetaClass(type):
cls = <type>type.__new__(mcs, class_name, bases, attributes)
cls.__self__ = providers.Object(cls)
self.set_container(cls)
cls.__self__ = self
for provider in six.itervalues(cls.providers):
_check_provider_type(cls, provider)
@ -375,6 +399,28 @@ class DeclarativeContainerMetaClass(type):
"""Return providers traversal generator."""
yield from providers.traverse(*cls.providers.values(), types=types)
@staticmethod
def __fetch_self(attributes):
self = None
alt_names = []
for name, value in attributes.items():
if not isinstance(value, providers.Self):
continue
if self is not None and value is not self:
raise errors.Error('Container can have only one "Self" provider')
if name != '__self__':
alt_names.append(name)
self = value
if self:
self.set_alt_names(alt_names)
return self
@six.add_metaclass(DeclarativeContainerMetaClass)
class DeclarativeContainer(Container):
@ -448,9 +494,21 @@ class DeclarativeContainer(Container):
container = cls.instance_type()
container.provider_type = cls.provider_type
container.declarative_parent = cls
container.set_providers(**providers.deepcopy(cls.providers))
copied_providers = providers.deepcopy({ **cls.providers, **{'@@self@@': cls.__self__}})
copied_self = copied_providers.pop('@@self@@')
copied_self.set_container(container)
container.__self__ = copied_self
for name in copied_self.alt_names:
container.set_provider(name, copied_self)
for name, provider in copied_providers.items():
container.set_provider(name, provider)
container.override_providers(**overriding_providers)
container.apply_container_providers_overridings()
return container
@classmethod

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,11 @@ cdef class Object(Provider):
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Self(Provider):
cdef object __container
cdef tuple __alt_names
cdef class Delegate(Provider):
cdef object __provides

View File

@ -77,7 +77,7 @@ class Provider(Generic[T]):
def is_async_mode_undefined(self) -> bool: ...
@property
def related(self) -> _Iterator[Provider]: ...
def traverse(self, types: Optional[_Iterable[TT]] = None) -> _Iterator[TT]: ...
def traverse(self, types: Optional[_Iterable[Type[TT]]] = None) -> _Iterator[TT]: ...
def _copy_overridings(self, copied: Provider, memo: Optional[_Dict[Any, Any]]) -> None: ...
@ -85,6 +85,14 @@ class Object(Provider[T]):
def __init__(self, provides: T) -> None: ...
class Self(Provider[T]):
def __init__(self, container: Optional[T] = None) -> None: ...
def set_container(self, container: T) -> None: ...
def set_alt_names(self, alt_names: _Iterable[Any]) -> None: ...
@property
def alt_names(self) -> Tuple[Any]: ...
class Delegate(Provider[Provider]):
def __init__(self, provides: Provider) -> None: ...
@property
@ -346,7 +354,7 @@ class Resource(Provider[T]):
class Container(Provider[T]):
def __init__(self, container_cls: Type[T], container: Optional[T] = None, **overriding_providers: Provider) -> None: ...
def __init__(self, container_cls: Type[T], container: Optional[T] = None, **overriding_providers: Union[Provider, Any]) -> None: ...
def __getattr__(self, name: str) -> Provider: ...
@property
def container(self) -> T: ...

View File

@ -458,6 +458,57 @@ cdef class Object(Provider):
return self.__provides
cdef class Self(Provider):
"""Self provider returns own container."""
def __init__(self, container=None):
"""Initialize provider."""
self.__container = container
self.__alt_names = tuple()
super().__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__()
copied.set_container(deepcopy(self.__container, memo))
copied.set_alt_names(self.__alt_names)
self._copy_overridings(copied, memo)
return copied
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self, provides=self.__container)
def __repr__(self):
"""Return string representation of provider.
:rtype: str
"""
return self.__str__()
def set_container(self, container):
self.__container = container
def set_alt_names(self, alt_names):
self.__alt_names = tuple(set(alt_names))
@property
def alt_names(self):
return self.__alt_names
cpdef object _provide(self, tuple args, dict kwargs):
return self.__container
cdef class Delegate(Provider):
"""Delegate provider returns provider "as is".

View File

@ -335,3 +335,204 @@ class DeclarativeContainerInstanceTests(unittest.TestCase):
self.assertIs(obj31, obj41)
self.assertIs(obj32, obj42)
self.assertIs(obj33, obj43)
class SelfTests(unittest.TestCase):
def test_self(self):
def call_bar(container):
return container.bar()
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
foo = providers.Callable(call_bar, __self__)
bar = providers.Object('hello')
container = Container()
self.assertIs(container.foo(), 'hello')
def test_self_attribute_implicit(self):
class Container(containers.DeclarativeContainer):
pass
container = Container()
self.assertIs(container.__self__(), container)
def test_self_attribute_explicit(self):
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
container = Container()
self.assertIs(container.__self__(), container)
def test_single_self(self):
with self.assertRaises(errors.Error):
class Container(containers.DeclarativeContainer):
self1 = providers.Self()
self2 = providers.Self()
def test_self_attribute_alt_name_implicit(self):
class Container(containers.DeclarativeContainer):
foo = providers.Self()
container = Container()
self.assertIs(container.__self__, container.foo)
self.assertEqual(set(container.__self__.alt_names), {'foo'})
def test_self_attribute_alt_name_explicit_1(self):
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
foo = __self__
bar = __self__
container = Container()
self.assertIs(container.__self__, container.foo)
self.assertIs(container.__self__, container.bar)
self.assertEqual(set(container.__self__.alt_names), {'foo', 'bar'})
def test_self_attribute_alt_name_explicit_2(self):
class Container(containers.DeclarativeContainer):
foo = providers.Self()
bar = foo
container = Container()
self.assertIs(container.__self__, container.foo)
self.assertIs(container.__self__, container.bar)
self.assertEqual(set(container.__self__.alt_names), {'foo', 'bar'})
def test_providers_attribute_1(self):
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
foo = __self__
bar = __self__
container = Container()
self.assertEqual(container.providers, {})
self.assertEqual(Container.providers, {})
def test_providers_attribute_2(self):
class Container(containers.DeclarativeContainer):
foo = providers.Self()
bar = foo
container = Container()
self.assertEqual(container.providers, {})
self.assertEqual(Container.providers, {})
def test_container_multiple_instances(self):
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
container1 = Container()
container2 = Container()
self.assertIsNot(container1, container2)
self.assertIs(container1.__self__(), container1)
self.assertIs(container2.__self__(), container2)
def test_deepcopy(self):
def call_bar(container):
return container.bar()
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
foo = providers.Callable(call_bar, __self__)
bar = providers.Object('hello')
container1 = Container()
container2 = providers.deepcopy(container1)
container1.bar.override('bye')
self.assertIs(container1.foo(), 'bye')
self.assertIs(container2.foo(), 'hello')
def test_deepcopy_alt_names_1(self):
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
foo = __self__
bar = foo
container1 = Container()
container2 = providers.deepcopy(container1)
self.assertIs(container2.__self__(), container2)
self.assertIs(container2.foo(), container2)
self.assertIs(container2.bar(), container2)
def test_deepcopy_alt_names_2(self):
class Container(containers.DeclarativeContainer):
self = providers.Self()
container1 = Container()
container2 = providers.deepcopy(container1)
self.assertIs(container2.__self__(), container2)
self.assertIs(container2.self(), container2)
def test_deepcopy_no_self_dependencies(self):
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
container1 = Container()
container2 = providers.deepcopy(container1)
self.assertIsNot(container1, container2)
self.assertIsNot(container1.__self__, container2.__self__)
self.assertIs(container1.__self__(), container1)
self.assertIs(container2.__self__(), container2)
def test_with_container_provider(self):
def call_bar(container):
return container.bar()
class SubContainer(containers.DeclarativeContainer):
__self__ = providers.Self()
foo = providers.Callable(call_bar, __self__)
bar = providers.Object('hello')
class Container(containers.DeclarativeContainer):
sub_container = providers.Container(SubContainer)
baz = providers.Callable(lambda value: value, sub_container.foo)
container = Container()
self.assertIs(container.baz(), 'hello')
def test_with_container_provider_overriding(self):
def call_bar(container):
return container.bar()
class SubContainer(containers.DeclarativeContainer):
__self__ = providers.Self()
foo = providers.Callable(call_bar, __self__)
bar = providers.Object('hello')
class Container(containers.DeclarativeContainer):
sub_container = providers.Container(SubContainer, bar='bye')
baz = providers.Callable(lambda value: value, sub_container.foo)
container = Container()
self.assertIs(container.baz(), 'bye')
def test_with_container_provider_self(self):
class SubContainer(containers.DeclarativeContainer):
__self__ = providers.Self()
class Container(containers.DeclarativeContainer):
sub_container = providers.Container(SubContainer)
container = Container()
self.assertIs(container.__self__(), container)
self.assertIs(container.sub_container().__self__(), container.sub_container())

View File

@ -218,6 +218,68 @@ class ObjectProviderTests(unittest.TestCase):
hex(id(provider))))
class SelfProviderTests(unittest.TestCase):
def test_is_provider(self):
self.assertTrue(providers.is_provider(providers.Self()))
def test_call_object_provider(self):
container = containers.DeclarativeContainer()
self.assertIs(providers.Self(container)(), container)
def test_set_container(self):
container = containers.DeclarativeContainer()
provider = providers.Self()
provider.set_container(container)
self.assertIs(provider(), container)
def test_set_alt_names(self):
provider = providers.Self()
provider.set_alt_names({'foo', 'bar', 'baz'})
self.assertEqual(set(provider.alt_names), {'foo', 'bar', 'baz'})
def test_deepcopy(self):
provider = providers.Self()
provider_copy = providers.deepcopy(provider)
self.assertIsNot(provider, provider_copy)
self.assertIsInstance(provider, providers.Self)
def test_deepcopy_from_memo(self):
provider = providers.Self()
provider_copy_memo = providers.Provider()
provider_copy = providers.deepcopy(
provider, memo={id(provider): provider_copy_memo})
self.assertIs(provider_copy, provider_copy_memo)
def test_deepcopy_overridden(self):
provider = providers.Self()
overriding_provider = providers.Provider()
provider.override(overriding_provider)
provider_copy = providers.deepcopy(provider)
overriding_provider_copy = provider_copy.overridden[0]
self.assertIsNot(provider, provider_copy)
self.assertIsInstance(provider, providers.Self)
self.assertIsNot(overriding_provider, overriding_provider_copy)
self.assertIsInstance(overriding_provider_copy, providers.Provider)
def test_repr(self):
container = containers.DeclarativeContainer()
provider = providers.Self(container)
self.assertEqual(repr(provider),
'<dependency_injector.providers.'
'Self({0}) at {1}>'.format(
repr(container),
hex(id(provider))))
class DelegateTests(unittest.TestCase):
def setUp(self):