Django and Django REST Framework Integration¶
dioxide provides seamless integration with Django and Django REST Framework (DRF) through the dioxide.django module.
Installation¶
pip install dioxide[django]
This installs dioxide along with Django as an optional dependency.
Quick Start¶
1. Configure dioxide¶
In your Django settings.py or in your app’s apps.py ready() method:
# settings.py
from dioxide import Profile
from dioxide.django import configure_dioxide
# Configure at module level (runs when Django loads settings)
configure_dioxide(profile=Profile.PRODUCTION, packages=["myapp"])
Or in your app configuration:
# myapp/apps.py
from django.apps import AppConfig
from dioxide import Profile
from dioxide.django import configure_dioxide
class MyAppConfig(AppConfig):
name = "myapp"
def ready(self):
configure_dioxide(profile=Profile.PRODUCTION, packages=["myapp"])
2. Add Middleware¶
Add the dioxide middleware to your MIDDLEWARE setting:
# settings.py
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"dioxide.django.DioxideMiddleware", # Add dioxide middleware
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
3. Use in Views¶
Inject dependencies using the inject() function:
# views.py
from django.http import JsonResponse
from dioxide.django import inject
def user_list(request):
service = inject(UserService)
users = service.list_all()
return JsonResponse({"users": users})
def user_detail(request, user_id):
service = inject(UserService)
user = service.get_user(user_id)
return JsonResponse(user)
API Reference¶
configure_dioxide()¶
def configure_dioxide(
profile: Profile | str | None = None,
container: Container | None = None,
packages: list[str] | None = None,
) -> None:
Configure dioxide dependency injection for a Django application.
Parameters:
Parameter |
Type |
Description |
|---|---|---|
|
|
Profile to scan with (e.g., |
|
|
Optional Container instance. Uses global singleton if not provided |
|
|
Optional list of packages to scan for components |
Example:
from dioxide import Container, Profile
from dioxide.django import configure_dioxide
# Basic usage
configure_dioxide(profile=Profile.PRODUCTION)
# With specific packages
configure_dioxide(
profile=Profile.PRODUCTION,
packages=["myapp.services", "myapp.adapters"],
)
# With custom container
my_container = Container()
configure_dioxide(profile=Profile.TEST, container=my_container)
DioxideMiddleware¶
Django middleware that creates a ScopedContainer for each HTTP request.
What it does:
Creates a
ScopedContainerbefore the view runsStores it in thread-local storage for
inject()to accessDisposes the scope after the response is returned
Placement:
The middleware should be placed after session/auth middleware but before your application middleware.
inject()¶
def inject(component_type: type[T]) -> T:
Resolve a component from the current request’s dioxide scope.
Parameters:
Parameter |
Type |
Description |
|---|---|---|
|
|
The type to resolve from the container |
Returns: An instance of the requested type.
Raises:
RuntimeError: If called outside a request contextRuntimeError: Ifconfigure_dioxide()was not called
Example:
from dioxide.django import inject
def my_view(request):
# Resolve dependencies
user_service = inject(UserService)
email_service = inject(EmailService)
# Use the services
user = user_service.get_current_user()
email_service.send_welcome(user.email)
return JsonResponse({"status": "ok"})
Request Scoping¶
The middleware creates a ScopedContainer for each HTTP request, enabling request-scoped dependencies:
from dioxide import service, Scope
import uuid
@service(scope=Scope.REQUEST)
class RequestContext:
"""Request-scoped context available throughout a single request."""
def __init__(self):
self.request_id = str(uuid.uuid4())
self.start_time = time.time()
# In views - same instance within the request
def my_view(request):
ctx = inject(RequestContext)
# ctx.request_id is unique per request but consistent
# across multiple inject() calls within the same request
return JsonResponse({"request_id": ctx.request_id})
def another_view(request):
ctx = inject(RequestContext)
# Different request = different request_id
return JsonResponse({"request_id": ctx.request_id})
Scope Behavior¶
Scope |
Behavior |
|---|---|
|
Shared across all requests (resolved from parent container) |
|
Fresh instance per HTTP request (resolved from scoped container) |
|
New instance every time |
Thread Safety¶
Django uses threading by default. The dioxide integration handles this by storing the scoped container in thread-local storage:
Each thread gets its own request scope
No cross-request contamination
Works correctly with Django’s threaded request handling
Compatible with WSGI servers like gunicorn with sync workers
Lifecycle Management¶
dioxide handles component lifecycle automatically:
from dioxide import adapter, lifecycle, Profile
from typing import Protocol
class DatabasePort(Protocol):
def query(self, sql: str) -> list: ...
@adapter.for_(DatabasePort, profile=Profile.PRODUCTION)
@lifecycle
class PostgresAdapter:
"""Database adapter with lifecycle management."""
async def initialize(self) -> None:
"""Called when configure_dioxide() starts the container."""
self.pool = await asyncpg.create_pool(DATABASE_URL)
print("Database pool created")
async def dispose(self) -> None:
"""Called on application shutdown."""
await self.pool.close()
print("Database pool closed")
def query(self, sql: str) -> list:
# Use self.pool for queries
...
Lifecycle timeline:
configure_dioxide()is called during Django startupContainer scans packages and starts (runs
initialize()on@lifecyclecomponents)Each request creates a
ScopedContainervia middlewareRequest ends: scope is disposed (cleans up REQUEST-scoped
@lifecyclecomponents)Application shutdown: container stops (runs
dispose()on SINGLETON@lifecyclecomponents)
Django REST Framework¶
dioxide integrates seamlessly with Django REST Framework. Use the same inject() function in any DRF view type.
Function-Based Views with @api_view¶
from rest_framework.decorators import api_view
from rest_framework.response import Response
from dioxide.django import inject
@api_view(["GET"])
def user_list(request):
service = inject(UserService)
users = service.list_all()
return Response(users)
@api_view(["GET", "POST"])
def user_detail(request, pk):
service = inject(UserService)
if request.method == "GET":
user = service.get_user(pk)
return Response(user)
elif request.method == "POST":
user = service.create_user(request.data)
return Response(user, status=201)
Class-Based APIViews¶
from rest_framework.views import APIView
from rest_framework.response import Response
from dioxide.django import inject
class UserListView(APIView):
def get(self, request):
service = inject(UserService)
users = service.list_all()
return Response(users)
def post(self, request):
service = inject(UserService)
user = service.create_user(request.data)
return Response(user, status=201)
class UserDetailView(APIView):
def get(self, request, pk):
service = inject(UserService)
user = service.get_user(pk)
return Response(user)
def put(self, request, pk):
service = inject(UserService)
user = service.update_user(pk, request.data)
return Response(user)
def delete(self, request, pk):
service = inject(UserService)
service.delete_user(pk)
return Response(status=204)
ViewSets¶
from rest_framework import viewsets
from rest_framework.response import Response
from dioxide.django import inject
class UserViewSet(viewsets.ViewSet):
def list(self, request):
service = inject(UserService)
users = service.list_all()
return Response(users)
def create(self, request):
service = inject(UserService)
user = service.create_user(request.data)
return Response(user, status=201)
def retrieve(self, request, pk=None):
service = inject(UserService)
user = service.get_user(pk)
return Response(user)
def update(self, request, pk=None):
service = inject(UserService)
user = service.update_user(pk, request.data)
return Response(user)
def destroy(self, request, pk=None):
service = inject(UserService)
service.delete_user(pk)
return Response(status=204)
Generic Views and Mixins¶
For generic views that require more customization:
from rest_framework import generics
from rest_framework.response import Response
from dioxide.django import inject
class UserListCreateView(generics.ListCreateAPIView):
def get_queryset(self):
service = inject(UserService)
return service.list_all()
def perform_create(self, serializer):
service = inject(UserService)
service.create_user(serializer.validated_data)
Complete Example¶
Here’s a complete example showing a Django application with dioxide integration:
Project Structure¶
myproject/
├── myproject/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── myapp/
│ ├── __init__.py
│ ├── apps.py
│ ├── ports.py
│ ├── adapters.py
│ ├── services.py
│ ├── views.py
│ └── urls.py
└── manage.py
ports.py - Define Interfaces¶
from typing import Protocol
class UserRepository(Protocol):
"""Port for user data access."""
def get_all(self) -> list[dict]:
...
def get_by_id(self, user_id: str) -> dict | None:
...
def save(self, user: dict) -> dict:
...
class EmailPort(Protocol):
"""Port for sending emails."""
def send(self, to: str, subject: str, body: str) -> None:
...
adapters.py - Implement Interfaces¶
from dioxide import adapter, Profile
from .ports import UserRepository, EmailPort
@adapter.for_(UserRepository, profile=Profile.PRODUCTION)
class DjangoUserRepository:
"""Production repository using Django ORM."""
def get_all(self) -> list[dict]:
from django.contrib.auth.models import User
return list(User.objects.values("id", "username", "email"))
def get_by_id(self, user_id: str) -> dict | None:
from django.contrib.auth.models import User
try:
user = User.objects.get(id=user_id)
return {"id": user.id, "username": user.username, "email": user.email}
except User.DoesNotExist:
return None
def save(self, user: dict) -> dict:
from django.contrib.auth.models import User
db_user = User.objects.create_user(**user)
return {"id": db_user.id, "username": db_user.username, "email": db_user.email}
@adapter.for_(UserRepository, profile=Profile.TEST)
class FakeUserRepository:
"""In-memory repository for testing."""
def __init__(self):
self.users: dict[str, dict] = {}
def get_all(self) -> list[dict]:
return list(self.users.values())
def get_by_id(self, user_id: str) -> dict | None:
return self.users.get(user_id)
def save(self, user: dict) -> dict:
import uuid
user_id = str(uuid.uuid4())
user["id"] = user_id
self.users[user_id] = user
return user
@adapter.for_(EmailPort, profile=Profile.PRODUCTION)
class SmtpEmailAdapter:
"""Production email adapter using SMTP."""
def send(self, to: str, subject: str, body: str) -> None:
from django.core.mail import send_mail
send_mail(subject, body, "noreply@example.com", [to])
@adapter.for_(EmailPort, profile=Profile.TEST)
class FakeEmailAdapter:
"""Fake email adapter for testing."""
def __init__(self):
self.sent_emails: list[dict] = []
def send(self, to: str, subject: str, body: str) -> None:
self.sent_emails.append({"to": to, "subject": subject, "body": body})
services.py - Business Logic¶
from dioxide import service
from .ports import UserRepository, EmailPort
@service
class UserService:
"""User management service."""
def __init__(self, repo: UserRepository, email: EmailPort):
self.repo = repo
self.email = email
def list_all(self) -> list[dict]:
return self.repo.get_all()
def get_user(self, user_id: str) -> dict | None:
return self.repo.get_by_id(user_id)
def create_user(self, user_data: dict) -> dict:
user = self.repo.save(user_data)
self.email.send(
to=user["email"],
subject="Welcome!",
body=f"Hello {user['username']}, welcome to our platform!",
)
return user
apps.py - Configure dioxide¶
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "myapp"
def ready(self):
from dioxide import Profile
from dioxide.django import configure_dioxide
configure_dioxide(profile=Profile.PRODUCTION, packages=["myapp"])
views.py - Use inject()¶
from django.http import JsonResponse
from rest_framework.decorators import api_view
from rest_framework.response import Response
from dioxide.django import inject
from .services import UserService
# Django function-based view
def user_list_django(request):
service = inject(UserService)
users = service.list_all()
return JsonResponse({"users": users})
# DRF function-based view
@api_view(["GET", "POST"])
def user_list_drf(request):
service = inject(UserService)
if request.method == "GET":
users = service.list_all()
return Response(users)
elif request.method == "POST":
user = service.create_user(request.data)
return Response(user, status=201)
settings.py - Add Middleware¶
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"dioxide.django.DioxideMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
Testing¶
Testing Django views with dioxide is straightforward using the test profile:
# tests/test_views.py
import pytest
from django.test import TestCase, Client
from dioxide import Container, Profile
from dioxide.django import configure_dioxide
class UserViewTests(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Configure with TEST profile for fakes
configure_dioxide(profile=Profile.TEST, packages=["myapp"])
def test_user_list_returns_empty_initially(self):
client = Client()
response = client.get("/api/users/")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), [])
def test_create_user_sends_welcome_email(self):
from myapp.adapters import FakeEmailAdapter
from myapp.ports import EmailPort
from dioxide import container
# Get the fake email adapter
email = container.resolve(EmailPort)
client = Client()
response = client.post(
"/api/users/",
data={"username": "alice", "email": "alice@example.com"},
content_type="application/json",
)
self.assertEqual(response.status_code, 201)
# Verify email was "sent"
self.assertEqual(len(email.sent_emails), 1)
self.assertEqual(email.sent_emails[0]["to"], "alice@example.com")
Troubleshooting¶
“dioxide container not configured”¶
This error occurs when inject() is called but configure_dioxide() was never run.
Solution: Ensure configure_dioxide() is called during Django startup, either in settings.py or in your app’s apps.py ready() method.
“inject() called outside of request context”¶
This error occurs when inject() is called outside of an HTTP request (e.g., in a management command or Celery task).
Solution: For non-request contexts, use the container directly:
from dioxide import container
# In a management command
def handle(self, *args, **options):
service = container.resolve(UserService)
service.do_something()
Middleware Order Issues¶
If dependencies are not being injected correctly, ensure DioxideMiddleware is placed correctly in your middleware stack. It should be:
After
SessionMiddlewareandAuthenticationMiddleware(if your services need access to user/session)Before any middleware that might use dioxide services
See Also¶
Scoping Guide - Detailed guide on request scoping
Testing Guide - Testing patterns with fakes
FastAPI Integration - Similar integration for FastAPI