Skip to main content

Next-generation Django toolkit: typed ORM, unified validation, async-first workflows, and smart caching

Project description

Django Nova Logo

Django Nova

Typed, unified, and async-first toolkit for Django 5+

Python 3.12+ Django 5.0+ PyPI version Ruff License: MIT

Eliminate architectural fragmentation. One schema. One truth. Zero duplication.


English

Table of Contents


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

  1. 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.

  2. 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.

  3. 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.

  4. Zero-downtime is the default assumption
    Operations on large tables must use native PostgreSQL CONCURRENTLY semantics. Scheduled maintenance windows are an anti-pattern.

  5. Type safety is not optional
    Full pyright --strict compatibility 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 Genericsclass Cache[T]: syntax for modern type inference.
  • PEP 562 Lazy Imports — Safe import paths that bypass AppRegistryNotReady during 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 GrantSchema for create() and update().
  • Returns 400 Bad Request with 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 GrantSchema for 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+
  • CONCURRENTLY cannot 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


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_nova-0.2.4.tar.gz (23.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_nova-0.2.4-py3-none-any.whl (35.0 kB view details)

Uploaded Python 3

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

Hashes for django_nova-0.2.4.tar.gz
Algorithm Hash digest
SHA256 88b97341dc4cd5727b227cef6bd2ed39d123b00f6783f9b1e038eccdcc977c04
MD5 805af80ea877a1e968fa390c686e2711
BLAKE2b-256 26a9e29e90281bead475901fb065dbacf2e1e81148b1a5d3af92f2bafe35e9cf

See more details on using hashes here.

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

Hashes for django_nova-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 3e7867b51e8846c65ed3c0f85f66bbeb5f0c007df2a8536e6627757d8515ed2f
MD5 63916c404113fff30f5f35203fa8136d
BLAKE2b-256 180ad0a7d1f188b911714967b53b37213ab73e88c7d5729aa690ea5946a32f6c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page