""" models.py - Manages Pydantic models """ from typing import List, Optional, Dict, Any from pydantic import BaseModel, Field class SettingsRequest(BaseModel): connection_id: Optional[str] = None label: str = Field(default="", max_length=100) clear_external_api_key: bool = False server_type: str emby_url: str emby_user: str emby_pass: str external_api_key: Optional[str] = None # AI fields are accepted for compatibility with older clients and ignored: saving a # media connection no longer writes ai_settings, so a stale form can never blank a # provider the user configured in the AI Hub. Use AiSettingsUpdateRequest instead. gemini_key: Optional[str] = None ai_provider: Optional[str] = None ollama_url: Optional[str] = None ollama_model: Optional[str] = None ollama_timeout: Optional[int] = None starred_models: Optional[List[str]] = None class ModelUpdateRequest(BaseModel): ollama_model: str class AiSettingsUpdateRequest(BaseModel): # None means "leave this field as stored". Defaults here would make a partial post # silently blank out settings the caller never mentioned -- that is how a saved # Gemini key gets destroyed. An empty string is still an explicit clear. ai_provider: Optional[str] = None gemini_key: Optional[str] = None ollama_url: Optional[str] = None ollama_model: Optional[str] = None ollama_timeout: Optional[int] = None starred_models: Optional[List[str]] = None class MovieFinderRequest(BaseModel): user_id: str filters: Dict[str, Any] = Field(default_factory=dict) class MusicFinderRequest(BaseModel): user_id: str filters: Dict[str, Any] = Field(default_factory=dict) class FreshnessOptions(BaseModel): last_successful_builds: int = 0 history_scope: str = "series" watched_within_days: int = 0 exhaustion_policy: str = "shorter" class DuplicatePolicyOptions(BaseModel): cross_block_policy: Optional[str] = None mode: str = "suppress" max_movies_per_franchise: int = 0 class SequencingItem(BaseModel): block_id: str take: int = 1 class SequencingOptions(BaseModel): mode: str = "sequential" pattern: Optional[List[SequencingItem]] = None exhaustion_policy: str = "continue" class RuntimeBudgetOptions(BaseModel): mode: str = "off" target_minutes: int = 0 allowed_overrun_minutes: int = Field(default=15, ge=0) end_local_time: Optional[str] = None timezone: Optional[str] = None class MixOptions(BaseModel): freshness: Optional[FreshnessOptions] = Field(default_factory=FreshnessOptions) duplicate_policy: Optional[DuplicatePolicyOptions] = Field(default_factory=DuplicatePolicyOptions) sequencing: Optional[SequencingOptions] = Field(default_factory=SequencingOptions) runtime_budget: Optional[RuntimeBudgetOptions] = Field(default_factory=RuntimeBudgetOptions) class MixedPlaylistRequest(BaseModel): user_id: str playlist_name: str blocks: Optional[List[Dict[str, Any]]] = None item_ids: Optional[List[str]] = None create_as_collection: bool = False mix_options: Optional[Dict[str, Any]] = None class BuilderPreviewRequest(BaseModel): user_id: str blocks: List[Dict[str, Any]] mix_options: Optional[Dict[str, Any]] = None class ReorderItemsRequest(BaseModel): user_id: str item_ids: List[str] class AddItemsRequest(BaseModel): user_id: str blocks: List[Dict[str, Any]] mix_options: Optional[Dict[str, Any]] = None class ScheduleDetails(BaseModel): frequency: str time: Optional[str] = None days_of_week: Optional[List[int]] = None interval_minutes: Optional[int] = None class QuickPlaylistScheduleData(BaseModel): quick_playlist_type: str options: Optional[Dict[str, Any]] = None class EnrichmentScheduleData(BaseModel): batch_size: int = Field(default=15, ge=1, le=500) timeout: int = Field(default=120, ge=10, le=600) class ScheduleRequest(BaseModel): job_type: str playlist_name: str user_id: str schedule_details: ScheduleDetails preset_id: Optional[str] = None preset_name: Optional[str] = None blocks: Optional[List[Dict[str, Any]]] = None quick_playlist_data: Optional[QuickPlaylistScheduleData] = None enrichment_data: Optional[EnrichmentScheduleData] = None create_as_collection: bool = False mix_options: Optional[Dict[str, Any]] = None enabled: bool = True snoozed_until: Optional[str] = None trigger_sources: List[str] = ["clock", "watch", "library"] timezone: Optional[str] = None class SnoozeRequest(BaseModel): minutes: Optional[int] = 60 until: Optional[str] = None class AiTweaks(BaseModel): threshold: float = 0.65 limit: int = 25 strictness: str = "genre_verified" temperature: float = 0.2 target_size: int = 10 only_unwatched: bool = False system_prompt: str = "" class AiPromptRequest(BaseModel): prompt: str tweaks: Optional[AiTweaks] = None existing_blocks: Optional[List[Dict[str, Any]]] = None class QuickBuildRequest(BaseModel): user_id: str playlist_name: str quick_build_type: str options: Dict[str, Any] = Field(default_factory=dict) class DeleteItemRequest(BaseModel): item_id: str user_id: str class RemoveFromPlaylistRequest(BaseModel): item_id_to_remove: str user_id: str class ConvertItemRequest(BaseModel): item_id: str user_id: str new_name: str target_type: str delete_original: bool = False class ResetWatchRequest(BaseModel): user_id: str season_number: Optional[int] = None class ExternalPromptRequest(BaseModel): prompt: str preset_name: str class ExternalBuildRequest(BaseModel): preset_name: str playlist_name: str class ResetVectorDbRequest(BaseModel): preserve_enrichments: bool = True class StartEnrichmentRequest(BaseModel): batch_size: int = 10 max_items: Optional[int] = None class BulkDeleteRequest(BaseModel): item_ids: List[str] user_id: str class OverlapReportRequest(BaseModel): item_ids: Optional[List[str]] = None user_id: str class ReplayRunRequest(BaseModel): playlist_name: Optional[str] = None user_id: Optional[str] = None dry_run: bool = False class AssistItem(BaseModel): """One movie on the Playlist Assist canvas as the browser reports it.""" Id: str locked: bool = False Name: Optional[str] = None class AssistChatMessage(BaseModel): role: str = "user" content: str = "" class AssistChatRequest(BaseModel): prompt: str revision_id: Optional[str] = None current_items: List[AssistItem] = Field(default_factory=list) chat_history: List[AssistChatMessage] = Field(default_factory=list) class AssistSaveRequest(BaseModel): playlist_name: str description: str = "" item_ids: List[str] = Field(default_factory=list) user_id: Optional[str] = None