MicroAPI supports FastAPI-style dependency injection via Depends(). Dependencies are resolved per-request, cached within the same request, and support both sync and async callables.
from microapi import Depends, Service, Schema
async def get_database():
"""Provide a database connection."""
return await create_db_connection()
service = Service("items")
@service.method
async def get_items(
payload: ItemQuery,
db = Depends(get_database),
) -> ItemList:
items = await db.query(payload.filter)
return ItemList(items=items)When get_items is called, MicroAPI will:
- Call
get_database()to resolve the dependency - Pass the result as the
dbparameter - Execute the handler
Depends(callable) is a sentinel marker. When MicroAPI sees a parameter with a Depends default value, it:
- Calls the dependency function
- Caches the result for the current request
- Injects the result into the handler
Dependencies are resolved before the handler is called.
Dependencies can be either sync or async:
# Async dependency
async def get_database():
return await Database.connect()
# Sync dependency
def get_config():
return {"version": "1.0", "debug": True}
@service.method
async def my_method(
payload: Payload,
db = Depends(get_database), # async
config = Depends(get_config), # sync
) -> Result:
...Dependencies can receive the current Request object by type-hinting a parameter:
from microapi.protocol import Request
async def get_current_user(req: Request) -> dict:
"""Extract user info from request metadata."""
user_id = req.metadata.get("user_id")
if not user_id:
raise ValueError("No user_id in metadata")
return {"id": user_id, "role": req.metadata.get("role", "user")}
@service.method
async def protected_action(
payload: ActionPayload,
user = Depends(get_current_user),
) -> ActionResult:
if user["role"] != "admin":
raise PermissionError("Admin only")
...This is especially powerful combined with middleware that sets metadata:
# Middleware sets user info
class AuthMiddleware(Middleware):
async def __call__(self, request, call_next):
token = request.metadata.get("authorization")
user = await verify_token(token)
request.metadata["user_id"] = str(user.id)
request.metadata["role"] = user.role
return await call_next(request)
# Dependency reads it
async def get_current_user(req: Request) -> dict:
return {
"id": req.metadata["user_id"],
"role": req.metadata["role"],
}Dependencies are cached within a single request. If multiple parameters depend on the same function, it's called only once:
async def get_database():
print("Connecting to database...") # Printed only once per request
return await Database.connect()
@service.method
async def handler(
payload: Payload,
db1 = Depends(get_database),
db2 = Depends(get_database), # Same instance as db1
) -> Result:
assert db1 is db2 # True!
...Dependencies can themselves have dependencies:
async def get_config():
return load_config()
async def get_database(config = Depends(get_config)):
return await Database.connect(config["db_url"])
async def get_user_repo(db = Depends(get_database)):
return UserRepository(db)
@service.method
async def get_user(
payload: GetUserPayload,
repo = Depends(get_user_repo),
) -> User:
return await repo.get(payload.user_id)Resolution order: get_config → get_database → get_user_repo → handler.
You can use callable classes as dependencies:
class DatabasePool:
def __init__(self, connection_string: str):
self.conn_str = connection_string
self.pool = None
async def __call__(self):
if not self.pool:
self.pool = await create_pool(self.conn_str)
return self.pool
# Create an instance
db_pool = DatabasePool("postgresql://localhost/mydb")
@service.method
async def query(
payload: QueryPayload,
pool = Depends(db_pool),
) -> QueryResult:
async with pool.acquire() as conn:
...A common pattern is to create a repository layer using dependencies:
# repositories.py
from microapi import Depends
from microapi.protocol import Request
async def get_db():
return await create_connection()
class UserRepository:
def __init__(self, db):
self.db = db
async def get(self, user_id: int) -> User | None:
row = await self.db.fetchrow("SELECT * FROM users WHERE id = $1", user_id)
return User(**row) if row else None
async def create(self, data: dict) -> User:
row = await self.db.fetchrow(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *",
data["name"], data["email"],
)
return User(**row)
async def get_user_repo(db = Depends(get_db)) -> UserRepository:
return UserRepository(db)
# service.py
@service.method
async def get_user(
payload: GetUserPayload,
repo: UserRepository = Depends(get_user_repo),
) -> User:
user = await repo.get(payload.user_id)
if not user:
raise NotFoundError(f"User {payload.user_id} not found")
return user- Dependencies are resolved per-request (no singleton/app-level scope)
- Generator dependencies (with
yield) are not yet supported — use lifespan hooks for setup/teardown - Dependencies don't have access to the raw transport-level connection