A Django-like ORM with synchronous and asynchronous support
Project description
djanorm
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 and libsql / Turso. Ships with migrations + linter, atomic transactions, signals, validation, relationship loading (select_related / prefetch_related), aggregations, DB functions, async-native ORM path, queryset & row caching, and Pydantic interop — all with real static typing (Field[T]).
What's new in 3.1
settings.USE_TZ = True— Django ≥4-compatible timezone-aware datetimes (UTC normalisation on insert,TIMESTAMP WITH TIME ZONEon PG).Meta.proxy = True— proxy models share the parent's table; autodetector skips them somakemigrationsdoesn't emit a phantomCreateModel.QuerySet.dates(field, kind)/datetimes(...)— distinct truncated values for archive listings.dorm migrate --fake/--fake-initial— record migrations as applied without running operations. Ideal for adopting dorm against a legacy schema.- JSONField PG operators —
__contained_by,__has_key,__has_keys,__has_any_keys,__overlap,__len. Same spelling as Django'scontrib.postgres. Field.deconstruct()— base-class implementation for migration serialisation. Custom field subclasses get it for free.Model.from_db(db, field_names, values)— Django-parity hydration hook; stamps_state.dbwith the alias.dorm.transaction.savepoint()/savepoint_commit()/savepoint_rollback()— manual savepoints insideatomic().dorm.contrib.auth.tokens— stateless HMAC-signed reset tokens for password-reset / email-verification flows.Meta.permissions = [...]+sync_permissions()— declare custom permissions, materialise intoauth_permission.dorm.contrib.tenants—TenantContext/aTenantContextfor PostgreSQLsearch_pathswitching.- MySQL / MariaDB scaffold —
ENGINE = "mysql"parses throughparse_database_url; the connection wrapper raisesImproperlyConfiguredpointing at v3.1 for the full implementation. - MySQL / MariaDB vector support —
VectorFieldreturnsVECTOR(N)and distance expressions compile toVEC_DISTANCE_EUCLIDEAN/VEC_DISTANCE_COSINE.
What's new in 3.0
dorm.contrib.auth—User/Group/Permissionwith stdlib PBKDF2 hashing. Same shape as Django, nopasslibdependency.dorm.contrib.encrypted—EncryptedCharField/EncryptedTextField(AES-GCM with key rotation;pip install 'djanorm[encrypted]').dorm.contrib.asyncguard— surfaces sync ORM calls inside an event loop as warnings or exceptions.dorm.contrib.querylog+dorm.contrib.querycount— request-scoped collectors for SQL traffic and N+1 guards.dorm.contrib.prometheus— stdlib-only metrics exposer for the/metricsendpoint.dorm lint-migrations— pre-merge gate that flags online-deploy footguns (full-table backfills, missingconcurrently=True, irreversibleRunPython).LocMemCache+Manager.cache_get(pk=…)/cache_get_many(pks=[…])— in-process LRU + single-row caching that piggy-backs on the same invalidation signal as queryset cache.- Sticky read-after-write window for the DB router — no stale replica reads after a write on the same request.
settings.SLOW_QUERY_MS— slow-query WARNING;settings.RETRY_ATTEMPTS/RETRY_BACKOFF— transient-error retry knobs (resolution: explicit setting > env var > default).dorm.test.assertNumQueries+assertMaxQueries— context-manager and decorator forms (sync + async).- Async-aware
dorm shell— top-levelawaitworks in the stdlib REPL fallback. - Math / string DB functions —
Power,Sqrt,Mod,Sign,Ceil,Floor,Log,Ln,Exp,Random,Trim,LTrim,RTrim,NullIf.
Full notes in CHANGELOG.md. Zero breaking changes vs 2.5 — every addition is opt-in or zero-cost when unused.
Installation
# SQLite
pip install "djanorm[sqlite]"
# PostgreSQL
pip install "djanorm[postgresql]"
# libsql / Turso (local, embedded replica or remote)
pip install "djanorm[libsql]"
# 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
Quick start
1. Scaffold a project
dorm init blog
That creates:
settings.py— uncomment theDATABASESblock matching your backend.blog/— an app package with an emptymodels.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
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 djanorm-3.0.0.tar.gz.
File metadata
- Download URL: djanorm-3.0.0.tar.gz
- Upload date:
- Size: 1.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99c6d6d83806c1aa5eae73095abde1afc8959f1066476b89511424fa7de88c74
|
|
| MD5 |
7ad750476e9b4ba8605e297ae49412fb
|
|
| BLAKE2b-256 |
6e92e804f2fa2fb80b05676a83142f61a3be2cbfdcf36f1d907d839d8a1f7744
|
Provenance
The following attestation bundles were made for djanorm-3.0.0.tar.gz:
Publisher:
publish.yml on rroblf01/d-orm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
djanorm-3.0.0.tar.gz -
Subject digest:
99c6d6d83806c1aa5eae73095abde1afc8959f1066476b89511424fa7de88c74 - Sigstore transparency entry: 1435947326
- Sigstore integration time:
-
Permalink:
rroblf01/d-orm@f986facf646112fbbb97ea998995798f1873e533 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f986facf646112fbbb97ea998995798f1873e533 -
Trigger Event:
release
-
Statement type:
File details
Details for the file djanorm-3.0.0-py3-none-any.whl.
File metadata
- Download URL: djanorm-3.0.0-py3-none-any.whl
- Upload date:
- Size: 340.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de812230519f377b838b00dea7db6ab54d901f15dfd7b120967ceec45fd1191a
|
|
| MD5 |
522cb742bef01d6690a8a6afbf18d454
|
|
| BLAKE2b-256 |
84eaf4ca752669a82686716992a7dfe1c0f9ec95190e7bd011e6cff1d1034081
|
Provenance
The following attestation bundles were made for djanorm-3.0.0-py3-none-any.whl:
Publisher:
publish.yml on rroblf01/d-orm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
djanorm-3.0.0-py3-none-any.whl -
Subject digest:
de812230519f377b838b00dea7db6ab54d901f15dfd7b120967ceec45fd1191a - Sigstore transparency entry: 1435947339
- Sigstore integration time:
-
Permalink:
rroblf01/d-orm@f986facf646112fbbb97ea998995798f1873e533 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f986facf646112fbbb97ea998995798f1873e533 -
Trigger Event:
release
-
Statement type: