Next-generation Django toolkit: typed ORM, unified validation, async-first workflows, and smart caching
Project description
Django Nova
Typed, unified, and async-first toolkit for Django 5+
Eliminate architectural fragmentation. One schema. One truth. Zero duplication.
English
Table of Contents
- Problem Statement
- Philosophy
- Architecture
- Installation
- Quick Start
- DRF Integration
- FastAPI Integration
- Smart Cache
- Zero-Downtime Migrations
- Observability
- Roadmap
- Benchmarks
- Limitations
- License
Problem Statement
Django’s validation layer is fragmented by design:
- Forms validate user input in the presentation layer.
- DRF Serializers re-implement the same logic in the API layer.
- Model
clean()runs only inside the admin or when explicitly called. - Database constraints are limited to simple, DB-expressible rules and are not reusable outside the ORM.
The result is validation drift: business rules scattered across forms, serializers, models, and database DDL. Change a rule in one place, and the other three become liabilities. Worse, calling Model.objects.create() from a management command, a Celery task, or a data pipeline bypasses form and serializer validation entirely, leaving only brittle DB constraints as a safety net.
Django Nova solves this by moving the contract to the schema and enforcing it at the ORM level.
Philosophy
-
Schema is the Single Source of Truth
Business logic lives in Pydantic models. Django Models, DRF Serializers, FastAPI routers, and Forms are generated projections of that schema—not independent validators. -
Validation is an ORM concern, not a view concern
A model should refuse to persist invalid data regardless of whether the caller is a web view, an API endpoint, a CLI command, or a background worker. -
Infrastructure must be transparent
Cache invalidation, structured logging, and distributed tracing should require zero boilerplate. If you have to think about them, the abstraction has leaked. -
Zero-downtime is the default assumption
Operations on large tables must use native PostgreSQLCONCURRENTLYsemantics. Scheduled maintenance windows are an anti-pattern. -
Type safety is not optional
Fullpyright --strictcompatibility across ORM, QuerySets, and generated code. If it type-checks, it runs.
Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ Consumer Layers │
│ ┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Forms │ │ DRF Serial. │ │FastAPI Routers │ Management Commands │ |
│ └────┬─────┘ └──────┬───────┘ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │ │ │
│ └───────────────┴─────────────── ─┴────────────────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Pydantic Schema │ ← Single Source of Truth │
│ │ (Business) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ NovaModel │ ← ORM Enforcement Layer │
│ │ (Interceptor) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌───────────────────────────┼───────────────────────────┐ │
│ │ │ │ │
│ ┌────▼─────┐ ┌────────▼────────┐ ┌───────▼──────┐ │
│ │ Cache │ │ Signals │ │ Telemetry │ │
│ │ Invalid. │ │ (post_save etc) │ │ (OTel/Logs) │ │
│ └──────────┘ └─────────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Core Design Decisions:
- PEP 695 Generics —
class Cache[T]:syntax for modern type inference. - PEP 562 Lazy Imports — Safe import paths that bypass
AppRegistryNotReadyduring startup. - SQL Compiler Hook — Deterministic cache-key generation from query AST, immune to Django version changes.
- Signal-Driven Invalidation — O(1) cache eviction on write without manual TTL management.
Installation
Requires Python 3.12+ and Django 5.0+.
Using uv (recommended):
# Core library
uv add django-nova
# With Django REST Framework support
uv add django-nova[drf]
# With FastAPI support
uv add django-nova[fastapi]
# Full enterprise stack (tracing + structured logging)
uv add django-nova[tracing,observability]
Add to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"nova",
]
Quick Start
Define your business rules once in a Pydantic schema. Nova enforces them everywhere.
# models.py
from decimal import Decimal
from datetime import date
from pydantic import BaseModel, field_validator, model_validator
from django.db import models
from nova import NovaModel, NovaConfig
class GrantSchema(BaseModel):
"""Single Source of Truth for Grant business rules."""
title: str
budget: Decimal
start_date: date
end_date: date
pi_email: str # Principal Investigator
@field_validator("title")
@classmethod
def title_not_empty(cls, v: str) -> str:
v = v.strip()
if len(v) < 5:
raise ValueError("Title must be at least 5 characters")
return v
@model_validator(mode="after")
def validate_grant(self):
if self.end_date <= self.start_date:
raise ValueError("End date must be after start date")
duration_days = (self.end_date - self.start_date).days
if self.budget > Decimal("1_000_000") and duration_days < 365:
raise ValueError("Large grants require a minimum 1-year duration")
if self.budget < Decimal("1_000"):
raise ValueError("Minimum grant budget is $1,000")
return self
class Grant(NovaModel):
title = models.CharField(max_length=300)
budget = models.DecimalField(max_digits=14, decimal_places=2)
start_date = models.DateField()
end_date = models.DateField()
pi_email = models.EmailField()
_nova_config = NovaConfig(
pydantic_schema=GrantSchema,
cache_enabled=True,
strict_validation=True,
)
Now validation is enforced at the ORM level:
# This raises ValidationError immediately — no DB round-trip required
Grant.objects.create(
title="X",
budget=Decimal("500"),
start_date=date(2026, 1, 1),
end_date=date(2026, 12, 31),
pi_email="pi@example.com",
)
# ValueError: Title must be at least 5 characters
# ValueError: Minimum grant budget is $1,000
The same schema is automatically reused by DRF, FastAPI, and Forms. No duplication.
DRF Integration
Generate a DRF ModelSerializer that delegates all business logic to the Pydantic schema.
# serializers.py
from nova.ecosystem.drf import NovaSerializer
from .models import Grant
class GrantSerializer(NovaSerializer):
class Meta:
model = Grant
fields = "__all__"
NovaSerializer automatically:
- Maps Pydantic validators to DRF field errors.
- Reuses
GrantSchemaforcreate()andupdate(). - Returns
400 Bad Requestwith structured error messages on validation failure.
FastAPI Integration
Generate a fully documented FastAPI router with native OpenAPI/Swagger support from the same Django model.
# api.py
from fastapi import FastAPI
from nova.ecosystem.fastapi import NovaRouter
from .models import Grant
app = FastAPI(title="Grants API")
# Generates GET /grants, POST /grants, GET /grants/{id}, PATCH /grants/{id}, DELETE /grants/{id}
app.include_router(NovaRouter(Grant, prefix="/grants"))
The generated router:
- Uses
GrantSchemafor request/response validation. - Emits OpenAPI documentation automatically.
- Supports async endpoints when running under ASGI.
Smart Cache
Nova provides an automatic, signal-driven QuerySet cache with O(1) invalidation.
class Grant(NovaModel):
# ... fields ...
_nova_config = NovaConfig(
pydantic_schema=GrantSchema,
cache_enabled=True,
cache_ttl=300, # seconds
)
How it works:
# First call hits the database; result is cached
grants = Grant.objects.filter(budget__gte=100_000).nova_cache()
# Subsequent calls return cached result instantly
grants = Grant.objects.filter(budget__gte=100_000).nova_cache()
# On any Grant.save(), the cache key is evicted automatically
Grant.objects.create(...) # Cache invalidation fires via Django signals
- Deterministic keys — Generated from the SQL AST, safe across Django versions.
- No stale data — Write operations trigger signal-based eviction.
- Zero boilerplate — No manual cache key management.
Zero-Downtime Migrations
For tables with millions of rows, standard CREATE INDEX acquires an exclusive lock. Nova provides migration operations that use PostgreSQL CONCURRENTLY.
# migrations/0002_add_grant_indexes.py
from django.db import migrations
from nova.migrations import ConcurrentIndexOperation, ConcurrentAddField
class Migration(migrations.Migration):
dependencies = [
("research", "0001_initial"),
]
operations = [
# Add a non-nullable field without locking the table
ConcurrentAddField(
model_name="grant",
name="funding_program",
field=models.CharField(max_length=100, default="NSF"),
),
# Create a composite index without downtime
ConcurrentIndexOperation(
model_name="grant",
fields=["start_date", "end_date"],
name="grant_dates_idx",
),
]
Requirements:
- PostgreSQL 14+
CONCURRENTLYcannot run inside a transaction; Nova handles this automatically.
Observability
Nova ships with built-in, zero-config observability powered by structlog and OpenTelemetry.
# settings.py
NOVA_OBSERVABILITY = {
"structlog": True,
"opentelemetry": True,
"log_level": "INFO",
"json_format": True, # Machine-readable for Datadog / ELK / Loki
}
What you get automatically:
| Signal | Output |
|---|---|
Model.save() |
JSON log with model name, PK, duration, validation result |
| Cache hit/miss | Structured log with query hash and TTL |
| Cache invalidation | Trace span showing evicted keys |
| DB query | OpenTelemetry span with SQL fingerprint |
Example log output:
{
"timestamp": "2026-07-16T10:12:00Z",
"event": "model_save",
"model": "research.Grant",
"pk": 42,
"validation": "passed",
"duration_ms": 12.4,
"trace_id": "4f6d9c8f2a1b..."
}
If OpenTelemetry is not installed, spans are no-ops with zero runtime overhead.
Roadmap
✅ Already Shipped
| Feature | Status |
|---|---|
| Typed Manager | Done |
| Schema Registry | Done |
| Cache Backend Abstraction | Done |
| AppConfig Auto Discovery | Done |
| Typed Settings | Done |
🚧 Coming Next
| Feature | Status | Target |
|---|---|---|
| Redis Cache | Planned | Q3 2026 |
| Task Backend | Planned | Q3 2026 |
| Metrics | Planned | Q4 2026 |
| Instrumentation | Planned | Q4 2026 |
| Read Replicas | Planned | Q4 2026 |
| Query Planner | Planned | Q1 2027 |
| Prefetch Optimizer | Planned | Q1 2027 |
| Stable API | Planned | Q1 2027 |
| Django 6 Support | Planned | Q2 2027 |
| Full OpenTelemetry Support | Planned | Q2 2027 |
| Production Readiness | Planned | Q2 2027 |
Benchmarks
All benchmarks measure model initialization speed (object creation + validation) on Python 3.13, local SSD, warm CPU.
| Test | Avg Time | Ops / Second | Overhead |
|---|---|---|---|
| Pure Pydantic (Baseline) | 2.70 µs | 369.7K | 1.0× |
| NovaModel (Django + Pydantic) | 4.08 µs | 244.8K | 1.51× |
Note: Although the relative overhead is ~1.51×, the absolute penalty is only 1.38 microseconds per object. In the context of a real HTTP request—where network latency and database round-trips are measured in milliseconds—this overhead is microscopic and completely invisible to the end user. You gain full ORM-level type safety at the cost of a single microsecond.
Run benchmarks locally:
uv sync --extra dev
pytest benchmarks/ -v --benchmark-only
Limitations
- Django 5.0+ only. We do not backport to Django 4.x; the project relies on modern ORM hooks.
- PostgreSQL recommended. Zero-downtime migrations and advanced cache invalidation rely on PG-specific features. SQLite and MySQL work for core validation but with degraded migration and cache capabilities.
- Pydantic v2 only. Pydantic v1 is not supported.
- Partial async support. QuerySet cache invalidation is currently synchronous. Full async ORM support is on the roadmap for Django 5.2+.
- Single-database assumption. Multi-database routing with cache invalidation requires manual configuration.
License
MIT License. See LICENSE for details.
👤 Автор
Artem Alimpiev
- ORCID: 0009-0007-6740-7242
- DOI: 10.5281/zenodo.20057443
- DOI: 10.5281/zenodo.20659647
- PYPI: Django Nova
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_nova-0.2.4.tar.gz.
File metadata
- Download URL: django_nova-0.2.4.tar.gz
- Upload date:
- Size: 23.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88b97341dc4cd5727b227cef6bd2ed39d123b00f6783f9b1e038eccdcc977c04
|
|
| MD5 |
805af80ea877a1e968fa390c686e2711
|
|
| BLAKE2b-256 |
26a9e29e90281bead475901fb065dbacf2e1e81148b1a5d3af92f2bafe35e9cf
|
File details
Details for the file django_nova-0.2.4-py3-none-any.whl.
File metadata
- Download URL: django_nova-0.2.4-py3-none-any.whl
- Upload date:
- Size: 35.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e7867b51e8846c65ed3c0f85f66bbeb5f0c007df2a8536e6627757d8515ed2f
|
|
| MD5 |
63916c404113fff30f5f35203fa8136d
|
|
| BLAKE2b-256 |
180ad0a7d1f188b911714967b53b37213ab73e88c7d5729aa690ea5946a32f6c
|