Django Integration¶
Wireup provides seamless integration with Django through the wireup.integration.django module, enabling dependency
injection in Django applications.
-
Automatic Dependency Management
Inject dependencies in routes and automatically manage container lifecycle.
-
Request Objects
Use Django request in Wireup dependencies.
-
Django Settings
The integration exposes Django settings to Wireup as config.
-
Shared business logic
Wireup is framework-agnostic. Share the service layer between web applications and other interfaces, such as a CLI.
Initialize the integration¶
Add the following to Django settings:
import os
from wireup.integration.django import WireupSettings
INSTALLED_APPS = [
# ...existing code...
"wireup.integration.django"
]
MIDDLEWARE = [
"wireup.integration.django.wireup_middleware",
# ...existing code...
]
WIREUP = WireupSettings(
injectables=["mysite.polls.services"] # Injectable modules here
)
# Additional application settings
S3_BUCKET_TOKEN = os.environ["S3_BUCKET_ACCESS_TOKEN"]
Inject Django settings¶
Django settings can be injected into injectables:
from wireup import injectable, Inject
from typing import Annotated
@injectable
class S3Manager:
def __init__(
self,
# Reference configuration by name
token: Annotated[str, Inject(config="S3_BUCKET_TOKEN")],
) -> None: ...
def upload(self, file: File) -> None: ...
You can also use Django settings in factories:
from wireup import injectable
from django.conf import settings
class GithubClient:
def __init__(self, api_key: str) -> None: ...
@injectable
def github_client_factory() -> GithubClient:
return GithubClient(api_key=settings.GH_API_KEY)
Inject the current request¶
The integration exposes the current Django request as a scoped lifetime dependency, which can be injected into
scoped or transient injectables:
from django.http import HttpRequest
from wireup import injectable
@injectable(lifetime="scoped")
class AuthService:
def __init__(self, request: HttpRequest) -> None:
self.request = request
Inject dependencies in views¶
To inject dependencies in views, request them by their type:
from django.http import HttpRequest, HttpResponse
from mysite.polls.services import S3Manager
from wireup import Injected
def upload_file_view(
request: HttpRequest, s3_manager: Injected[S3Manager]
) -> HttpResponse:
return HttpResponse(...)
from django.http import HttpRequest, HttpResponse
from mysite.polls.services import S3Manager
from wireup import Injected
async def upload_file_view(
request: HttpRequest, s3_manager: Injected[S3Manager]
) -> HttpResponse:
return HttpResponse(...)
from django.http import HttpRequest, HttpResponse
from django.views import View
from mysite.polls.services import S3Manager
from wireup import Injected
class UploadFileView(View):
def __init__(self, s3_manager: Injected[S3Manager]) -> None:
self.s3_manager = s3_manager
def post(self, request: HttpRequest) -> HttpResponse:
return HttpResponse(...)
For more examples, see the Wireup Django integration tests.
Forms and Model Methods¶
Use the @inject decorator to inject dependencies into Django Forms, Model methods, or any other function or method
during a request.
from django import forms
from wireup import Injected
from wireup.integration.django import inject
class UserRegistrationForm(forms.Form):
username = forms.CharField()
@inject
def __init__(self, *args, user_service: Injected[UserService], **kwargs):
super().__init__(*args, **kwargs)
self.user_service = user_service
def clean_username(self):
username = self.cleaned_data["username"]
if self.user_service.is_taken(username):
raise forms.ValidationError("Username taken")
return username
Third-party Django frameworks¶
If your project uses third-party packages to create views, such as
Django REST framework or Django Ninja, you must
use the @inject decorator explicitly.
This approach should work for any Django-based framework as long as it relies on Django's AppConfig and middleware
mechanisms.
from rest_framework.decorators import api_view
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet
from wireup import Injected
from wireup.integration.django import inject
from mysite.polls.services import S3Manager
@api_view(("GET",))
@inject
def drf_function_based_view(
request: Request, s3_manager: Injected[S3Manager]
) -> Response:
# Use the injected S3Manager instance
return Response(...)
class DRFClassBasedView(APIView):
@inject
def get(
self, request: Request, s3_manager: Injected[S3Manager]
) -> Response:
# Use the injected S3Manager instance
return Response(...)
class DRFViewSet(ViewSet):
@inject
def list(
self, request: Request, s3_manager: Injected[S3Manager]
) -> Response:
# Use the injected S3Manager instance
return Response(...)
from ninja import Router, Schema
from wireup import Injected
from wireup.integration.django import inject
from mysite.polls.services import S3Manager
router = Router()
class ItemSchema(Schema):
name: str
price: float
@router.get("/items")
@inject
def list_items(request, s3_manager: Injected[S3Manager]):
# Use the injected S3Manager instance
return {"items": [...]}
@router.post("/items")
@inject
def create_item(request, data: ItemSchema, s3_manager: Injected[S3Manager]):
# Both request body and injected service work together
return {"name": data.name, "price": data.price}
Best practice for mixing core and non-core Django views
If your project shares core and non core-django views, consider disabling auto-injection and using @inject explicitly
across all your views for consistency:
WIREUP = WireupSettings(
injectables=["mysite.polls.services"],
auto_inject_views=False, # Disable auto-injection
)
# Consistent approach: use @inject everywhere
@inject
def core_django_view(
request: HttpRequest, service: Injected[MyService]
) -> HttpResponse:
return HttpResponse(...)
@api_view(("GET",))
@inject
def drf_view(request: Request, service: Injected[MyService]) -> Response:
return Response(...)
# Inconsistent: mixing auto-injection and @inject
def core_django_view(
request: HttpRequest,
service: Injected[MyService], # Auto-injected
) -> HttpResponse:
return HttpResponse(...)
@api_view(("GET",))
@inject # Explicit injection
def drf_view(request: Request, service: Injected[MyService]) -> Response:
return Response(...)
Accessing the container¶
To access the Wireup container directly, use the following functions:
from wireup.integration.django import get_app_container, get_request_container
# Get application-wide container
app_container = get_app_container()
# Get request-scoped container
request_container = get_request_container()
Testing¶
For general testing tips with Wireup refer to the test docs. With Django you can override dependencies in the container as follows:
from wireup.integration.django import get_app_container
def test_override():
class DummyGreeter(GreeterService):
def greet(self, name: str):
return f"Hi, {name}"
with get_app_container().override.injectable(
GreeterService,
new=DummyGreeter(),
):
res = self.client.get("/greet?name=Test")
assert res.status_code == 200
Testing async views
When testing async views, use Django's AsyncClient instead of the regular Client:
from django.test import AsyncClient
import pytest
@pytest.fixture
def async_client():
return AsyncClient()
async def test_async_view(async_client):
response = await async_client.get("/async-endpoint/")
assert response.status_code == 200
Closing the Container¶
Django doesn't have built-in lifecycle events like FastAPI. If you use generator factories that
require cleanup, register a shutdown handler using Python's atexit module:
import atexit
from wireup.integration.django import get_app_container
def close_container():
get_app_container().close()
atexit.register(close_container)