mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-25 11:04:01 +03:00
Wiring config (#516)
* Implement POC * Implement concept with WiringConfiguration object * Update changelog * Add docs * Update changelog
This commit is contained in:
parent
08ea99759d
commit
73a43e6191
|
@ -11,6 +11,7 @@ Develop
|
||||||
-------
|
-------
|
||||||
- Improve wiring with adding importing modules and packages from a string
|
- Improve wiring with adding importing modules and packages from a string
|
||||||
``container.wire(modules=["yourapp.module1"])``.
|
``container.wire(modules=["yourapp.module1"])``.
|
||||||
|
- Add container wiring configuration ``wiring_config = containers.WiringConfiguration()``.
|
||||||
- Update documentation and fix typos.
|
- Update documentation and fix typos.
|
||||||
|
|
||||||
4.36.2
|
4.36.2
|
||||||
|
|
|
@ -215,7 +215,7 @@ Method ``container.wire()`` can resolve relative imports:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# In module "yourapp.foo":
|
# In module "yourapp.main":
|
||||||
|
|
||||||
container.wire(
|
container.wire(
|
||||||
modules=[
|
modules=[
|
||||||
|
@ -348,6 +348,76 @@ You can use that in testing to re-create and re-wire a container before each tes
|
||||||
|
|
||||||
module.fn()
|
module.fn()
|
||||||
|
|
||||||
|
Wiring configuration
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
You can specify wiring configuration in the container. When wiring configuration is defined,
|
||||||
|
container will call method ``.wire()`` automatically when you create an instance:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
wiring_config = containers.WiringConfiguration(
|
||||||
|
modules=[
|
||||||
|
"yourapp.module1",
|
||||||
|
"yourapp.module2",
|
||||||
|
],
|
||||||
|
packages=[
|
||||||
|
"yourapp.package1",
|
||||||
|
"yourapp.package2",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container() # container.wire() is called automatically
|
||||||
|
...
|
||||||
|
|
||||||
|
You can also use relative imports. Container will resolve them corresponding
|
||||||
|
to the module of the container class:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# In module "yourapp.container":
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
wiring_config = containers.WiringConfiguration(
|
||||||
|
modules=[
|
||||||
|
".module1", # Resolved to: "yourapp.module1"
|
||||||
|
".module2", # Resolved to: "yourapp.module2"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# In module "yourapp.foo.bar.main":
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container() # wire to "yourapp.module1" and "yourapp.module2"
|
||||||
|
...
|
||||||
|
|
||||||
|
To use wiring configuration and call method ``.wire()`` manually, set flag ``auto_wire=False``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 5
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
wiring_config = containers.WiringConfiguration(
|
||||||
|
modules=["yourapp.module1"],
|
||||||
|
auto_wire=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container() # container.wire() is NOT called automatically
|
||||||
|
container.wire() # wire to "yourapp.module1"
|
||||||
|
...
|
||||||
|
|
||||||
.. _async-injections-wiring:
|
.. _async-injections-wiring:
|
||||||
|
|
||||||
Asynchronous injections
|
Asynchronous injections
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,6 +3,7 @@ from typing import (
|
||||||
Generic,
|
Generic,
|
||||||
Type,
|
Type,
|
||||||
Dict,
|
Dict,
|
||||||
|
List,
|
||||||
Tuple,
|
Tuple,
|
||||||
Optional,
|
Optional,
|
||||||
Any,
|
Any,
|
||||||
|
@ -26,11 +27,20 @@ T = TypeVar('T')
|
||||||
TT = TypeVar('TT')
|
TT = TypeVar('TT')
|
||||||
|
|
||||||
|
|
||||||
|
class WiringConfiguration:
|
||||||
|
modules: List[Any]
|
||||||
|
packages: List[Any]
|
||||||
|
from_package: Optional[str]
|
||||||
|
auto_wire: bool
|
||||||
|
def __init__(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None, from_package: Optional[str] = None, auto_wire: bool = True) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
class Container:
|
class Container:
|
||||||
provider_type: Type[Provider] = Provider
|
provider_type: Type[Provider] = Provider
|
||||||
providers: Dict[str, Provider]
|
providers: Dict[str, Provider]
|
||||||
dependencies: Dict[str, Provider]
|
dependencies: Dict[str, Provider]
|
||||||
overridden: Tuple[Provider]
|
overridden: Tuple[Provider]
|
||||||
|
wiring_config: WiringConfiguration
|
||||||
__self__: Self
|
__self__: Self
|
||||||
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: ...
|
||||||
|
@ -43,6 +53,7 @@ class Container:
|
||||||
def override_providers(self, **overriding_providers: Union[Provider, Any]) -> None: ...
|
def override_providers(self, **overriding_providers: Union[Provider, Any]) -> None: ...
|
||||||
def reset_last_overriding(self) -> None: ...
|
def reset_last_overriding(self) -> None: ...
|
||||||
def reset_override(self) -> None: ...
|
def reset_override(self) -> None: ...
|
||||||
|
def is_auto_wiring_enabled(self) -> bool: ...
|
||||||
def wire(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None, from_package: Optional[str] = None) -> None: ...
|
def wire(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None, from_package: Optional[str] = None) -> None: ...
|
||||||
def unwire(self) -> None: ...
|
def unwire(self) -> None: ...
|
||||||
def init_resources(self) -> Optional[Awaitable]: ...
|
def init_resources(self) -> Optional[Awaitable]: ...
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Containers module."""
|
"""Containers module."""
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import copy as copy_module
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import importlib
|
import importlib
|
||||||
|
@ -32,6 +33,19 @@ else:
|
||||||
raise NotImplementedError('Wiring requires Python 3.6 or above')
|
raise NotImplementedError('Wiring requires Python 3.6 or above')
|
||||||
|
|
||||||
|
|
||||||
|
class WiringConfiguration:
|
||||||
|
"""Container wiring configuration."""
|
||||||
|
|
||||||
|
def __init__(self, modules=None, packages=None, from_package=None, auto_wire=True):
|
||||||
|
self.modules = [*modules] if modules else []
|
||||||
|
self.packages = [*packages] if packages else []
|
||||||
|
self.from_package = from_package
|
||||||
|
self.auto_wire = auto_wire
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo=None):
|
||||||
|
return self.__class__(self.modules, self.packages, self.from_package, self.auto_wire)
|
||||||
|
|
||||||
|
|
||||||
class Container(object):
|
class Container(object):
|
||||||
"""Abstract container."""
|
"""Abstract container."""
|
||||||
|
|
||||||
|
@ -77,6 +91,7 @@ class DynamicContainer(Container):
|
||||||
self.overridden = tuple()
|
self.overridden = tuple()
|
||||||
self.parent = None
|
self.parent = None
|
||||||
self.declarative_parent = None
|
self.declarative_parent = None
|
||||||
|
self.wiring_config = WiringConfiguration()
|
||||||
self.wired_to_modules = []
|
self.wired_to_modules = []
|
||||||
self.wired_to_packages = []
|
self.wired_to_packages = []
|
||||||
self.__self__ = providers.Self(self)
|
self.__self__ = providers.Self(self)
|
||||||
|
@ -97,6 +112,7 @@ class DynamicContainer(Container):
|
||||||
|
|
||||||
copied.provider_type = providers.Provider
|
copied.provider_type = providers.Provider
|
||||||
copied.overridden = providers.deepcopy(self.overridden, memo)
|
copied.overridden = providers.deepcopy(self.overridden, memo)
|
||||||
|
copied.wiring_config = copy_module.deepcopy(self.wiring_config, memo)
|
||||||
copied.declarative_parent = self.declarative_parent
|
copied.declarative_parent = self.declarative_parent
|
||||||
|
|
||||||
for name, provider in providers.deepcopy(self.providers, memo).items():
|
for name, provider in providers.deepcopy(self.providers, memo).items():
|
||||||
|
@ -251,22 +267,41 @@ class DynamicContainer(Container):
|
||||||
for provider in six.itervalues(self.providers):
|
for provider in six.itervalues(self.providers):
|
||||||
provider.reset_override()
|
provider.reset_override()
|
||||||
|
|
||||||
|
def is_auto_wiring_enabled(self):
|
||||||
|
"""Check if auto wiring is needed."""
|
||||||
|
return self.wiring_config.auto_wire is True
|
||||||
|
|
||||||
def wire(self, modules=None, packages=None, from_package=None):
|
def wire(self, modules=None, packages=None, from_package=None):
|
||||||
"""Wire container providers with provided packages and modules.
|
"""Wire container providers with provided packages and modules.
|
||||||
|
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
if modules is None and self.wiring_config.modules:
|
||||||
|
modules = self.wiring_config.modules
|
||||||
|
if packages is None and self.wiring_config.packages:
|
||||||
|
packages = self.wiring_config.packages
|
||||||
|
|
||||||
modules = [*modules] if modules else []
|
modules = [*modules] if modules else []
|
||||||
packages = [*packages] if packages else []
|
packages = [*packages] if packages else []
|
||||||
|
|
||||||
if _any_relative_string_imports_in(modules) or _any_relative_string_imports_in(packages):
|
if _any_relative_string_imports_in(modules) or _any_relative_string_imports_in(packages):
|
||||||
if from_package is None:
|
if from_package is None:
|
||||||
|
if self.wiring_config.from_package is not None:
|
||||||
|
from_package = self.wiring_config.from_package
|
||||||
|
elif self.declarative_parent is not None \
|
||||||
|
and (self.wiring_config.modules or self.wiring_config.packages):
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
from_package = _resolve_package_name_from_cls(self.declarative_parent)
|
||||||
|
else:
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
from_package = _resolve_calling_package_name()
|
from_package = _resolve_calling_package_name()
|
||||||
|
|
||||||
modules = _resolve_string_imports(modules, from_package)
|
modules = _resolve_string_imports(modules, from_package)
|
||||||
packages = _resolve_string_imports(packages, from_package)
|
packages = _resolve_string_imports(packages, from_package)
|
||||||
|
|
||||||
|
if not modules and not packages:
|
||||||
|
return
|
||||||
|
|
||||||
wire(
|
wire(
|
||||||
container=self,
|
container=self,
|
||||||
modules=modules,
|
modules=modules,
|
||||||
|
@ -463,10 +498,20 @@ class DeclarativeContainerMetaClass(type):
|
||||||
all_providers.update(inherited_providers)
|
all_providers.update(inherited_providers)
|
||||||
all_providers.update(cls_providers)
|
all_providers.update(cls_providers)
|
||||||
|
|
||||||
|
wiring_config = attributes.get("wiring_config")
|
||||||
|
if wiring_config is None:
|
||||||
|
wiring_config = WiringConfiguration()
|
||||||
|
if wiring_config is not None and not isinstance(wiring_config, WiringConfiguration):
|
||||||
|
raise errors.Error(
|
||||||
|
"Wiring configuration should be an instance of WiringConfiguration, "
|
||||||
|
"instead got {0}".format(wiring_config)
|
||||||
|
)
|
||||||
|
|
||||||
attributes['containers'] = containers
|
attributes['containers'] = containers
|
||||||
attributes['inherited_providers'] = inherited_providers
|
attributes['inherited_providers'] = inherited_providers
|
||||||
attributes['cls_providers'] = cls_providers
|
attributes['cls_providers'] = cls_providers
|
||||||
attributes['providers'] = all_providers
|
attributes['providers'] = all_providers
|
||||||
|
attributes['wiring_config'] = wiring_config
|
||||||
|
|
||||||
cls = <type>type.__new__(mcs, class_name, bases, attributes)
|
cls = <type>type.__new__(mcs, class_name, bases, attributes)
|
||||||
|
|
||||||
|
@ -617,6 +662,12 @@ class DeclarativeContainer(Container):
|
||||||
:type: dict[str, :py:class:`dependency_injector.providers.Provider`]
|
:type: dict[str, :py:class:`dependency_injector.providers.Provider`]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
wiring_config = WiringConfiguration()
|
||||||
|
"""Wiring configuration.
|
||||||
|
|
||||||
|
:type: WiringConfiguration
|
||||||
|
"""
|
||||||
|
|
||||||
cls_providers = dict()
|
cls_providers = dict()
|
||||||
"""Read-only dictionary of current container providers.
|
"""Read-only dictionary of current container providers.
|
||||||
|
|
||||||
|
@ -649,6 +700,7 @@ class DeclarativeContainer(Container):
|
||||||
"""
|
"""
|
||||||
container = cls.instance_type()
|
container = cls.instance_type()
|
||||||
container.provider_type = cls.provider_type
|
container.provider_type = cls.provider_type
|
||||||
|
container.wiring_config = copy_module.deepcopy(cls.wiring_config)
|
||||||
container.declarative_parent = cls
|
container.declarative_parent = cls
|
||||||
|
|
||||||
copied_providers = providers.deepcopy({ **cls.providers, **{'@@self@@': cls.__self__}})
|
copied_providers = providers.deepcopy({ **cls.providers, **{'@@self@@': cls.__self__}})
|
||||||
|
@ -665,6 +717,9 @@ class DeclarativeContainer(Container):
|
||||||
container.override_providers(**overriding_providers)
|
container.override_providers(**overriding_providers)
|
||||||
container.apply_container_providers_overridings()
|
container.apply_container_providers_overridings()
|
||||||
|
|
||||||
|
if container.is_auto_wiring_enabled():
|
||||||
|
container.wire()
|
||||||
|
|
||||||
return container
|
return container
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -826,3 +881,8 @@ cpdef object _resolve_calling_package_name():
|
||||||
pre_last_frame = stack[0]
|
pre_last_frame = stack[0]
|
||||||
module = inspect.getmodule(pre_last_frame[0])
|
module = inspect.getmodule(pre_last_frame[0])
|
||||||
return module.__package__
|
return module.__package__
|
||||||
|
|
||||||
|
|
||||||
|
cpdef object _resolve_package_name_from_cls(cls):
|
||||||
|
module = importlib.import_module(cls.__module__)
|
||||||
|
return module.__package__
|
||||||
|
|
|
@ -11,7 +11,7 @@ from dependency_injector.wiring import (
|
||||||
register_loader_containers,
|
register_loader_containers,
|
||||||
unregister_loader_containers,
|
unregister_loader_containers,
|
||||||
)
|
)
|
||||||
from dependency_injector import errors
|
from dependency_injector import containers, errors
|
||||||
|
|
||||||
# Runtime import to avoid syntax errors in samples on Python < 3.5
|
# Runtime import to avoid syntax errors in samples on Python < 3.5
|
||||||
import os
|
import os
|
||||||
|
@ -361,6 +361,73 @@ class WiringWithStringModuleAndPackageNamesTest(unittest.TestCase):
|
||||||
self.assertIsInstance(service, Service)
|
self.assertIsInstance(service, Service)
|
||||||
|
|
||||||
|
|
||||||
|
class WiringWithWiringConfigInTheContainerTest(unittest.TestCase):
|
||||||
|
|
||||||
|
container: Container
|
||||||
|
original_wiring_config = Container.wiring_config
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
Container.wiring_config = self.original_wiring_config
|
||||||
|
self.container.unwire()
|
||||||
|
|
||||||
|
def test_absolute_names(self):
|
||||||
|
Container.wiring_config = containers.WiringConfiguration(
|
||||||
|
modules=["wiringsamples.module"],
|
||||||
|
packages=["wiringsamples.package"],
|
||||||
|
)
|
||||||
|
self.container = Container()
|
||||||
|
|
||||||
|
service = module.test_function()
|
||||||
|
self.assertIsInstance(service, Service)
|
||||||
|
|
||||||
|
from wiringsamples.package.subpackage.submodule import test_function
|
||||||
|
service = test_function()
|
||||||
|
self.assertIsInstance(service, Service)
|
||||||
|
|
||||||
|
def test_relative_names_with_explicit_package(self):
|
||||||
|
Container.wiring_config = containers.WiringConfiguration(
|
||||||
|
modules=[".module"],
|
||||||
|
packages=[".package"],
|
||||||
|
from_package="wiringsamples",
|
||||||
|
)
|
||||||
|
self.container = Container()
|
||||||
|
|
||||||
|
service = module.test_function()
|
||||||
|
self.assertIsInstance(service, Service)
|
||||||
|
|
||||||
|
from wiringsamples.package.subpackage.submodule import test_function
|
||||||
|
service = test_function()
|
||||||
|
self.assertIsInstance(service, Service)
|
||||||
|
|
||||||
|
def test_relative_names_with_auto_package(self):
|
||||||
|
Container.wiring_config = containers.WiringConfiguration(
|
||||||
|
modules=[".module"],
|
||||||
|
packages=[".package"],
|
||||||
|
)
|
||||||
|
self.container = Container()
|
||||||
|
|
||||||
|
service = module.test_function()
|
||||||
|
self.assertIsInstance(service, Service)
|
||||||
|
|
||||||
|
from wiringsamples.package.subpackage.submodule import test_function
|
||||||
|
service = test_function()
|
||||||
|
self.assertIsInstance(service, Service)
|
||||||
|
|
||||||
|
def test_auto_wire_disabled(self):
|
||||||
|
Container.wiring_config = containers.WiringConfiguration(
|
||||||
|
modules=[".module"],
|
||||||
|
auto_wire=False,
|
||||||
|
)
|
||||||
|
self.container = Container()
|
||||||
|
|
||||||
|
service = module.test_function()
|
||||||
|
self.assertIsInstance(service, Provide)
|
||||||
|
|
||||||
|
self.container.wire()
|
||||||
|
service = module.test_function()
|
||||||
|
self.assertIsInstance(service, Service)
|
||||||
|
|
||||||
|
|
||||||
class ModuleAsPackageTest(unittest.TestCase):
|
class ModuleAsPackageTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user