Wiring container injection (#353)

* Add container injections to wiring

* Add example

* Update docs

* Update changelog

* Improve typing
This commit is contained in:
Roman Mogylatov 2021-01-11 08:18:02 -05:00 committed by GitHub
parent 6e77a95909
commit 8dd8446d39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1626 additions and 1335 deletions

View File

@ -7,6 +7,10 @@ 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`_
Development version
-------------------
- Add container injection support for wiring.
4.6.1 4.6.1
----- -----
- Add Disqus comments widget to the provider's async injections docs page. - Add Disqus comments widget to the provider's async injections docs page.

View File

@ -77,6 +77,13 @@ You can use configuration, provided instance and sub-container providers as you
You can compound wiring and ``Resource`` provider to implement per-function execution scope. You can compound wiring and ``Resource`` provider to implement per-function execution scope.
See :ref:`Resources, wiring and per-function execution scope <resource-provider-wiring-closing>` for details. See :ref:`Resources, wiring and per-function execution scope <resource-provider-wiring-closing>` for details.
Also you can use ``Provide`` marker to inject a container.
.. literalinclude:: ../examples/wiring/example_container.py
:language: python
:emphasize-lines: 16-19
:lines: 3-
Wiring with modules and packages Wiring with modules and packages
-------------------------------- --------------------------------

View File

@ -0,0 +1,28 @@
"""Wiring container injection example."""
import sys
from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
class Service:
...
class Container(containers.DeclarativeContainer):
service = providers.Factory(Service)
@inject
def main(container: Container = Provide[Container]):
service = container.service()
...
if __name__ == '__main__':
container = Container()
container.wire(modules=[sys.modules[__name__]])
main()

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@ class Container:
provider_type: Type[Provider] = Provider provider_type: Type[Provider] = Provider
providers: Dict[str, Provider] providers: Dict[str, Provider]
overridden: Tuple[Provider] overridden: Tuple[Provider]
__self__: Provider
def __init__(self) -> None: ... def __init__(self) -> None: ...
def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> Provider: ... def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> Provider: ...
def __setattr__(self, name: str, value: Union[Provider, Any]) -> None: ... def __setattr__(self, name: str, value: Union[Provider, Any]) -> None: ...

View File

@ -13,6 +13,7 @@ import six
from .errors import Error from .errors import Error
from .providers cimport ( from .providers cimport (
Provider, Provider,
Object,
Resource, Resource,
deepcopy, deepcopy,
) )
@ -70,6 +71,7 @@ class DynamicContainer(object):
self.declarative_parent = None self.declarative_parent = None
self.wired_to_modules = [] self.wired_to_modules = []
self.wired_to_packages = [] self.wired_to_packages = []
self.__self__ = Object(self)
super(DynamicContainer, self).__init__() super(DynamicContainer, self).__init__()
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
@ -102,7 +104,7 @@ class DynamicContainer(object):
:rtype: None :rtype: None
""" """
if isinstance(value, Provider): if isinstance(value, Provider) and name != '__self__':
_check_provider_type(self, value) _check_provider_type(self, value)
self.providers[name] = value self.providers[name] = value
super(DynamicContainer, self).__setattr__(name, value) super(DynamicContainer, self).__setattr__(name, value)
@ -282,6 +284,8 @@ class DeclarativeContainerMetaClass(type):
cls = <type>type.__new__(mcs, class_name, bases, attributes) cls = <type>type.__new__(mcs, class_name, bases, attributes)
cls.__self__ = Object(cls)
for provider in six.itervalues(cls.providers): for provider in six.itervalues(cls.providers):
_check_provider_type(cls, provider) _check_provider_type(cls, provider)
@ -301,7 +305,7 @@ class DeclarativeContainerMetaClass(type):
:rtype: None :rtype: None
""" """
if isinstance(value, Provider): if isinstance(value, Provider) and name != '__self__':
_check_provider_type(cls, value) _check_provider_type(cls, value)
cls.providers[name] = value cls.providers[name] = value
cls.cls_providers[name] = value cls.cls_providers[name] = value
@ -381,6 +385,12 @@ class DeclarativeContainer(object):
:type: tuple[:py:class:`DeclarativeContainer`] :type: tuple[:py:class:`DeclarativeContainer`]
""" """
__self__ = None
"""Provider that provides current container.
:type: :py:class:`dependency_injector.providers.Provider`
"""
def __new__(cls, **overriding_providers): def __new__(cls, **overriding_providers):
"""Constructor. """Constructor.

View File

@ -18,6 +18,7 @@ from typing import (
Generic, Generic,
TypeVar, TypeVar,
Type, Type,
Union,
cast, cast,
) )
@ -75,8 +76,8 @@ class ProvidersMap:
def __init__(self, container): def __init__(self, container):
self._container = container self._container = container
self._map = self._create_providers_map( self._map = self._create_providers_map(
current_providers=container.providers, current_container=container,
original_providers=container.declarative_parent.providers, original_container=container.declarative_parent,
) )
def resolve_provider( def resolve_provider(
@ -173,9 +174,15 @@ class ProvidersMap:
@classmethod @classmethod
def _create_providers_map( def _create_providers_map(
cls, cls,
current_providers: Dict[str, providers.Provider], current_container: Container,
original_providers: Dict[str, providers.Provider], original_container: Container,
) -> Dict[providers.Provider, providers.Provider]: ) -> Dict[providers.Provider, providers.Provider]:
current_providers = current_container.providers
current_providers['__self__'] = current_container.__self__
original_providers = original_container.providers
original_providers['__self__'] = original_container.__self__
providers_map = {} providers_map = {}
for provider_name, current_provider in current_providers.items(): for provider_name, current_provider in current_providers.items():
original_provider = original_providers[provider_name] original_provider = original_providers[provider_name]
@ -184,8 +191,8 @@ class ProvidersMap:
if isinstance(current_provider, providers.Container) \ if isinstance(current_provider, providers.Container) \
and isinstance(original_provider, providers.Container): and isinstance(original_provider, providers.Container):
subcontainer_map = cls._create_providers_map( subcontainer_map = cls._create_providers_map(
current_providers=current_provider.container.providers, current_container=current_provider.container,
original_providers=original_provider.container.providers, original_container=original_provider.container,
) )
providers_map.update(subcontainer_map) providers_map.update(subcontainer_map)
@ -479,6 +486,12 @@ def _is_declarative_container_instance(instance: Any) -> bool:
and getattr(instance, 'declarative_parent', None) is not None) and getattr(instance, 'declarative_parent', None) is not None)
def _is_declarative_container(instance: Any) -> bool:
return (isinstance(instance, type)
and getattr(instance, '__IS_CONTAINER__', False) is True
and getattr(instance, 'declarative_parent', None) is None)
class ClassGetItemMeta(GenericMeta): class ClassGetItemMeta(GenericMeta):
def __getitem__(cls, item): def __getitem__(cls, item):
# Spike for Python 3.6 # Spike for Python 3.6
@ -487,8 +500,10 @@ class ClassGetItemMeta(GenericMeta):
class _Marker(Generic[T], metaclass=ClassGetItemMeta): class _Marker(Generic[T], metaclass=ClassGetItemMeta):
def __init__(self, provider: providers.Provider) -> None: def __init__(self, provider: Union[providers.Provider, Container]) -> None:
self.provider = provider if _is_declarative_container(provider):
provider = provider.__self__
self.provider: providers.Provider = provider
def __class_getitem__(cls, item) -> T: def __class_getitem__(cls, item) -> T:
return cls(item) return cls(item)

View File

@ -91,3 +91,7 @@ class ClassDecorator:
@inject @inject
def test_class_decorator(service: Service = Provide[Container.service]): def test_class_decorator(service: Service = Provide[Container.service]):
return service return service
def test_container(container: Container = Provide[Container]):
return container.service()

View File

@ -239,6 +239,10 @@ class WiringTest(unittest.TestCase):
service = module.test_class_decorator() service = module.test_class_decorator()
self.assertIsInstance(service, Service) self.assertIsInstance(service, Service)
def test_container(self):
service = module.test_container()
self.assertIsInstance(service, Service)
class WiringAndFastAPITest(unittest.TestCase): class WiringAndFastAPITest(unittest.TestCase):