Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Container "self" injections #392

Merged
merged 8 commits into from Feb 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/containers/index.rst
Expand Up @@ -22,6 +22,7 @@ Containers module API docs - :py:mod:`dependency_injector.containers`.
declarative
dynamic
specialization
inject_self
overriding
reset_singletons
traversal
20 changes: 20 additions & 0 deletions docs/containers/inject_self.rst
@@ -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::

6 changes: 6 additions & 0 deletions docs/main/changelog.rst
Expand Up @@ -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.
Expand Down
36 changes: 36 additions & 0 deletions examples/containers/inject_self.py
@@ -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)
5,779 changes: 3,572 additions & 2,207 deletions src/dependency_injector/containers.c

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions src/dependency_injector/containers.pyi
Expand Up @@ -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')
Expand All @@ -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: ...
Expand All @@ -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): ...
Expand Down
70 changes: 64 additions & 6 deletions src/dependency_injector/containers.pyx
Expand Up @@ -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):
Expand All @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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)
Expand All @@ -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 = {
Expand All @@ -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)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down