Skip to main content
Django Nova logo

Django Nova

Typed Django models. Shared Pydantic schemas. Explicit validation boundaries.

Django Nova is a typed, async-oriented toolkit for Django 5. It connects Django models with Pydantic schemas, schema-driven query planning, caching, background tasks, and optional OpenTelemetry tracing.

PyPI · Documentation · Project site · Coverage report · Roadmap

Coverage

Development status: Beta. This README describes the current development workflow and the reviewed source implementation. Recent source changes may not yet be included in the published PyPI package. Check the changelog and the version you install before adopting an API.

Philosophy

Nova keeps Django's ORM and database semantics while making the boundaries around validation, serialization, and typing explicit.

  • Shared schemas: use Pydantic to express data contracts and reusable rules. Django fields, Model.clean(), uniqueness checks, and database constraints retain their own responsibilities.
  • Typed boundaries: contain dynamic Django metadata behind focused helpers. Static analysis complements runtime validation; it cannot replace it.
  • Schema-selected serialization: serialize the fields required by the selected schema rather than exposing every model attribute.
  • Explicit async behavior: support asynchronous workflows without implying that every operation or relation lookup is non-blocking.
  • Reproducible evidence: report tests, coverage, and benchmarks as measurements, not as automatic proof of production readiness.

Requirements

The current package manifest declares:

Dependency Requirement
Python 3.12 or newer
Django >=5.0,<6.0
Pydantic >=2.8,<3.0

These are dependency bounds, not a claim that every version combination has been tested. Django 6 is outside the declared range.

Installation

For an existing project managed with uv:

uv add django-nova

Or with pip inside an activated virtual environment:

python -m pip install django-nova

Optional integrations are installed explicitly:

uv add 'django-nova[redis]'
uv add 'django-nova[drf]'
uv add 'django-nova[fastapi]'
uv add 'django-nova[graphql]'
uv add 'django-nova[tracing]'
uv add 'django-nova[observability]'

Choose the extras your application needs. tracing supplies the OpenTelemetry API; observability additionally supplies SDK/exporter dependencies. Installing those dependencies does not configure a telemetry exporter. The manifest also provides cache, tasks, and async extras; installing an extra alone does not activate an integration or turn synchronous ORM calls into async operations.

Add Nova and your application to Django settings:

# config/settings.py — extend your existing INSTALLED_APPS.
INSTALLED_APPS = [
    "django.contrib.contenttypes",
    "nova",
    "articles",
]

Keep the other Django applications required by your project. Import model classes only after Django settings and the app registry are initialized.

Quick start

The examples below assume an existing Django project with an articles app. Create it if needed:

uv run python manage.py startapp articles

1. Define a schema and a model

# articles/models.py
from django.db import models
from pydantic import BaseModel, Field

from nova import NovaConfig, NovaModel


class ArticleSchema(BaseModel):
    title: str = Field(min_length=5, max_length=200)
    body: str
    views: int = Field(default=0, ge=0)


class Article(NovaModel):
    title = models.CharField(max_length=200)
    body = models.TextField()
    views = models.IntegerField(default=0)

    _nova_config = NovaConfig(
        pydantic_schema=ArticleSchema,
        strict_validation=True,
        cache_enabled=False,
    )

Django field declarations use assignment (=). Pydantic defines the application contract; Django fields define persistence and field-level rules. Some limits belong at both boundaries. An explicit schema does not rewrite the database model.

2. Create the database table

uv run python manage.py makemigrations articles
uv run python manage.py migrate
uv run python manage.py check
uv run python manage.py shell

3. Save and serialize

Run in the Django shell or an initialized application service:

from articles.models import Article

article = Article(
    title="Hello Nova",
    body="Shared validation with explicit boundaries.",
)
article.save()

payload = article.to_pydantic().model_dump()
assert payload == {
    "title": "Hello Nova",
    "body": "Shared validation with explicit boundaries.",
    "views": 0,
}
assert "id" not in payload  # The explicit schema does not declare it.

4. Handle validation errors

from articles.models import Article
from nova.core.exceptions import NovaValidationError

try:
    Article(title="Hi", body="Too short a title.").save()
except NovaValidationError as exc:
    print(f"Validation failed: {exc}")

For this example, the title fails Pydantic validation before persistence.

Validation contract

NovaModel.save() runs this pipeline before calling Django's save implementation:

Pydantic → Django field validation and conversion → Model.clean()
         → uniqueness checks → constraint checks → database save
  • strict_validation=True enables the Pydantic stage.
  • strict_validation=False skips that stage; Django validation still runs.
  • Successful field.clean() results are assigned back to model attributes before Model.clean() executes.
  • Full-model validation is retained when save(update_fields=...) is used.
  • QuerySet.update(), bulk_create(), and bulk_update() bypass model save(); they do not acquire its validation contract.
  • Database constraints remain necessary. Pre-save checks do not replace database enforcement under concurrent writes.

Automatic schema generation supports mapped Django types, inherited field types, nullable fields, and callable defaults. Decimal digit constraints are transferred to generated schemas. An unknown field falls back to Any, which does not provide specialized type validation.

FileField and ImageField values are serialized as names/paths rather than file contents. Their Django max_length is not transferred to the Pydantic value in the current compiler. Django field validation still applies on the save path.

Query planning and async ORM

For a Nova queryset, .auto() applies a plan derived from the model's schema:

from articles.models import Article

queryset = Article.objects.filter(title__icontains="Nova").auto()
articles = list(queryset)

The selected joins, prefetches, and deferred fields depend on the model/schema. Measure query counts for your actual workload; .auto() is not a universal performance guarantee.

For Nova's additional async queryset helpers, explicitly configure the manager:

from nova.async_orm import AsyncNovaManager

# Inside a NovaModel subclass:
# objects = AsyncNovaManager()

Django's async save API can be used from an async application function:

from articles.models import Article


async def create_article() -> Article:
    article = Article(title="Async Nova", body="Created asynchronously.")
    await article.asave()
    return article

An unloaded nested foreign key may require synchronous SQL during serialization. Do not assume that calling to_pydantic() directly in an async context is safe for every model. Nested M2M and reverse-relation serialization remain separate contracts; see the roadmap.

Background tasks

@nova_task() decorates an async function. Awaiting the decorated call submits work and returns a task ID; it does not await the task's business result. The decorator uses the shared engine returned by get_engine().

import asyncio

from nova import nova_task
from nova.tasks.engine import get_engine


async def main() -> None:
    engine = get_engine()
    completed = asyncio.Event()

    @nova_task(name="demo.greet")
    async def greet(person: str) -> None:
        print(f"Hello, {person}!")
        completed.set()

    await engine.start()
    try:
        task_id = await greet("Nova")
        print(f"Submitted task: {task_id}")
        await asyncio.wait_for(completed.wait(), timeout=10)
    finally:
        await engine.stop()


if __name__ == "__main__":
    asyncio.run(main())

This is an in-process example using the default backend. The event is demo-local synchronization, not a distributed completion mechanism. Start the shared engine in application startup and stop it during shutdown. Backend shutdown behavior, retries, durability, and process recovery must be evaluated for your deployment. Do not submit blocking CPU-intensive work directly to the event loop.

Tracing

Tracing is optional. With no usable tracer, nova_span() yields None. For async operations, keep the awaited work inside the span:

from nova.core.tracing import nova_span


async def fetch_and_measure(fetch):
    with nova_span("article.fetch", component="service"):
        return await fetch()

The current tracing decorators support both ordinary functions and async def:

from nova.core.tracing import trace_task


@trace_task(operation="calculate")
async def calculate_total(prices: list[int]) -> int:
    return sum(prices)

Nova guards its internal telemetry setup, recording, and teardown calls against ordinary exceptions while preserving business results, exceptions, and cancellation. Direct span method calls made by application code are outside that protection. Provider signals derived directly from BaseException are not suppressed. Configure the OpenTelemetry SDK and exporter separately if you want to collect spans.

Development and verification

To work on the repository rather than the published package:

git clone https://github.com/Artem7898/django-nova.git
cd django-nova
uv sync --locked --all-extras --dev

For an existing checkout containing local changes, run the sync command there; cloning the remote does not include unpublished local fixes. Commit and retain uv.lock for reproducible development environments.

Run local checks (SQLite by default):

