"""Declarative base + shared mixins for all ORM models.""" from __future__ import annotations import uuid from datetime import UTC, datetime from sqlalchemy import DateTime, MetaData, func from sqlalchemy.dialects.postgresql import UUID as PG_UUID from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column NAMING_CONVENTION = { "ix": "ix_%(column_0_label)s", "uq": "uq_%(table_name)s_%(column_0_name)s", "ck": "ck_%(table_name)s_%(constraint_name)s", "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", "pk": "pk_%(table_name)s", } class Base(DeclarativeBase): """Project-wide declarative base. UUID columns are declared explicitly on each model via `PG_UUID(as_uuid=True)` rather than through a `type_annotation_map` — Flask-SQLAlchemy injects its own registry which is incompatible with per-base annotation maps. """ metadata = MetaData(naming_convention=NAMING_CONVENTION) class UuidPkMixin: """Mixin: UUID v4 primary key generated client-side.""" id: Mapped[uuid.UUID] = mapped_column( PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) def _utcnow() -> datetime: return datetime.now(tz=UTC) class TimestampsMixin: """Mixin: `created_at` / `updated_at` columns, UTC timezone-aware.""" created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False, ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=_utcnow, nullable=False, )