Skip to content

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:

settings.py
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:

mysite/polls/services/s3_manager.py
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:

mysite/polls/services/github_client.py
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:

mysite/polls/services/auth_service.py
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:

app/views.py
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(...)
app/views.py
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(...)
app/views.py
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.

forms.py
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.

app/views.py
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(...)
app/views.py
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:

settings.py
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:

test_thing.py
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:

myapp/__init__.py
import atexit
from wireup.integration.django import get_app_container


def close_container():
    get_app_container().close()


atexit.register(close_container)

API Reference