Minor release. No breaking changes vs 4.2 — every addition is opt-in.
Fixed (release-blocker round)
upsert(returning=True)passed Field objects instead of names —bulk_create.returningexpectedlist[str]. Now resolves to field names + skips M2M.InboxRecorddocstring now mandatesMeta.unique_together = [("message_id", "handler_name")]on concrete subclasses (DB-level race guard).- Saga inside outer atomic logs a WARNING — explains the savepoint-degradation; supports both PG (
_atomic_conn) and SQLite (_atomic_depth). - anonymizer moved from offset to cursor pagination (
pk__gt=last) — O(N) instead of O(N²) on million-row tables. two_phase_commitrejects nested atomic blocks with a clear RuntimeError.- temporal docstring warns that bulk_create / bulk_update / queryset.update / queryset.delete bypass post_save / post_delete and miss the temporal mirror.
- tasks._wait_for_notify wrapped in broader try/except for psycopg internals.
Added — Tier 2 (TaskQueue expansion)
- Cross-vendor UPSERT —
Manager.upsert(objs, unique_fields=[...])/
aupsert(...)sugar over
bulk_create(update_conflicts=True). Default
update_fieldsexcludes PK + unique columns. - Temporal tables —
@temporaldecorator builds a
<Model>Temporalmirror withvalid_from/valid_to/
operationcolumns.post_save/post_deletereceivers
close the previous open row and open a new one; the free function
:func:dorm.contrib.temporal.as_of(Model, ts)returns the
point-in-time snapshot. dorm.contrib.tasks— Celery-lite background task runner
built on the outbox + LISTEN/NOTIFY.TaskQueue,
@task(queue, name=...)decorator,.delay(...)enqueue.
Workers fall back to polling when LISTEN/NOTIFY isn't available.
Added — Tier 2 (extra field types)
dorm.contrib.extra_fields.MoneyField— Decimal column
paired with a fixed ISO-4217 currency code; assignment accepts
:class:Money/ Decimal / int / str.SemverField— validates SemVer 2.0.0 grammar at assignment.PhoneField— E.164-shaped number with whitespace /
punctuation stripping.ColorField—#RRGGBB/#RRGGBBAAhex string,
upper-cased on storage.JSONSchemaField—JSONFieldvalidated against a
user-supplied JSON Schema at assignment time. Requires the
optionaljsonschemadep.
Added — Tier 3 (async/sync ergonomics)
SyncDataLoader— synchronous sibling of the async
:class:DataLoader. Same key-coalescing pattern, explicit
:meth:flushboundary,prime()/clear_all()parity.dorm.contrib.background.BackgroundTasks— bounded async
task scheduler for fire-and-forget work inside one request.
Configurable concurrency, swallow-or-raise semantics, cancel-all
on shutdown.
Added — Tier 4 (distributed transactions)
dorm.contrib.saga— :class:Saga+ :class:Step. Each
step runs in its ownatomic()block; compensations fire in
reverse order on failure.SagaRunrecords the per-run audit
trail (completed / compensated / failure / context).dorm.contrib.two_phase.two_phase_commit— XA-style 2PC
over PostgreSQLPREPARE TRANSACTION. PG-only; raises
:class:TwoPhaseErroron phase-1 or phase-2 failure.dorm.contrib.inbox— :class:InboxRecordabstract model@idempotentdecorator that guarantees exactly-once
processing via a unique-key constraint on(message_id, handler_name).
Added — Tier 5 (observability / perf)
- Plan-drift history — every :func:
compare/
:func:acomparecall is appended to a bounded ring (50 / tag).
history(tag=None)reads it back;clear_historyflushes. - Pool-warmup gauge —
dorm.contrib.pool_autoscale.warmup_poolnow records its
duration viarecord_pool_warmup(alias, seconds)and the
Prometheus snapshot exposesdorm_pool_warmup_seconds{alias}. RowCache— process-local LRU cache keyed by PK with
automatic invalidation onpost_save/post_delete.
Bounded, opt-out viainvalidate_on_write=False.- N+1 fix suggestions —
dorm.contrib.nplusone.suggest_fix(template, model_hint=...)
returns a human-readable recommendation (select_relatedfor
FK,prefetch_relatedfor reverse / M2M).
Added — Tier 6 (DX)
dorm init --template— scaffolding cookiecutter-style
templates. Shipsfastapi-postgresandlitestar-sqlite
out of the box.
Added — Tier 7 (sugar)
F.coalesce(*defaults)— sugar forCoalesce(F(...), ...). Reads top-down vs nested constructor.subtree_filter()— convenience wrapper over
:func:descendants_ctethat splices aWHEREpredicate
around the recursive CTE.
Added — Tier 8 (security)
dorm.contrib.anonymizer— batch row rewrite for safe
dumps / GDPR. Built-in strategies (redact/
random_email/random_phone) + arbitrary
Callable[[Any], Any]strategies. Per-batchatomic(),
optional progress callback.rotate_short_lived_token(existing)—
dorm.contrib.auth.tokenshelper returning(new, old)
for the dual-token grace-window rotation pattern.- Extensible SQL-log sensitive patterns —
dorm.db.utils.add_sensitive_pattern("ssn", "credit_card")
extends the log-redactor's substring list;reset_sensitive_patterns()
restores the built-in tuple.
Deferred (won't land in 4.3)
- MSSQL / SQL Server backend — requires a live driver + server
for safe integration; scoped for 4.4. - ClickHouse read-only backend — same rationale.
- Spanner backend — same rationale.
- Composite Foreign Keys — refactor of the query compiler;
scoped for 4.4. - TimescaleDB hypertable helpers — needs the PG extension
available in test infra. dorm shell --notebook— Jupyter kernel; scoped for 4.4.pytest-djanormfactories — change to the sibling package
rather than the main wheel.- Migration squash preserving
RunPython— high-risk for
data ops; needs targeted incident review first.
Validated
ruff check: clean.ty check: clean.mkdocs build --strict: clean.pytest tests/: every new v4.3 suite green (~120 new tests).