Merge branch 'release/4.12.0' into master

This commit is contained in:
Roman Mogylatov 2021-01-28 19:50:49 -05:00
commit 1f17bc6e08
5 changed files with 176 additions and 2 deletions

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`_
4.12.0
------
- Add wiring import hook that auto-wires dynamically imported modules.
See issue: `#365 <https://github.com/ets-labs/python-dependency-injector/issues/365>`_.
Thanks to `@Balthus1989 <https://github.com/Balthus1989>`_ for providing a use case.
4.11.3
------
- Replace weakrefs with normal refs in ``ConfigurationOption`` to support

View File

@ -18,6 +18,10 @@ To use wiring you need:
:language: python
:lines: 3-
.. contents::
:local:
:backlinks: none
Markers
-------
@ -274,6 +278,35 @@ See also:
- Resource provider :ref:`resource-async-initializers`
- :ref:`fastapi-redis-example`
Wiring of dynamically imported modules
--------------------------------------
You can install an import hook that automatically wires containers to the imported modules.
This is useful when you import modules dynamically.
.. code-block:: python
import importlib
from dependency_injector.wiring import register_loader_containers
from .containers import Container
if __name__ == '__main__':
container = Container()
register_loader_containers(container) # <--- installs import hook
module = importlib.import_module('package.module')
module.foo()
You can register multiple containers in the import hook. For doing this call register function
with multiple containers ``register_loader_containers(container1, container2, ...)``
or with a single container ``register_loader_containers(container)`` multiple times.
To unregister a container use ``unregister_loader_containers(container)``.
Wiring module will uninstall the import hook when unregister last container.
Integration with other frameworks
---------------------------------

View File

@ -1,6 +1,6 @@
"""Top-level package."""
__version__ = '4.11.3'
__version__ = '4.12.0'
"""Version number.
:type: str

View File

@ -4,6 +4,7 @@ import asyncio
import functools
import inspect
import importlib
import importlib.machinery
import pkgutil
import sys
from types import ModuleType
@ -52,6 +53,11 @@ __all__ = (
'Provide',
'Provider',
'Closing',
'register_loader_containers',
'unregister_loader_containers',
'install_loader',
'uninstall_loader',
'is_loader_installed',
)
T = TypeVar('T')
@ -535,3 +541,98 @@ class Provider(_Marker):
class Closing(_Marker):
...
class AutoLoader:
"""Auto-wiring module loader.
Automatically wire containers when modules are imported.
"""
def __init__(self):
self.containers = []
self._path_hook = None
def register_containers(self, *containers):
self.containers.extend(containers)
if not self.installed:
self.install()
def unregister_containers(self, *containers):
for container in containers:
self.containers.remove(container)
if not self.containers:
self.uninstall()
def wire_module(self, module):
for container in self.containers:
container.wire(modules=[module])
@property
def installed(self):
return self._path_hook is not None
def install(self):
if self.installed:
return
loader = self
class SourcelessFileLoader(importlib.machinery.SourcelessFileLoader):
def exec_module(self, module):
super().exec_module(module)
loader.wire_module(module)
class SourceFileLoader(importlib.machinery.SourceFileLoader):
def exec_module(self, module):
super().exec_module(module)
loader.wire_module(module)
loader_details = [
(SourcelessFileLoader, importlib.machinery.BYTECODE_SUFFIXES),
(SourceFileLoader, importlib.machinery.SOURCE_SUFFIXES),
]
self._path_hook = importlib.machinery.FileFinder.path_hook(*loader_details)
sys.path_hooks.insert(0, self._path_hook)
sys.path_importer_cache.clear()
importlib.invalidate_caches()
def uninstall(self):
if not self.installed:
return
sys.path_hooks.remove(self._path_hook)
sys.path_importer_cache.clear()
importlib.invalidate_caches()
_loader = AutoLoader()
def register_loader_containers(*containers: Container) -> None:
"""Register containers in auto-wiring module loader."""
_loader.register_containers(*containers)
def unregister_loader_containers(*containers: Container) -> None:
"""Unregister containers from auto-wiring module loader."""
_loader.unregister_containers(*containers)
def install_loader() -> None:
"""Install auto-wiring module loader hook."""
_loader.install()
def uninstall_loader() -> None:
"""Uninstall auto-wiring module loader hook."""
_loader.uninstall()
def is_loader_installed() -> bool:
"""Check if auto-wiring module loader hook is installed."""
return _loader.installed

View File

@ -1,7 +1,15 @@
import contextlib
from decimal import Decimal
import importlib
import unittest
from dependency_injector.wiring import wire, Provide, Closing
from dependency_injector.wiring import (
wire,
Provide,
Closing,
register_loader_containers,
unregister_loader_containers,
)
from dependency_injector import errors
# Runtime import to avoid syntax errors in samples on Python < 3.5
@ -367,3 +375,29 @@ class WiringAsyncInjectionsTest(AsyncTestCase):
self.assertIs(resource2, asyncinjections.resource2)
self.assertEqual(asyncinjections.resource2.init_counter, 2)
self.assertEqual(asyncinjections.resource2.shutdown_counter, 2)
class AutoLoaderTest(unittest.TestCase):
container: Container
def setUp(self) -> None:
self.container = Container(config={'a': {'b': {'c': 10}}})
importlib.reload(module)
def tearDown(self) -> None:
with contextlib.suppress(ValueError):
unregister_loader_containers(self.container)
self.container.unwire()
@classmethod
def tearDownClass(cls) -> None:
importlib.reload(module)
def test_register_container(self):
register_loader_containers(self.container)
importlib.reload(module)
service = module.test_function()
self.assertIsInstance(service, Service)