django-modern-rest

django-modern-rest is built on top of Django’s regular request lifecycle, so the diwire integration is the same one you use for plain Django views:

  • diwire.integrations.django.RequestContextMiddleware stores the current django.http.HttpRequest in a contextvars.ContextVar for the duration of a request.

  • diwire.integrations.django.add_request_context() registers HttpRequest in your diwire.Container, so controller methods and request-scoped services can depend on it.

The package is installed from PyPI as django-modern-rest, but current releases expose their runtime API from the dmr module.

Minimal setup

# settings.py
MIDDLEWARE = [
    # ...
    "diwire.integrations.django.RequestContextMiddleware",
    # ...
]
import django

from django.apps import apps
from django.conf import settings
from django.http import HttpRequest
from django.urls import path
from pydantic import BaseModel

from dmr import Controller
from dmr.plugins.pydantic import PydanticSerializer

from diwire import Container, Injected, Lifetime, Scope, resolver_context
from diwire.integrations.django import add_request_context

if not settings.configured:
    settings.configure(
        ALLOWED_HOSTS=["*"],
        INSTALLED_APPS=[],
        SECRET_KEY="docs-example-secret",
    )

if not apps.ready:
    django.setup()

container = Container()
add_request_context(container)


class PathResponse(BaseModel):
    direct_path: str
    service_path: str


class RequestPathService:
    def __init__(self, request: HttpRequest) -> None:
        self._request = request

    def path(self) -> str:
        return self._request.path


container.add(
    RequestPathService,
    scope=Scope.REQUEST,
    lifetime=Lifetime.SCOPED,
)


class PathController(Controller[PydanticSerializer]):
    @resolver_context.inject(scope=Scope.REQUEST)
    def get(
        self,
        request: Injected[HttpRequest],
        service: Injected[RequestPathService],
    ) -> PathResponse:
        return PathResponse(
            direct_path=request.path,
            service_path=service.path(),
        )


urlpatterns = [
    path("api/path/", PathController.as_view()),
]

Inject HttpRequest in controllers and services

django-modern-rest controllers are regular Django View subclasses, so diwire works well on controller methods decorated with @resolver_context.inject(scope=Scope.REQUEST).

This gives you the same benefits as plain Django integration:

  • inject the active request directly into controller methods

  • inject the same request into request-scoped services

Response handling

django-modern-rest validates and serializes controller responses. Prefer returning typed data or using controller helpers like self.to_response(...) / self.to_error(...) instead of constructing raw Django HttpResponse objects yourself.

Testing

For in-process tests, you can use dmr.test.DMRClient (or Django’s normal test client) and keep the same diwire setup: install RequestContextMiddleware, call add_request_context(container), and route your controller with .as_view().

from django.test import override_settings

from dmr.test import DMRClient

with override_settings(
    ROOT_URLCONF=__name__,
    MIDDLEWARE=["diwire.integrations.django.RequestContextMiddleware"],
):
    response = DMRClient().get("/api/path/")
    assert response.status_code == 200
    assert response.json() == {
        "direct_path": "/api/path/",
        "service_path": "/api/path/",
    }

How it works

  1. Django receives a request and executes RequestContextMiddleware.

  2. The middleware stores the active HttpRequest in a ContextVar.

  3. django-modern-rest instantiates the controller and dispatches the matching endpoint.

  4. @resolver_context.inject(scope=Scope.REQUEST) opens a request scope, resolves Injected[...] parameters, and calls the controller method.

  5. After the controller method returns, the injected wrapper ends request-scoped resolution and the middleware resets request context.