dj-services
Services + svcs Dependency Injection
This project separates Django's framework concerns from business logic using a plain service layer, wired with the svcs service locator. The result:
- Views are one-liners. They pull a wired service and call a method.
- Services contain the business logic. They take repositories (and other services) via
__init__, call methods on them, and return DTOs. - Services never import Django ORM or models. Every test can run without a database.
- One registry, one
get[T]()helper. The same call works in views, tasks, commands, anywhere.
Why svcs Instead of Module-Level Singletons or a Custom Container
svcsis a tiny, typed, well-maintained service locator — no metaclasses, no decorators, no framework coupling.- Factories are lazy: a service is constructed only when something asks for it.
- Generic
get[T](type[T]) -> Tpreserves types through IDE/type-checker inference. - Swapping an implementation in tests is a one-line factory override.
- No import-order gymnastics: the registry is populated once at startup and then used by name.
The Registry — src/project/services.py
More from dvf/opinionated-django
services
Structure Django business logic as plain services that receive their dependencies via constructor injection, and wire them through an svcs registry so they can be resolved anywhere — views, Celery tasks, management commands, tests. Use when adding a new service, refactoring fat views or model methods into a service, wiring a service into the registry, or explaining where business logic should live in this project.
11dj-lint
Run linting, formatting, and static type checks on a Django project using ruff and pyrefly, and fix any issues found. Use after making code changes, before committing, or whenever the user asks to lint, format, or type-check the codebase.
11architecture
Implement a Django feature following the opinionated architecture — prefixed ULID IDs, repository pattern, Pydantic DTOs, svcs service locator, project-scoped django-ninja API, Celery reliable signals, and layered tests. Use when the user asks to add a new entity, endpoint, app, or business logic in a Django project that follows these conventions.
11dj-pytest
Set up and write pytest tests for an op-django project — pytest-django configuration, Celery eager mode for reliable-signal tests, freezegun for time-sensitive logic, shared conftest fixtures for DTOs and svcs overrides, and the three-layer test convention (repository against a real DB, service against mocked repos, API through HTTP). Use when adding tests to a new project, writing tests for a new feature, setting up test infrastructure, or explaining how tests should be organized.
11lint
Run linting, formatting, and static type checks on a Django project using ruff and pyrefly, and fix any issues found. Use after making code changes, before committing, or whenever the user asks to lint, format, or type-check the codebase.
10pytest
Set up and write pytest tests for an op-django project — pytest-django configuration, Celery eager mode for reliable-signal tests, freezegun for time-sensitive logic, shared conftest fixtures for DTOs and svcs overrides, and the three-layer test convention (repository against a real DB, service against mocked repos, API through HTTP). Use when adding tests to a new project, writing tests for a new feature, setting up test infrastructure, or explaining how tests should be organized.
10