"""Declarative base and shared column mixins.""" from __future__ import annotations import datetime as dt from sqlalchemy import DateTime, MetaData, func from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column # Deterministic constraint names. This has to be in place *before* the first # migration: MySQL will otherwise invent its own names, and Alembic autogenerate # then produces diffs referencing constraints it cannot reliably drop. 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): metadata = MetaData(naming_convention=NAMING_CONVENTION) class TimestampMixin: """Server-side created/updated timestamps. Defaults are rendered by MySQL rather than Python so rows written outside the application (migrations, manual SQL) are stamped consistently. """ created_at: Mapped[dt.datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False, ) updated_at: Mapped[dt.datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False, )