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 · Live demo . Coverage report · Roadmap
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=Trueenables the Pydantic stage.strict_validation=Falseskips that stage; Django validation still runs.- Successful
field.clean()results are assigned back to model attributes beforeModel.clean()executes. - Full-model validation is retained when
save(update_fields=...)is used. QuerySet.update(),bulk_create(), andbulk_update()bypass modelsave(); 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.
- ORCID: 0009-0007-6740-7242
- Research references: 10.5281/zenodo.20057443, 10.5281/zenodo.20659647
License
MIT, as declared in the package metadata.
Metadata
Release files for django-nova 0.6.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_nova-0.6.4.tar.gz | 70.4 kB | With provenance | Details for django_nova-0.6.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_nova-0.6.4-py3-none-any.whl | Python 3 | none | any | Details for django_nova-0.6.4-py3-none-any.whl |
Total release size: 175.7 kB
Release files / django_nova-0.6.4.tar.gz
| Download URL | django_nova-0.6.4.tar.gz |
|---|---|
| Size | 70.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cb4b7bbc3b3d94aa1ed1a9857f87664acf777e1f652b91890a5518b72bcb64a9
|
|
BLAKE2b-256 checksum How to use checksums |
50e04ac5201cc38ea5b4975dacc2987b188c7888959a2e0618c960980a2bd534
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 29, 2026.
Transparency logRelease files / django_nova-0.6.4-py3-none-any.whl
| Download URL | django_nova-0.6.4-py3-none-any.whl |
|---|---|
| Size | 105.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c706ce14493062d6a2acf06725e9ef40af3963b21dc1cc24234e764b19d5dd41
|
|
BLAKE2b-256 checksum How to use checksums |
c2ee6ab5087fdb448f7f24b63e32a3e31aa2b419ce852ba380d85821891b0815
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 29, 2026.
Transparency logSecurity for django-nova 0.6.4
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.
Attestation for django_nova-0.6.4.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 Oct 29, 2026.
Transparency logA 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.