"""Declarative base and shared mixins for all Aurora Core models.""" from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import DateTime, Enum, MetaData, func from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from uuid6 import uuid7 from app.models.enums import ( ArtworkType, CompanyType, IngestionJobStatus, MatchConfidence, MovieStatus, SeriesStatus, VideoType, ) 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", } def _enum_column(enum_cls: type) -> Enum: """Store our str-Enum members by `.value` (lowercase snake_case), not `.name`, which is what SQLAlchemy's Enum type does by default. Without this, the DB would store e.g. "RELEASED" while every part of the codebase (schemas, ingestion, API filters) works with "released".""" return Enum( enum_cls, values_callable=lambda obj: [e.value for e in obj], name=f"{enum_cls.__name__.lower()}_enum", ) class Base(DeclarativeBase): """Base class for all ORM models. Naming convention keeps Alembic's autogenerated constraint names stable across databases.""" metadata = MetaData(naming_convention=NAMING_CONVENTION) type_annotation_map = { MovieStatus: _enum_column(MovieStatus), SeriesStatus: _enum_column(SeriesStatus), ArtworkType: _enum_column(ArtworkType), VideoType: _enum_column(VideoType), CompanyType: _enum_column(CompanyType), IngestionJobStatus: _enum_column(IngestionJobStatus), MatchConfidence: _enum_column(MatchConfidence), } class UUIDPrimaryKeyMixin: """UUIDv7 primary key. Time-ordered so it stays btree-friendly and can double as a creation-order cursor, unlike a random UUIDv4.""" id: Mapped[uuid.UUID] = mapped_column( primary_key=True, default=lambda: uuid.UUID(str(uuid7())), ) class TimestampMixin: 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, )