Source code for fundi.configurable

from collections.abc import Callable
import typing
import warnings
import functools

from fundi.scan import scan
from fundi.util import callable_str
from fundi.types import DependencyConfiguration

P = typing.ParamSpec("P")
InnerP = typing.ParamSpec("InnerP")
R = typing.TypeVar("R")


class MutableConfigurationWarning(UserWarning):
    pass


class DependencyConfiguratorProtocol(typing.Protocol[P, InnerP, R]):
    origin: Callable[P, Callable[InnerP, R]]
    """
    Original dependency configurator.
    This is the function that passed to @configurable_dependency decorator
    """

    def __call__(
        self, *args: P.args, **kwargs: P.kwargs
    ) -> "ConfiguredDependencyProtocol[InnerP, R]": ...


class ConfiguredDependencyProtocol(typing.Protocol[P, R]):
    __fundi_configuration__: DependencyConfiguration

    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ...


[docs] def configurable_dependency( configurator: Callable[P, Callable[InnerP, R]], ) -> DependencyConfiguratorProtocol[P, InnerP, R]: """ Create dependency configurator that caches configured dependencies. This helps FunDI cache resolver understand that dependency already executed, if it was. Note: Calls with mutable arguments will not be stored in cache and warning would be shown :param configurator: Original dependency configurator :return: cache aware dependency configurator """ dependencies: dict[ frozenset[tuple[str, typing.Any]], ConfiguredDependencyProtocol[InnerP, R] ] = {} info = scan(configurator) if info.async_: raise ValueError("Dependency configurator should not be asynchronous") @functools.wraps(configurator) def cached_dependency_generator( *args: typing.Any, **kwargs: typing.Any ) -> ConfiguredDependencyProtocol[InnerP, R]: values = info.build_values(*args, **kwargs) key: frozenset[tuple[str, typing.Any]] | None = None try: key = frozenset(values.items()) if key in dependencies: return dependencies[key] except TypeError: warnings.warn( f"Can't cache dependency created via {callable_str(configurator)}: configured with unhashable arguments", MutableConfigurationWarning, ) dependency = configurator(*args, **kwargs) setattr( dependency, "__fundi_configuration__", DependencyConfiguration(configurator=info, values=values), ) dependency = typing.cast(ConfiguredDependencyProtocol[InnerP, R], dependency) if key is not None: dependencies[key] = dependency return dependency setattr(cached_dependency_generator, "origin", configurator) return typing.cast(DependencyConfiguratorProtocol, cached_dependency_generator)