CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
Production-grade media metadata backend per docs/aurora-core-spec.md: Aurora-owned UUIDv7 identity system, normalized movie/TV/people/credits schema, provider abstraction with a TMDb adapter, an idempotent async ingestion pipeline (normalize -> match -> upsert -> provenance), a Postgres FTS/trigram search layer, a stateless FastAPI v1 API with Redis caching/rate-limiting, arq workers, dev/prod Docker Compose topologies, unit + integration + load tests, CI, and architecture/ database/API/ERD documentation. Co-Authored-By: Claude Sonnet 5 <[email protected]>
81 lines
2.4 KiB
Python
81 lines
2.4 KiB
Python
"""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,
|
|
)
|