"""Reusable column mixins. Pattern: subclass `Base, TimestampMixin, SoftDeleteMixin` to get the columns. """ from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import DateTime, Uuid, func from sqlalchemy.orm import Mapped, mapped_column class UuidPkMixin: """Native UUID primary key, generated Python-side.""" id: Mapped[uuid.UUID] = mapped_column( Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4, nullable=False, ) class TimestampMixin: """`created_at` / `updated_at` server-managed timestamps (UTC).""" 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=func.now(), nullable=False, ) class SoftDeleteMixin: """Soft delete via a nullable `deleted_at` column. NOTE: each soft-deletable model must declare its own `ix__active` partial index in `__table_args__`. We deliberately don't auto-inject one here because SQLAlchemy's `__table_args__` from a mixin gets clobbered as soon as the model class declares its own — silently dropping the index. Declaring it explicitly keeps the contract visible at the model site. """ deleted_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, default=None, )