Configurable dependency(factory)¶
Configurable dependencies are simple functions that produce dependencies. They should be used to create dependencies with different behavior based on the arguments passed to them.
from collections.abc import Mapping
from fundi import from_, scan, inject, configurable_dependency, Scope
def require_user() -> Mapping[str, str | tuple[str]]:
return {"username": "Kuyugama", "permissions": ("catch-apple",)}
@configurable_dependency
def require_permission(permission: str):
def checker(user: Mapping[str, str | tuple[str]] = from_(require_user)) -> None:
if permission not in user["permissions"]:
raise PermissionError(permission)
return checker
def application(
_=from_(require_permission("catch-apple")),
):
print("User has permission")
inject(Scope(), scan(application))
Note:
@configurable_dependencyis optional, but it caches dependencies, so their results can be cached on injection.Also,
@configurable_dependencydoes not cache dependencies configured with mutable arguments.
Composite dependencies¶
Composite dependencies are a special kind of configurable dependency that accept other dependencies as parameters.
These are a kind of higher-order functions, but with DI.
import typing
from collections.abc import Mapping
from fundi import from_, scan, inject, configurable_dependency, Scope
def require_user() -> dict[str, str | tuple[str]]:
return {"username": "Kuyugama", "permissions": ("catch-apple",)}
@configurable_dependency
def require_permission(
permission: str,
user_resolver: typing.Callable[..., Mapping[str, str | tuple[str]]] = require_user,
):
def checker(user: Mapping[str, str | tuple[str]] = from_(user_resolver)) -> None:
if permission not in user["permissions"]:
raise PermissionError(permission)
return checker
def application(
_=from_(require_permission("catch-apple")),
):
print("User has permission")
inject(Scope(), scan(application))
Dependency configuration¶
When a configurable dependency is called, FunDI stores its configuration, so third-party tools (e.g. routers, docs generators, validators) can extract the metadata.
To get configuration of already scanned(using fundi.scan.scan) dependency —
you can use CallableInfo.configuration attribute
If dependency is not scanned — use is_configured(call) function to check whether dependency is configured:
from fundi import is_configured, configurable_dependency
@configurable_dependency
def auth(optional: bool = False):
return lambda: optional
assert is_configured(auth())
And to get dependency configuration use get_configuration(call) function on dependency callable:
import inspect
from fundi import get_configuration, configurable_dependency
@configurable_dependency
def auth(optional: bool = False):
return lambda: optional
config = get_configuration(auth())
origin = inspect.unwrap(auth)
assert config.configurator.call == origin
assert config.values == {"optional": False}