Skip to content

Dependencies

Django-Bolt provides a dependency injection system using the Depends marker. Dependencies let you extract common logic into reusable functions.

Basic usage

Creating a dependency

A dependency is any callable (function or class) that returns a value:

from django_bolt import BoltAPI, Depends

api = BoltAPI()

async def get_current_user(request):
    """Dependency that extracts the current user."""
    user_id = request.get("context", {}).get("user_id")
    if not user_id:
        raise HTTPException(status_code=401, detail="Not authenticated")
    return await User.objects.aget(id=user_id)

@api.get("/profile")
async def get_profile(user=Depends(get_current_user)):
    return {"id": user.id, "username": user.username}

Dependency resolution

When a handler parameter is annotated with Depends(), Django-Bolt:

  1. Calls the dependency function
  2. Passes the result to the handler
  3. Caches the result for subsequent uses in the same request

Dependency patterns

Request access

Dependencies receive the request dict:

async def get_db_connection(request):
    """Get database connection from request context."""
    return request.get("db_connection")

With parameters

Dependencies can accept the same parameters as handlers:

from typing import Annotated
from django_bolt.param_functions import Header

async def get_api_version(
    x_api_version: Annotated[str, Header()] = "v1"
):
    """Extract API version from header."""
    return x_api_version

@api.get("/data")
async def get_data(version=Depends(get_api_version)):
    if version == "v2":
        return {"format": "new"}
    return {"format": "legacy"}

Authentication dependency

from django_bolt.auth import get_current_user

@api.get("/me")
async def me(user=Depends(get_current_user)):
    return {
        "id": user.id,
        "username": user.username,
        "email": user.email
    }

Dependency caching

Default behavior

By default, dependencies are cached per-request:

call_count = 0

async def expensive_operation(request):
    global call_count
    call_count += 1
    # Expensive computation
    return {"count": call_count}

@api.get("/test")
async def test(
    dep1=Depends(expensive_operation),
    dep2=Depends(expensive_operation)  # Same dependency
):
    # expensive_operation is called ONCE, result is reused
    return {"dep1": dep1, "dep2": dep2}

Disabling cache

To force fresh calls, use use_cache=False:

@api.get("/fresh")
async def fresh(
    dep=Depends(some_dependency, use_cache=False)
):
    # Always gets fresh result
    return dep

Sync and async dependencies

Django-Bolt supports both sync and async dependencies:

# Async dependency
async def async_dep(request):
    user = await User.objects.aget(id=1)
    return user

# Sync dependency
def sync_dep(request):
    return {"timestamp": time.time()}

@api.get("/mixed")
async def mixed(
    user=Depends(async_dep),
    data=Depends(sync_dep)
):
    return {"user": user.username, "data": data}

Nested dependencies

Dependencies can depend on other dependencies:

async def get_settings(request):
    """Get application settings."""
    return await Settings.objects.afirst()

async def get_feature_flags(settings=Depends(get_settings)):
    """Get feature flags based on settings."""
    return {
        "new_ui": settings.enable_new_ui,
        "beta": settings.beta_features,
    }

@api.get("/features")
async def features(flags=Depends(get_feature_flags)):
    return flags

The dependency resolution is: 1. get_settings is called first 2. Its result is passed to get_feature_flags 3. get_feature_flags result is passed to the handler

Class-based dependencies

Classes can be used as dependencies:

class DatabaseSession:
    def __init__(self, request):
        self.request = request
        self.connection = None

    async def __aenter__(self):
        self.connection = await get_connection()
        return self.connection

    async def __aexit__(self, *args):
        if self.connection:
            await self.connection.close()

@api.get("/data")
async def get_data(db=Depends(DatabaseSession)):
    async with db:
        # Use database connection
        pass

Parameter extraction in dependencies

Dependencies can use the same parameter markers as handlers:

from typing import Annotated
from django_bolt.param_functions import Header, Cookie

async def verify_token(
    authorization: Annotated[str, Header()],
    session_id: Annotated[str, Cookie()]
):
    """Verify both token and session."""
    # Validate authorization header
    # Validate session cookie
    return {"valid": True}

@api.get("/secure")
async def secure_endpoint(auth=Depends(verify_token)):
    return {"status": "authenticated"}

Common dependency patterns

Database session

async def get_db(request):
    """Provide a database session."""
    from django.db import connection
    await connection.ensure_connection()
    return connection

@api.get("/users")
async def list_users(db=Depends(get_db)):
    return await User.objects.all()[:10]

Pagination

from dataclasses import dataclass

@dataclass
class Pagination:
    page: int
    page_size: int
    offset: int

def get_pagination(page: int = 1, page_size: int = 20) -> Pagination:
    """Extract pagination parameters."""
    return Pagination(
        page=page,
        page_size=min(page_size, 100),  # Max 100 per page
        offset=(page - 1) * page_size
    )

@api.get("/items")
async def list_items(pagination: Pagination = Depends(get_pagination)):
    items = await Item.objects.all()[
        pagination.offset:pagination.offset + pagination.page_size
    ]
    return {"page": pagination.page, "items": items}

Rate limit check

from django_bolt.exceptions import TooManyRequests

async def check_rate_limit(request):
    """Check if request is rate limited."""
    client_ip = request.get("headers", {}).get("x-forwarded-for", "unknown")
    # Check rate limit logic
    if is_rate_limited(client_ip):
        raise TooManyRequests(detail="Rate limit exceeded")
    return True

@api.get("/api/data")
async def get_data(_=Depends(check_rate_limit)):
    return {"data": "value"}

Permission check

async def require_admin(request):
    """Require admin user."""
    user_id = request.get("context", {}).get("user_id")
    if not user_id:
        raise Unauthorized()

    user = await User.objects.aget(id=user_id)
    if not user.is_staff:
        raise Forbidden(detail="Admin access required")

    return user

@api.delete("/users/{user_id}")
async def delete_user(user_id: int, admin=Depends(require_admin)):
    await User.objects.filter(id=user_id).adelete()
    return {"deleted": True}

Dependencies with ViewSets

Dependencies work with class-based views:

from django_bolt import ViewSet

@api.viewset("/items")
class ItemViewSet(ViewSet):
    queryset = Item.objects.all()

    async def list(self, request, pagination=Depends(get_pagination)):
        items = []
        queryset = await self.get_queryset()
        async for item in queryset[pagination.offset:pagination.offset + pagination.page_size]:
            items.append({"id": item.id, "name": item.name})
        return items

Testing with dependencies

Override dependencies in tests:

from django_bolt.testing import TestClient

def mock_get_user(request):
    return User(id=1, username="test")

# In tests, the actual dependency is replaced
@api.get("/profile")
async def profile(user=Depends(get_current_user)):
    return {"username": user.username}

# Testing (pseudocode - actual implementation may vary)
with TestClient(api) as client:
    # Override dependency
    response = client.get("/profile")