"""Managed thumbnail cache records. One row per ``cache_key`` (pixel hash + size + algorithm version, or the source byte hash for undecodable inputs). The row records whether generation succeeded or failed and why, so a broken original is not retried on every request. Because the key contains the normalized pixel hash, an EXIF-only change reuses the thumbnail and a genuine pixel change invalidates it automatically. """ from __future__ import annotations from datetime import datetime from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, func from sqlalchemy.orm import Mapped, mapped_column from photo_pipeline.db import Base class Thumbnail(Base): __tablename__ = "thumbnails" cache_key: Mapped[str] = mapped_column(String, primary_key=True) asset_id: Mapped[str] = mapped_column(ForeignKey("assets.id"), nullable=False) size: Mapped[int] = mapped_column(Integer, nullable=False) state: Mapped[str] = mapped_column(String, nullable=False) # ready | error error_code: Mapped[str | None] = mapped_column(String) path: Mapped[str | None] = mapped_column(String) width: Mapped[int | None] = mapped_column(Integer) height: Mapped[int | None] = mapped_column(Integer) format: Mapped[str | None] = mapped_column(String) # Durable comparison evidence for an archived asset: never evicted by the LRU # quota, because the original may be on a medium that is no longer reachable. protected: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() )