mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-11-04 01:47:36 +03:00 
			
		
		
		
	Implement wiring autoloader
This commit is contained in:
		
							parent
							
								
									9225f9dcd6
								
							
						
					
					
						commit
						41e18d2c89
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user