python-dependency-injector/src/dependency_injector/wiring.py
2020-09-17 22:06:34 -04:00

129 lines
3.6 KiB
Python

"""Wiring module."""
import functools
import inspect
import pkgutil
from types import ModuleType
from typing import Optional, Iterable, Callable, Any, Type, Dict
from . import providers
AnyContainer = Any
def wire(
container: AnyContainer,
*,
modules: Optional[Iterable[ModuleType]] = None,
packages: Optional[Iterable[ModuleType]] = None,
) -> None:
"""Wire container providers with provided packages and modules by name."""
if not modules:
modules = []
if packages:
for package in packages:
modules.extend(_fetch_modules(package))
for module in modules:
for name, member in inspect.getmembers(module):
if inspect.isfunction(member):
_patch_fn(module, name, member, container)
elif inspect.isclass(member):
_patch_cls(member, container)
def _patch_cls(
cls: Type[Any],
container: AnyContainer,
) -> None:
if not hasattr(cls, '__init__'):
return
init_method = getattr(cls, '__init__')
injections = _resolve_injections(init_method, container)
if not injections:
return
setattr(cls, '__init__', _patch_with_injections(init_method, injections))
def _patch_fn(
module: ModuleType,
name: str,
fn: Callable[..., Any],
container: AnyContainer,
) -> None:
injections = _resolve_injections(fn, container)
if not injections:
return
setattr(module, name, _patch_with_injections(fn, injections))
def _resolve_injections(fn: Callable[..., Any], container: AnyContainer) -> Dict[str, Any]:
signature = inspect.signature(fn)
config = _resolve_container_config(container)
injections = {}
for parameter_name, parameter in signature.parameters.items():
if parameter_name in container.providers:
injections[parameter_name] = container.providers[parameter_name]
if parameter_name.endswith('_provider'):
provider_name = parameter_name[:-9]
if provider_name in container.providers:
injections[parameter_name] = container.providers[provider_name].provider
if config and isinstance(parameter.default, ConfigurationOption):
option_provider = config.get_option_provider(parameter.default.selector)
if parameter.annotation:
injections[parameter_name] = option_provider.as_(parameter.annotation)
else:
injections[parameter_name] = option_provider
return injections
def _resolve_container_config(container: AnyContainer) -> Optional[providers.Configuration]:
for provider in container.providers.values():
if isinstance(provider, providers.Configuration):
return provider
else:
return None
def _fetch_modules(package):
modules = []
for loader, module_name, is_pkg in pkgutil.walk_packages(
path=package.__path__,
prefix=package.__name__ + '.',
):
module = loader.find_module(module_name).load_module(module_name)
modules.append(module)
return modules
def _patch_with_injections(fn, injections):
@functools.wraps(fn)
def _patched(*args, **kwargs):
to_inject = {}
for injection, provider in injections.items():
to_inject[injection] = provider()
to_inject.update(kwargs)
return fn(*args, **to_inject)
return _patched
class ConfigurationOption:
"""Configuration option marker."""
def __init__(self, selector: str):
self.selector = selector
def __class_getitem__(cls, item):
return cls(item)