Decorators — Aquilia Documentation
Comprehensive guide and documentation for Decorators in the Aquilia framework. View API reference, examples, and implementation patterns.
Dependency Injection / Decorators DI Decorators & Metadata Aquilia provides clear annotations under aquilia/di/decorators.py to configure scopes, inject instances, and define factory dependencies declaratively. Inject Dataclass The Inject dataclass is used within typing.Annotated hints to instruct providers on how to resolve dependencies: from dataclasses import dataclass from typing import Any, Optional @dataclass class Inject: token: Optional[Any] = None # Override resolution token tag: Optional[str] = None # Disambiguate between multiple providers optional: bool = False # Resolves to None if unregistered Usage with Annotated from typing import Annotated from aquilia.di import Inject class OrderService: def __init__( self, # Resolved by parameter type hint repo: OrderRepository, # Tagged resolution (disambiguate multiple CacheBackends) cache: Annotated[CacheBackend, Inject(tag="redis")], # Optional resolution (defaults to None if missing) metrics: Annotated[MetricsClient, Inject(optional=True)], ): self.repo = repo self.cache = cache self.metrics = metrics String Token & Cross-App Resolution String tokens allow resolving cross-module services or registered string identifiers without tight class imports: from typing import Annotated, Any from aquilia.di import Inject, inject class AuthController: def __init__( self, # Container unwraps Inject("modules.auth.services:CrossAppService") directly cross_app: Annotated[Any, Inject("modules.auth.services:CrossAppService")], # Shorthand inject() helper with tag and optional fallback auth_service: Annotated[Any, inject("modules.auth.services:AuthService", optional=True)], ): self.cross_app = cross_app self.auth_service = auth_service Internal Unwrapping: In Aquilia v1.3.4+, Container._unwrap_token() automatically unwraps Annotated[T, Inject("token")] aliases and direct Inject("token") markers, resolving target string tokens cleanly from container registries. inject() A shorthand helper that generates Inject configurations: from aquilia.di import inject class OrderService: def __init__( self, cache: Annotated[CacheBackend, inject(tag="redis")] ): self.cache = cache Dep (Per-Request Dependency Injection) FastAPI-Style Injection: Dep is Aquilia's modern approach to inline route injection. It allows you to declare dependencies directly in route signatures, bypassing manifest declarations for route-specific tools. Dependencies declared in route signatures form a per-request Directed Acyclic Graph (DAG) resolved concurrently: from typing import Annotated from aquilia.di import Dep from aquilia.controller import Controller, get async def get_db_session(): async with db.session() as session: yield session class UserController(Controller): prefix = "/users" # UserController constructor injection is still used for core services def __init__(self, auth: AuthService): self.auth = auth @get("/ ") async def get_user( self, ctx, db_session: Annotated[DbSession, Dep(get_db_session)] # resolved per-request ): user = await db_session.query(User).filter_by(id=ctx.request.params["user_id"]).first() return ctx.json(user) Conditional Providers Register a service only when a predicate passes — the Spring @Profile / @ConditionalOnProperty equivalent. The predicate receives a ConditionContext carrying the active environment and config. Use the when= parameter on @service, or the standalone @conditional decorator. Both are honoured at registration when enable_conditional_providers is on (default). from aquilia.di import service, conditional, ConditionContext # Register only in production via @service(when=...) @service(when=lambda c: c.env == "prod") class RealPaymentGateway: ... # Fake gateway everywhere else @service(when=lambda c: c.env != "prod") class FakePaymentGateway: ... # Standalone @conditional — matches prod OR staging (case-insensitive) @conditional(lambda c: c.is_env("prod", "staging")) class MetricsExporter: ... # Property-based: dot-path lookup into config @conditional(lambda c: c.get("cache.backend") == "redis") class RedisCacheWarmup: ... ConditionContext is a frozen dataclass with two fields and two helpers: @dataclass(frozen=True, slots=True) class ConditionContext: env: str = "prod" # active env (AQUILIA_ENV or config "env") config: Any = None # raw config mapping/loader def get(self, path: str, default=None) -> Any: # dot-path lookup: "cache.backend" ... def is_env(self, *names: str) -> bool: # case-insensitive env match ... Safe by default: a service with no condition always registers. If a predicate raises, the service is skipped (treated as False) and boot continues — a bad predicate never crashes startup. Use should_register(target, ctx) to evaluate a predicate manually. @factory & @provides Use @factory when construction needs logic (async connect, config-driven choice). Use @provides(Token) when the factory returns an abstract/interface type and you want to bind it under that token. Both take scope (default "app"), tag, and inject their own parameters. from aquilia.di import factory, provides @factory(scope="singleton", name="db_pool") async def create_db_pool(config: AppConfig) -> DatabasePool: return await DatabasePool.connect(config.db_url) @provides(UserRepository, scope="app", tag="sql") def build_repo(db: DatabasePool) -> UserRepository: return SqlUserRepository(db) Required annotations. The ClassProvider reads constructor type hints. A parameter with no annotation and no default raises DIError at build. A parameter with a default is treated as optional and skipped by DI. Define an async def async_init(self) for construction steps that need await. Registration Decorators Decorator Scope Description , , , , , , , , ].map((row, i) => ( ))} Scopes Lifecycle )
Go to Homepage