Skip to content

Django integration

The priority integration. If you have to pick one framework to try this library with, pick Django.

Installation

pip install "django-autowired[django,injector]"

Two ways to bootstrap

Project-wide auto-discovery (recommended for typical projects, especially those with many domain apps) — one INSTALLED_APPS entry plus a settings dict. Spring Boot–style. See Project-wide auto-discovery below.

Per-app AutowiredAppConfig (for apps that own their DI scanning, or for reusable apps published as packages). See Per-app config below.

You can use either independently — but not both at once in the same project (that would double-initialize the container).

Project-wide auto-discovery

Add the integration to INSTALLED_APPS and define a DJANGO_AUTOWIRED settings dict:

# settings.py
INSTALLED_APPS = [
    ...,
    "myproject.users",
    "myproject.billing",
    "myproject.notifications",
    "django_autowired.integrations.django",   # adds AutowiredDjangoConfig
]

DJANGO_AUTOWIRED = {
    "AUTODISCOVER_PREFIX": "myproject.",     # scan every shop.* app's adapters/
    "AUTODISCOVER_SUBPACKAGE": "adapters",   # optional: only scan <app>/adapters/
    "BACKEND": "injector",                   # default
    "EXTRA_MODULES": [                       # optional: dotted "module:attr" specs
        "myproject.di:database_module",
    ],
    "EXCLUDE": {"legacy"},                   # optional: extra exclude_patterns
    "STRICT": True,                          # optional: raise on broken imports
}

That's it. Adding a new domain app is just:

  1. Append it to INSTALLED_APPS.
  2. Decorate its services with @injectable (or @provides for conditional bindings).

No per-app DI config required.

Settings keys

Key Type Default Description
PACKAGES list[str] Explicit list of dotted package paths. Mutually exclusive with AUTODISCOVER_PREFIX.
AUTODISCOVER_PREFIX str Filter INSTALLED_APPS to entries whose package name starts with this prefix. Empty string scans every non-Django app.
AUTODISCOVER_SUBPACKAGE str \| None None Sub-path appended to each discovered app (e.g. "adapters"). Apps lacking the sub-path are silently skipped — useful for incremental adoption.
BACKEND str "injector" DI backend name.
EXTRA_MODULES list[str] [] Backend-specific modules as "module.path:attr" strings, resolved by import.
EXCLUDE set[str] set() Additional module-name segments to skip during scanning.
STRICT bool False If True, the scanner raises on broken sub-modules. Recommended for CI.

Spring Boot mental model

Spring Boot django-autowired
@SpringBootApplication INSTALLED_APPS += ["django_autowired.integrations.django"]
@ComponentScan(basePackages = "com.shop") DJANGO_AUTOWIRED["AUTODISCOVER_PREFIX"] = "shop."
@Service / @Repository / @Component @injectable
@Configuration + @Bean A module containing @provides factories
application.properties DJANGO_AUTOWIRED + Django settings

A complete worked example lives in examples/django_autodiscover_demo/.

Per-app config

1. Subclass AutowiredAppConfig:

# myapp/apps.py
from django_autowired.integrations.django import AutowiredAppConfig

class MyAppConfig(AutowiredAppConfig):
    name = "myapp"
    autowired_packages = ["myapp.services", "myapp.adapters"]
    autowired_backend = "injector"    # default

2. Wire it up in myapp/__init__.py:

default_app_config = "myapp.apps.MyAppConfig"

3. Add the app to INSTALLED_APPS:

# settings.py
INSTALLED_APPS = [
    ...,
    "myapp",
]

That's it. At Django startup, ready() triggers the scan and builds the container.

Structuring packages for scanning

myapp/
├── __init__.py
├── apps.py
├── services/
│   ├── __init__.py
│   ├── order_service.py        # @injectable()
│   └── email/
│       └── notifier.py         # @injectable()
├── adapters/
│   ├── __init__.py
│   └── out_/
│       ├── __init__.py
│       ├── sql_user_repo.py    # @injectable(bind_to=IUserRepository)
│       └── stripe_gateway.py   # @injectable(bind_to=IPaymentGateway)
├── ports/
│   ├── __init__.py
│   ├── user_repository.py      # IUserRepository(ABC)
│   └── payment_gateway.py      # IPaymentGateway(ABC)
├── migrations/                 # auto-skipped
└── tests/                      # auto-skipped

List each branch explicitly:

autowired_packages = ["myapp.services", "myapp.adapters"]

Django-specific bindings (extra_modules)

Wrap Django primitives in an injector.Module:

import injector
from django.conf import settings
from django.core.cache import cache
from django.db import connection

class DjangoModule(injector.Module):
    def configure(self, binder):
        binder.bind(type(cache), to=cache)
        binder.bind(type(connection), to=connection)
        # Config values
        binder.bind_scalar("secret_key", to=settings.SECRET_KEY)

class MyAppConfig(AutowiredAppConfig):
    name = "myapp"
    autowired_packages = ["myapp.services", "myapp.adapters"]
    autowired_extra_modules = [DjangoModule()]

Using injected services in views / tasks

# myapp/views.py
from django.http import JsonResponse
from django_autowired import container
from myapp.services.order_service import OrderService

def place_order(request, sku: str):
    svc = container.get(OrderService)
    order = svc.place(sku)
    return JsonResponse({"id": order.id})

Testing with Django's test runner

# myapp/tests/test_order_service.py
import pytest
from django_autowired import container
from myapp.services.order_service import OrderService

@pytest.mark.autowired_packages(["myapp.services", "myapp.adapters"])
@pytest.mark.autowired_backend("injector")
def test_order_service(autowired_container):
    svc = container.get(OrderService)
    assert svc is not None

Compatibility

Django version Status
5.1
5.0
4.2 LTS

FAQ

Why not use Django's built-in signals for this?

Signals are an event bus, not a dependency container. They don't solve constructor injection, interface binding, or scope management. This library composes cleanly with signals if you use both.

Does AutowiredAppConfig run on every request?

No. ready() runs once at process start, just like any other AppConfig.ready().

Can I use this with Django's built-in injector patterns (if I already have them)?

Yes. Put your existing Module subclasses into autowired_extra_modules and they'll be merged with the scanned registrations.