mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-10-31 16:07:51 +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