"""Gemeinsame Bausteine aller API-Schemata.""" from datetime import date from decimal import Decimal from typing import Annotated from pydantic import BaseModel, ConfigDict, Field # Geldbeträge werden als Decimal geführt und im JSON als String ausgeliefert, # damit auf dem Weg zum Frontend keine Genauigkeit verloren geht. Money = Annotated[Decimal, Field(max_digits=12, decimal_places=2)] PositiveMoney = Annotated[Decimal, Field(gt=0, max_digits=12, decimal_places=2)] NonNegativeMoney = Annotated[Decimal, Field(ge=0, max_digits=12, decimal_places=2)] HexColor = Annotated[str, Field(pattern=r"^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")] class ApiModel(BaseModel): """Basisklasse für Ausgabeschemata; liest Werte direkt von ORM-Objekten.""" model_config = ConfigDict(from_attributes=True) class InputModel(BaseModel): """Basisklasse für Eingabeschemata; unbekannte Felder werden abgewiesen.""" model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) class Page[T](ApiModel): """Einfache Seitenausgabe mit Gesamtzahl.""" items: list[T] total: int = Field(description="Gesamtzahl der Treffer ohne Berücksichtigung der Seite.") limit: int offset: int class DateRange(InputModel): """Ein von/bis-Zeitraum, beide Grenzen einschließlich.""" date_from: date date_to: date class ErrorResponse(BaseModel): """Einheitliches Fehlerformat aller Endpunkte.""" detail: str = Field(description="Für Menschen lesbare Beschreibung des Fehlers.") code: str = Field(description="Stabiler Fehlercode zur Auswertung im Frontend.") class MessageResponse(BaseModel): """Antwort für Aktionen ohne eigene Nutzlast.""" detail: str