Source code for injectable.autowiring.autowired_decorator

import inspect
from functools import wraps
from typing import TypeVar, Callable, get_args, _AnnotatedAlias, Union

from injectable.autowiring.autowired_type import _Autowired, Autowired
from injectable.errors import AutowiringError

R = TypeVar("R")


[docs] def autowired(func: Callable[..., R]) -> Callable[..., R]: """ Function decorator to setup dependency injection autowiring. Only parameters annotated with :class:`Autowired <injectable.Autowired>` will be autowired for injection. If no parameter is annotated with :class:`Autowired <injectable.Autowired>` an :class:`AutowiringError <injectable.errors.AutowiringError>` will be raised. An :class:`AutowiringError <injectable.errors.AutowiringError>` will also be raised if a parameter annotated with :class:`Autowired <injectable.Autowired>` is given a default value or if a non Autowired-annotated positional parameter is placed after an Autowired-annotated positional parameter. Before attempting to call an autowired function make sure :meth:`load_injection_container <injectable.load_injection_container>` was invoked. .. note:: This decorator can be applied to any function, not only an `__init__` method. .. note:: This decorator accepts no arguments and must be used without trailing parenthesis. Usage:: >>> from injectable import Autowired, autowired >>> >>> @autowired ... def foo(dep: Autowired(...)): ... ... """ signature = inspect.signature(func) autowired_parameters = [] for index, parameter in enumerate(signature.parameters.values()): annotation = _get_parameter_annotation(parameter) if not isinstance(annotation, _Autowired): if len(autowired_parameters) == 0 or parameter.kind in [ parameter.KEYWORD_ONLY, parameter.VAR_KEYWORD, ]: continue raise AutowiringError( "Non-Autowired positional parameter follows Autowired parameter" ) if parameter.default is not parameter.empty: raise AutowiringError("Default value assigned to Autowired parameter") if parameter.kind in (parameter.VAR_POSITIONAL, parameter.VAR_KEYWORD): raise AutowiringError(f"Autowired parameter is of kind {parameter.kind}") autowired_parameters.append(parameter) if len(autowired_parameters) == 0: raise AutowiringError("No parameter is typed with 'Autowired'") @wraps(func) def wrapper(*args, **kwargs) -> R: bound_arguments = signature.bind_partial(*args, **kwargs).arguments args = list(args) for parameter in autowired_parameters: if parameter.name in bound_arguments: continue annotation = _get_parameter_annotation(parameter) dependency = annotation.inject() if parameter.kind is parameter.POSITIONAL_ONLY: args.append(dependency) else: kwargs[parameter.name] = dependency return func(*args, **kwargs) return wrapper
def _get_parameter_annotation(parameter) -> Union[type, _Autowired]: if isinstance(parameter.annotation, _AnnotatedAlias): autowired_annotations = list( filter(lambda t: _is_autowired(t), get_args(parameter.annotation)) ) if len(autowired_annotations) == 0: return parameter.annotation if len(autowired_annotations) > 1: raise AutowiringError("Multiple Autowired annotations found") autowired_annotation = autowired_annotations[0] if not isinstance(autowired_annotation, _Autowired): return autowired_annotation(dependency=get_args(parameter.annotation)[0]) if autowired_annotation.dependency is None: return type(autowired_annotation)( dependency=get_args(parameter.annotation)[0], namespace=autowired_annotation.namespace, group=autowired_annotation.group, exclude_groups=autowired_annotation.exclude_groups, lazy=autowired_annotation.lazy, ) return autowired_annotation return parameter.annotation def _is_autowired(annotation) -> bool: return isinstance(annotation, _Autowired) or ( inspect.isclass(annotation) and issubclass(annotation, Autowired) )