Skip to main content

A Django-like ORM with synchronous and asynchronous support

Project description

djanorm

PyPI version Python versions License: MIT Docs Typing: Typed

A Django-inspired ORM for Python with full synchronous and asynchronous support. The same API you know from Django, without depending on the full framework.

Works with SQLite, PostgreSQL, CockroachDB (4.1+), MySQL / MariaDB, libsql / Turso and (4.0+) DuckDB for embedded analytics. Ships with migrations + linter, atomic transactions, signals, validation, relationship loading (select_related / prefetch_related / FilteredRelation), aggregations, DB functions, async-native ORM path, queryset & row caching, audit-trail tracking (@track_history), multi-tenancy (schema-level + row-level + PG Row-Level Security ops), recursive CTEs, full-text search, GIS, and Pydantic interop — all with real static typing (Field[T]).

Production primitives: query budget (HTTP SLA), circuit breaker, outbox pattern, hash sharding, idempotency keys, lag-aware read routing, async pool task affinity, online (zero-downtime) migrations, schema drift detection (dorm diff), PgBouncer transaction-pool compatibility (4.1+), PII field tagging (4.1+), framework-agnostic ASGI middleware + Litestar plugin (4.1+).

Sibling packages: pytest-djanorm (test fixtures) and djanorm-mypy (mypy plugin) ship in their own packages so the main wheel never pulls dev tooling.

Release notes for every version live in CHANGELOG.md. For the 4.3 highlights see docs/v4_3.md + the upgrading guide; the 4.2 highlights live in docs/v4_2.md; the 4.1 highlights live in docs/v4_1.md; the 4.0 highlights live in docs/v4_0.md; upgrading from 3.3 → 4.0 is documented in docs/upgrading-to-4.0.md. 4.2 → 4.3 needs no code changes.

Installation

# SQLite
pip install "djanorm[sqlite]"

# PostgreSQL
pip install "djanorm[postgresql]"

# libsql / Turso (local, embedded replica or remote)
pip install "djanorm[libsql]"

# DuckDB (embedded analytics, 4.0+)
pip install "djanorm[duckdb]"

# CockroachDB (distributed SQL, 4.1+ — reuses the psycopg pipeline)
pip install "djanorm[cockroachdb]"

# Optional extras
pip install "djanorm[redis]"      # queryset + row cache backend
pip install "djanorm[encrypted]"  # AES-GCM EncryptedCharField/TextField
pip install "djanorm[pydantic]"   # FastAPI-friendly DormSchema
pip install "djanorm[s3]"         # FileField on AWS S3 / MinIO / R2 / B2
pip install "djanorm[litestar]"   # DormPlugin: ASGI middleware + lifespan hooks (4.1+)
pip install "djanorm[parquet]"    # QueryLog.dump_parquet via pyarrow (4.1+)

# Dev tooling (sibling packages)
pip install pytest-djanorm djanorm-mypy

Quick start

1. Scaffold a project

dorm init blog

That creates:

  • settings.py — uncomment the DATABASES block matching your backend.
  • blog/ — an app package with an empty models.py.

A minimal settings.py looks like:

DATABASES = {
    "default": {
        "ENGINE": "sqlite",
        "NAME": "db.sqlite3",
    },
}
INSTALLED_APPS = ["blog"]

2. Define a model

# blog/models.py
import dorm


class Author(dorm.Model):
    name = dorm.CharField(max_length=100)
    email = dorm.EmailField(unique=True)
    is_active = dorm.BooleanField(default=True)


class Post(dorm.Model):
    title = dorm.CharField(max_length=200)
    body = dorm.TextField()
    author = dorm.ForeignKey(Author, on_delete=dorm.CASCADE)
    published_at = dorm.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["-published_at"]

3. Generate and apply migrations

dorm makemigrations blog
dorm migrate

4. Use it

Open a shell with dorm shell (IPython auto-detected) or import the models from your own script.

