Novedades 4.0¶
Major release. Salto directo desde 3.3 — todo lo planificado para
3.4 (helpers framework-agnósticos, helpers PG, sibling packages
djanorm-mypy y pytest-djanorm) viaja en este release, junto con
las siete features headline que justifican el major bump.
1. Full-text search ampliado¶
Sobre la base existente (SearchVector, SearchQuery, SearchRank,
SearchHeadline) se añaden:
from dorm.search import (
TrigramSimilarity, TrigramWordSimilarity, search_index,
)
# Ranking por similitud trigram (requires pg_trgm extension)
qs = (
Author.objects
.annotate(score=TrigramSimilarity("name", "alise"))
.order_by("-score")
)
# Indice GIN funcional para acelerar __search lookups
from dorm.migrations.operations import RunSQL
operations = [
RunSQL(
search_index("articles", "title", "body"),
reverse_sql='DROP INDEX IF EXISTS ix_articles_search'
),
]
2. Migraciones online (zero-downtime)¶
from dorm.migrations.operations import (
AddFieldOnline, BackfillBatch, SetNotNullOnline,
)
operations = [
# 1. Añade columna nullable — no rewrite.
AddFieldOnline(
"Order",
"currency",
dorm.CharField(max_length=3, null=False, default="USD"),
),
# 2. Backfill por chunks de PK.
BackfillBatch(
table="orders",
update_sql=(
'UPDATE "orders" SET "currency" = \'USD\' '
'WHERE "id" BETWEEN %s AND %s AND "currency" IS NULL'
),
batch_size=10_000,
sleep_seconds=0.05,
),
# 3. SET NOT NULL sin rewrite (PG ≥ 12 valida vía CHECK NOT VALID).
SetNotNullOnline("Order", "currency"),
]
3. OpenTelemetry enriquecido¶
dorm.contrib.otel.instrument() ahora añade atributos siguiendo las
SemConv 1.20+:
db.operation— verbo (SELECT/INSERT/UPDATE/DELETE/COPY/...)db.sql.tableydb.collection.name— tabla principaldb.dorm.alias— alias de conexión (multi-DB filtering)- Span name:
"<OPERATION> <table>"(Datadog/Honeycomb agrupa).
4. CTEs recursivos (árboles)¶
from dorm.tree import descendants, ancestors, descendants_cte
# Walks the adjacency list rooted at category id=42.
rows = descendants(Category, parent_field="parent_id", root_pk=42)
# Compose into a manual queryset:
cte = descendants_cte(
Category,
parent_field="parent_id",
root_pk=42,
cycle_field="path", # cycle protection (PG-only)
)
qs = Category.objects.with_cte(descendants=cte).raw(
'SELECT id, name FROM descendants ORDER BY id'
)
5. Multi-tenancy a nivel fila¶
Alternativa a dorm.contrib.tenants (schema-level PG-only):
import dorm
from dorm.contrib.tenants_row import TenantModel, current_tenant
class Note(TenantModel):
title = dorm.CharField(max_length=200)
# En el handler / job:
with current_tenant(request.user.tenant_id):
Note.objects.create(title="hi") # tenant_id auto-fill
notes = list(Note.objects.all()) # filtro auto
# Escape hatch
all_notes = list(Note.unscoped.all())
Funciona en PG, MySQL, SQLite, libsql. NoActiveTenantError cuando
no hay tenant activo (no fallback silencioso).
6. Backend DuckDB¶
Primer backend OLAP analítico de la familia. Embebido, sin servidor, columnar, ejecución vectorizada.
import dorm
dorm.configure(
DATABASES={"default": {"ENGINE": "duckdb", "NAME": "analytics.duckdb"}},
INSTALLED_APPS=["dashboards"],
)
Soporta:
- Atomic transactions (sin SAVEPOINT — DuckDB no lo tiene; nested
atomic() degrada a no-op boundary).
- Streaming iterator.
- Introspection vía information_schema (compatible con dorm diff).
- Async wrapper que ejecuta llamadas síncronas en thread executor.
Instalación: pip install 'djanorm[duckdb]'.
7. HStoreField + PG ENUM nativo¶
import enum, dorm
class Status(enum.Enum):
ACTIVE = "active"
ARCHIVED = "archived"
class Article(dorm.Model):
# Storage hstore en PG, JSON-en-TEXT en SQLite.
metadata = dorm.HStoreField(null=True, blank=True)
# Tipo ENUM nativo PG (CREATE TYPE ... AS ENUM).
status = dorm.EnumField(Status, native=True, type_name="article_status")
Migration matching:
from dorm.migrations.operations import (
CreatePGEnum, DropPGEnum, AddPGEnumValue, CreateModel, RunSQL,
)
operations = [
RunSQL("CREATE EXTENSION IF NOT EXISTS hstore",
reverse_sql="DROP EXTENSION IF EXISTS hstore"),
CreatePGEnum("article_status", ["active", "archived"]),
CreateModel("Article", fields=[...]),
]
# Más adelante, para añadir un valor:
operations = [AddPGEnumValue("article_status", "deleted")]
HStoreField en SQLite hace fallback automático a TEXT JSON.
EnumField(native=True) en SQLite/MySQL hace fallback a VARCHAR.
Sibling packages¶
Ver paquetes hermanos:
djanorm-mypy— plugin de mypy validando kwargsfilter(), suffixes lookup, sintetizandopk/id.pytest-djanorm— fixturestransactional_db,atransactional_db,pg_container,nplusone_guard.
Sacados a paquetes propios para mantener el wheel principal sin dependencias dev-only.
Migración desde 3.3.0¶
Cero cambios obligatorios. Todo opt-in:
pip install --upgrade djanorm— runtime sigue igual.- Si quieres DuckDB:
pip install 'djanorm[duckdb]'. - Si quieres mypy/pytest tooling:
pip install djanorm-mypy pytest-djanorm. - Si quieres helpers v4:
from dorm.tree import ...,from dorm.contrib.tenants_row import ..., etc.