Dependency Injection¶
Shakti binds handler parameters automatically by inspecting each handler's signature — no decorators needed on the parameters themselves. For every parameter, in order:
- Default is
Depends(...)→ resolved as a dependency (see below). - Annotated
Request(or namedrequestwith no annotation) → the current request. - Name matches a path parameter → converted to the annotated type.
- Annotation is registered in the container → resolved from the container.
- Name is
body→ the parsed JSON request body. - Name matches a query parameter → converted to the annotated type.
- Parameter has a default value → used as-is.
- Otherwise →
422 Unprocessable Entity.
@app.get("/posts/{post_id:int}")
async def get_post(post_id: int, request: Request, db: Database) -> dict:
... # post_id: path param, request: current request, db: container-resolved
The container¶
Every Shakti app has a Container (app.container) used for step 4 above. Register instances or factories:
app.container.register_instance(Database, db) # a fixed instance
app.container.register(Cache, lambda: Cache(config)) # a factory, memoized (singleton=True by default)
app.container.register(Logger, make_logger, singleton=False) # a new instance per resolve()
Any type registered this way can then just show up as a type-annotated handler parameter and Shakti will supply it — no explicit Depends() required. Config and the Shakti app instance itself are registered automatically.
Depends()¶
For request-scoped dependencies that need their own logic (auth checks, per-request setup) rather than a container lookup, use Depends:
from shakti import Depends
async def current_user(request: Request, auth: Auth) -> User:
token = request.headers.get("authorization", "").removeprefix("Bearer ")
return await auth.get_user(token)
@app.get("/me")
async def me(user: User = Depends(current_user)) -> dict:
return {"id": user.id, "email": user.email}
Depends(dependency) resolves dependency the same way a handler is resolved (recursively, with the same binding rules) — so a dependency can itself take request, other Depends(...), or container-registered services.
By default, results are cached per-request: if two parameters (or a dependency and a handler) both depend on current_user, it only runs once. Pass use_cache=False to force it to re-run:
Type conversion¶
Path and query parameters arrive as strings and get converted to the annotated type (int, float, bool, or any callable single-arg constructor). Booleans accept 1/true/yes/on and 0/false/no/off (case-insensitive). A conversion failure raises 422 with a message naming the offending parameter.