Django Extension
Quick Start
Install anydi with Django support:
pip install 'anydi-django'
Add anydi_django to the bottom of your INSTALLED_APPS setting in your Django settings file:
INSTALLED_APPS = [
# ...
"anydi_django",
]
Add AnyDI settings to your Django settings file:
ANYDI = {
"INJECT_URLCONF": "path.to.your.urls",
}
This configuration injects dependencies into your Django views in the specified URLconf.
Let's say you have a service class you want to inject into your views:
class HelloService:
def get_message(self) -> str:
return "Hello, World!"
You can now use this service in your views as follows:
from anydi import Provide
from django.http import HttpRequest, HttpResponse
def hello(request, hello_service: Provide[HelloService]) -> HttpResponse:
return HttpResponse(hello_service.get_message())
In your urls.py, set up the routing:
from django.urls import path
from .views import hello
urlpatterns = [
path("hello", hello),
]
The HelloService is injected automatically into the hello view with the Provide[HelloService] marker.
Settings
ANYDI supports the following settings:
CONTAINER_FACTORY: str | None(default:None) - Factory function to create the container. If not provided, uses default container factory.REGISTER_SETTINGS: bool(default:False) - IfTrue, registers Django settings in the container.REGISTER_COMPONENTS: bool(default:False) - IfTrue, registers Django components like database and cache.INJECT_URLCONF: str | Sequence[str] | None(default:None) - URL configuration where dependencies should be injected.MODULES: Sequence[str](default:[]) - Modules to scan for dependencies.SCAN_PACKAGES: Sequence[str](default:[]) - Packages to scan for dependencies.PATCH_NINJA: bool(default:False) - IfTrue, modifies theninjaframework to support dependency injection.
Modules
To add modules to the container, you can use the MODULES setting:
ANYDI = {
"MODULES": [
"yourproject.user.UserModule",
"yourproject.payment.PaymentModule",
],
}
Assume you have a module that provides a UserService class:
from anydi import provider
from yourproject.user.service import UserService
class UserModule:
@provider(scope="singleton")
def user_service(self) -> UserService:
return UserService()
You can now use the UserService in your views as demonstrated in the Quick Start section.
Custom Container
To use a custom container, you can specify the CONTAINER_FACTORY setting:
ANYDI = {
"CONTAINER_FACTORY": "yourproject.config.container.get_container",
}
In yourproject/config/container.py:
from anydi import Container
def get_container() -> Container:
container = Container()
# Add custom container configuration here
return container
Request Scope
To use request-scoped dependencies in your Django application, add the request_scoped_middleware. This middleware creates request-specific dependency instances at the start of each request. They stay available for the request duration.
Add the middleware to your settings.py:
MIDDLEWARE = [
"anydi_django.middleware.request_scoped_middleware",
...
]
With this setup, you can use request-scoped dependencies in your application. HttpRequest is automatically available in request-scoped providers, so you can access the request object and its data.
Testing
When you write tests for your Django application, you may need to mock or override services. AnyDI has two ways to mock dependencies in tests.
Using container.override
You can use container.override as a context manager to replace a service temporarily with a mock. This is useful for functional tests where you want to override dependencies for specific tests.
from unittest import mock
from anydi_django import container
from django.test import Client
from your_module import HelloService
def test_hello_view() -> None:
# Create a mock of the HelloService
hello_service_mock = mock.MagicMock(spec=HelloService)
hello_service_mock.get_message.return_value = "Hello from mock!"
# Override the service in the container
with container.override(HelloService, instance=hello_service_mock):
client: Client = Client()
response = client.get("/hello")
assert response.status_code == 200
assert b"Hello from mock!" in response.content
hello_service_mock.get_message.assert_called_once()
Using the container fixture with pytest
If you use pytest, you can use the container fixture from the anydi pytest plugin. This is more convenient for test isolation and lets you override dependencies for the entire test function or test class.
First, install pytest-django:
pip install pytest-django
Then ensure the anydi pytest plugin is enabled (it's automatically enabled if anydi is installed).
from typing import Iterator
from unittest import mock
import pytest
from django.test import Client
from anydi import Container
from your_module import HelloService
@pytest.fixture
def hello_service_mock() -> mock.MagicMock:
"""Fixture that provides a mocked HelloService."""
return mock.MagicMock(spec=HelloService, get_message=mock.Mock(return_value="Hello from pytest mock!"))
def test_hello_view_with_fixture(container: Container, hello_service_mock: mock.MagicMock) -> None:
# Override the service using the container fixture
with container.override(HelloService, instance=hello_service_mock):
client: Client = Client()
response = client.get("/hello")
assert response.status_code == 200
assert b"Hello from pytest mock!" in response.content
hello_service_mock.get_message.assert_called_once()
Alternatively, you can use pytest's autouse fixture to automatically override services for all tests in a module or class:
from typing import Iterator
from unittest import mock
import pytest
from django.test import Client
from anydi import Container
from your_module import HelloService
@pytest.fixture(autouse=True)
def override_hello_service(container: Container) -> Iterator[mock.MagicMock]:
"""Automatically override HelloService for all tests."""
hello_service_mock = mock.MagicMock(spec=HelloService)
hello_service_mock.get_message.return_value = "Mocked message"
with container.override(HelloService, instance=hello_service_mock):
yield hello_service_mock
def test_hello_view(client: Client, hello_service_mock: mock.MagicMock) -> None:
response = client.get("/hello")
assert response.status_code == 200
assert b"Mocked message" in response.content
hello_service_mock.get_message.assert_called_once()
Both ways let you mock dependencies flexibly in your tests, so you can test your Django views and business logic separately.
Django Ninja
Install anydi with Django Ninja support:
pip install 'anydi-django[ninja]'
If you use the Django Ninja framework, you can enable dependency injection by setting PATCH_NINJA to True.
ANYDI = {
"PATCH_NINJA": True,
}
This setting modifies the Django Ninja framework to support dependency injection.
from typing import Any
from anydi import Provide
from django.http import HttpRequest
from ninja import Router
from your_module import HelloService
router = Router()
@router.get("/hello")
def hello(request: HttpRequest, hello_service: Provide[HelloService]) -> dict[str, Any]:
return {
"message": hello_service.get_message(),
}
The HelloService is injected automatically into the hello endpoint with the Provide[HelloService] marker.
Testing Django Ninja endpoints
Testing Django Ninja endpoints works the same way as testing regular Django views. You can use the same testing approaches described in the Testing section above with container.override or the pytest container fixture.