"""Pydantic-Schemas für API-Ein- und -Ausgabe.""" # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations from datetime import date, datetime from typing import List, Literal, Optional from pydantic import BaseModel, ConfigDict, Field # ---------- Typen ------------------------------------------------------------ MediaKind = Literal["book", "series"] MediaStatus = Literal["plan", "reading", "done", "hold", "dropped"] # ---------- Eingaben --------------------------------------------------------- class MediaBase(BaseModel): kind: MediaKind title: str = Field(..., max_length=200, min_length=1) original_title: Optional[str] = Field(None, max_length=200) description: Optional[str] = None cover_url: Optional[str] = Field(None, max_length=600) release_year: Optional[int] = Field(None, ge=1800, le=2200) status: MediaStatus = "plan" rating: Optional[float] = Field(None, ge=0, le=10) notes: Optional[str] = None # Buch-Felder author: Optional[str] = Field(None, max_length=200) isbn: Optional[str] = Field(None, max_length=20) total_volumes: Optional[int] = Field(None, ge=0) total_chapters: Optional[int] = Field(None, ge=0) total_pages: Optional[int] = Field(None, ge=0) volumes_read: Optional[int] = Field(None, ge=0) chapters_read: Optional[int] = Field(None, ge=0) pages_read: Optional[int] = Field(None, ge=0) # Serien-Felder network: Optional[str] = Field(None, max_length=120) total_seasons: Optional[int] = Field(None, ge=0) total_episodes: Optional[int] = Field(None, ge=0) episodes_watched: Optional[int] = Field(None, ge=0) season_watching: Optional[int] = Field(None, ge=0) start_date: Optional[date] = None end_date: Optional[date] = None genres: List[str] = [] tags: List[str] = [] class MediaCreate(MediaBase): pass class MediaUpdate(BaseModel): """Alle Felder optional – PATCH-Semantik.""" title: Optional[str] = Field(None, max_length=200, min_length=1) original_title: Optional[str] = None description: Optional[str] = None cover_url: Optional[str] = None release_year: Optional[int] = None status: Optional[MediaStatus] = None rating: Optional[float] = Field(None, ge=0, le=10) notes: Optional[str] = None author: Optional[str] = None isbn: Optional[str] = None total_volumes: Optional[int] = None total_chapters: Optional[int] = None total_pages: Optional[int] = None volumes_read: Optional[int] = None chapters_read: Optional[int] = None pages_read: Optional[int] = None network: Optional[str] = None total_seasons: Optional[int] = None total_episodes: Optional[int] = None episodes_watched: Optional[int] = None season_watching: Optional[int] = None start_date: Optional[date] = None end_date: Optional[date] = None genres: Optional[List[str]] = None tags: Optional[List[str]] = None class ProgressUpdate(BaseModel): """Schnelles Fortschritts-Update.""" delta: int = 1 set_to: Optional[int] = None # wenn angegeben, setzt absolut # ---------- Ausgaben --------------------------------------------------------- class GenreOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int name: str class TagOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int name: str class MediaOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int kind: str title: str original_title: Optional[str] description: Optional[str] cover_url: Optional[str] release_year: Optional[int] status: str rating: Optional[float] notes: Optional[str] author: Optional[str] isbn: Optional[str] total_volumes: Optional[int] total_chapters: Optional[int] total_pages: Optional[int] volumes_read: Optional[int] chapters_read: Optional[int] pages_read: Optional[int] network: Optional[str] total_seasons: Optional[int] total_episodes: Optional[int] episodes_watched: Optional[int] season_watching: Optional[int] start_date: Optional[date] end_date: Optional[date] progress_percent: float genres: List[GenreOut] tags: List[TagOut] created_at: datetime updated_at: datetime class StatsBucket(BaseModel): status: str count: int class StatsPerKind(BaseModel): kind: str total: int avg_rating: Optional[float] by_status: List[StatsBucket] class StatsResponse(BaseModel): books: StatsPerKind series: StatsPerKind total_entries: int