from blog.models import Author, Post

# Create
alice = Author.objects.create(name="Alice", email="alice@example.com")
post = Post.objects.create(
    title="Hello world",
    body="First post body.",
    author=alice,
)

# Bulk create
Post.objects.bulk_create([
    Post(title=f"Draft {i}", body="...", author=alice)
    for i in range(5)
])

# Filter / exclude / Q / F
from dorm import Q, F

active_authors = Author.objects.filter(is_active=True)
some_posts = Post.objects.filter(
    Q(title__icontains="hello") | Q(title__startswith="Draft")
).exclude(published_at__isnull=True)

# Lookups across relations
alices_posts = Post.objects.filter(author__name="Alice")

# select_related / prefetch_related to dodge N+1
for post in Post.objects.select_related("author"):
    print(post.author.name, post.title)   # 1 query, JOIN

# Get one
post = Post.objects.get(pk=1)

# Update — single instance
post.title = "Renamed"
post.save()

# Update — bulk via queryset
Post.objects.filter(author=alice).update(title=F("title") + " (by Alice)")

# Delete — single instance
post.delete()

# Delete — bulk
Post.objects.filter(published_at__isnull=True).delete()

Async API (same names with a prefix)

from blog.models import Author, Post

async def main():
    alice = await Author.objects.acreate(name="Alice", email="a@x.com")
    post = await Post.objects.acreate(title="Hi", body="...", author=alice)

    async for p in Post.objects.filter(author=alice):
        print(p.title)

    await Post.objects.filter(pk=post.pk).aupdate(title="Hi!")
    await post.adelete()

Atomic transactions

from dorm import transaction

with transaction.atomic():
    alice = Author.objects.create(name="Alice", email="a@x.com")
    Post.objects.create(title="t", body="b", author=alice)
    # any exception here rolls back both inserts

Documentation

The full documentation, tutorials and API reference are published at:

https://rroblf01.github.io/d-orm/

You will find the getting-started guide, complete examples, the API reference and production deployment notes there.

Contributing

Everyone is welcome to get involved! If you want to suggest changes, propose improvements or discuss the direction of the project, open an issue or a pull request on this repository. Discussions, ideas and critiques are very welcome.

License

See LICENSE.

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

djanorm-4.4.0.tar.gz (1.4 MB view details)

Uploaded Source

Built Distribution

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

djanorm-4.4.0-py3-none-any.whl (592.2 kB view details)

Uploaded Python 3

File details

Details for the file djanorm-4.4.0.tar.gz.

File metadata

  • Download URL: djanorm-4.4.0.tar.gz
  • Upload date:
  • Size: 1.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for djanorm-4.4.0.tar.gz
Algorithm Hash digest
SHA256 bb3445dba041715ce0d74cf1d01b47d915818862e6bd49ea59c5137f4ae7dc3a
MD5 e1afc2e083b51f81d18f8437f449ea9d
BLAKE2b-256 540cffac085784666734d88a851b7f11e6d5480e060155041568346dfeaf9b0e

See more details on using hashes here.

Provenance

The following attestation bundles were made for djanorm-4.4.0.tar.gz:

Publisher: publish.yml on rroblf01/d-orm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file djanorm-4.4.0-py3-none-any.whl.

File metadata

  • Download URL: djanorm-4.4.0-py3-none-any.whl
  • Upload date:
  • Size: 592.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for djanorm-4.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 57ef71bb786f6dba3fa0471507eb794fee7bc2dbc76f90ffadd8558da376191e
MD5 dc3fe3d1e27f592eb93af6e01f6bcd12
BLAKE2b-256 546ca555fb0743b2fa4a7139e9dbeea6f12d692b9c01ffe92f02eec10630ed51

See more details on using hashes here.

Provenance

The following attestation bundles were made for djanorm-4.4.0-py3-none-any.whl:

Publisher: publish.yml on rroblf01/d-orm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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