uv run --locked pyright src/nova \
&& uv run --locked ruff check . \
&& uv run --locked ruff format --check . \
&& uv run --locked pytest -q -rs \
&& git diff --check

Tests requiring PostgreSQL, Redis, or Memcached need their explicit settings and service addresses. Follow the full integration and coverage commands before describing a run as complete. -rs shows the reasons for skipped cases.

Run the isolated example and build the documentation:

uv run --locked python -m examples.dogfooding \
&& uv run --locked pytest -q tests/docs/test_dogfooding_demo.py \
&& uv run --locked --with-requirements docs/requirements.txt mkdocs build --strict

The dogfooding demo applies migrations to its own in-memory database and checks validation, serialization, file handling, and task submission. Its models are included directly in the documentation from the executable source.

Pyright uses the repository configuration, including exclusions and scoped exceptions. Application typing also depends on its checker and Django stubs.

Coverage and generated status

After the full coverage run, update both generated files from that same coverage.xml:

uv run --locked python scripts/generate_status.py --write \
&& uv run --locked python scripts/generate_status.py --check \
&& uv run --locked python scripts/update_badge.py --write \
&& uv run --locked python scripts/update_badge.py --check

The README badge and STATUS.md use XML line coverage, which can differ from the terminal line-and-branch percentage. STATUS.md distinguishes missing measurements from measured zero coverage. Its checks compare supplied XML and source inventory; they do not prove source-content freshness. Regenerate coverage after source changes. Keep run-specific test counts in the verification record rather than treating them as a live property of this README.

Performance

Benchmark model construction and serialization separately:

uv run python scripts/bench.py

Treat this as a local diagnostic, not a universal speed claim. Review the current benchmark script before publishing results; the roadmap tracks improvements to its execution and measurement setup.

Django model construction, Pydantic validation, to_pydantic(), SQL execution, and cache access perform different work. Report Python and dependency versions, hardware, benchmark configuration, repetitions, variability, and raw results. Small differences within measurement noise do not establish that Nova is faster than Django or adds zero overhead.

Scope and limitations

Area Boundary to evaluate
Cache Backend behavior, invalidation coverage, consistency, and failure handling
Tasks Process-local behavior of the default backend; no implied durable queue guarantee
DRF, FastAPI, GraphQL Integration-specific contracts and dependency versions
TypedField Migration reconstruction and complete conversion/validator delegation
Migrations PostgreSQL operation and transaction requirements; no blanket lock-free guarantee
Validation Distinct Pydantic, Django, and database responsibilities

See ROADMAP.md for planned work and STATUS.md for coverage measurements. Neither assigns production readiness from a percentage.

Contributing

Include a minimal reproducer for defects and a regression test for behavior changes. Keep compatibility boundaries explicit, run the checks above, and update documentation when public behavior changes.

Author and references

Developed and maintained by Artem Alimpiev.

License

MIT, as declared in the package metadata.

Metadata

Release files for django-nova 0.6.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-nova 0.6.3
File Size Uploaded Provenance Actions
django_nova-0.6.3.tar.gz 69.5 kB With provenance Details for django_nova-0.6.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-nova 0.6.3
File Interpreter ABI Platform Actions
django_nova-0.6.3-py3-none-any.whl Python 3 none any Details for django_nova-0.6.3-py3-none-any.whl

Total release size: 174.8 kB

Release files / django_nova-0.6.3.tar.gz

Download URL django_nova-0.6.3.tar.gz
Size 69.5 kB
Tags Source
SHA-256 checksum
How to use checksums
81517bc0da31db5f55d007c7a8e8ada2709d96889edf643b905d13d0793f8ffa
BLAKE2b-256 checksum
How to use checksums
5a02bdfca5b5d32a86e791d0d7c5bcd96b5cdbfffe5052ac992a6d6dbdf16f2e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / django_nova-0.6.3-py3-none-any.whl

Download URL django_nova-0.6.3-py3-none-any.whl
Size 105.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d9b17c37bc1fa4f621613d3de80df50d3af87826f3542c3f0935869d479031a6
BLAKE2b-256 checksum
How to use checksums
69584169c78c1f6567d8980fac4b8beac47c2c7604aac21d10bdfc051be5f77a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Security for django-nova 0.6.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_nova-0.6.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page