Files
ohm_streaming/app/models/watchlist.py
root a684237725
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled
Phase 2 Complete: SQL migration with SQLModel and Alembic
2026-03-25 13:46:15 +00:00

179 lines
5.8 KiB
Python

"""Models for Watchlist and Auto-Download system with SQLModel support"""
import uuid
import json
from pydantic import BaseModel, Field as PydanticField
from typing import Optional, Literal, List
from datetime import datetime
from enum import Enum
from sqlmodel import SQLModel, Field, Relationship, Column, String
class WatchlistStatus(str, Enum):
"""Status of a watchlist item"""
ACTIVE = "active" # Currently tracking for new episodes
PAUSED = "paused" # Temporarily paused
COMPLETED = "completed" # Anime completed, no longer tracking
ARCHIVED = "archived" # Archived but kept for history
class QualityPreference(str, Enum):
"""Preferred video quality"""
AUTO = "auto" # Let provider decide
P1080 = "1080p" # Full HD
P720 = "720p" # HD
P480 = "480p" # SD
class WatchlistItemBase(SQLModel):
"""Base schema for watchlist items"""
anime_title: str = Field(index=True)
anime_url: str
provider_id: str
lang: str = Field(default="vostfr")
# Tracking state
last_checked: Optional[datetime] = None
last_episode_downloaded: int = Field(default=0)
total_episodes: Optional[int] = None
# Settings
auto_download: bool = Field(default=True)
quality_preference: QualityPreference = Field(default=QualityPreference.AUTO)
status: WatchlistStatus = Field(default=WatchlistStatus.ACTIVE)
# Metadata
poster_image: Optional[str] = None
cover_image: Optional[str] = None
synopsis: Optional[str] = None
# Timestamps
added_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
class WatchlistItemTable(WatchlistItemBase, table=True):
"""Database table for watchlist items"""
__tablename__ = "watchlist_items"
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
primary_key=True,
index=True,
nullable=False
)
user_id: str = Field(foreign_key="users.id", index=True)
# Store list as JSON string in SQLite
genres_json: Optional[str] = Field(default="[]", sa_column=Column(String))
@property
def genres(self) -> List[str]:
return json.loads(self.genres_json or "[]")
@genres.setter
def genres(self, value: List[str]):
self.genres_json = json.dumps(value or [])
# Relationships - Using string reference
user: Optional["UserTable"] = Relationship(back_populates="watchlist_items")
class WatchlistItem(BaseModel):
"""An anime being tracked for automatic episode downloads (API Response)"""
id: str
user_id: str
anime_title: str
anime_url: str
provider_id: str
lang: str
last_checked: Optional[datetime] = None
last_episode_downloaded: int = 0
total_episodes: Optional[int] = None
auto_download: bool = True
quality_preference: QualityPreference = QualityPreference.AUTO
status: WatchlistStatus = WatchlistStatus.ACTIVE
poster_image: Optional[str] = None
cover_image: Optional[str] = None
synopsis: Optional[str] = None
genres: List[str] = []
added_at: datetime
updated_at: datetime
class Config:
json_encoders = {
datetime: lambda v: v.isoformat()
}
class WatchlistItemCreate(BaseModel):
"""Model for creating a new watchlist item"""
anime_title: str
anime_url: str
provider_id: str
lang: Literal["vostfr", "vf"] = "vostfr"
auto_download: bool = True
quality_preference: QualityPreference = QualityPreference.AUTO
poster_image: Optional[str] = None
cover_image: Optional[str] = None
synopsis: Optional[str] = None
genres: List[str] = []
class WatchlistItemUpdate(BaseModel):
"""Model for updating a watchlist item"""
auto_download: Optional[bool] = None
quality_preference: Optional[QualityPreference] = None
status: Optional[WatchlistStatus] = None
last_episode_downloaded: Optional[int] = None
total_episodes: Optional[int] = None
class NewEpisodeInfo(BaseModel):
"""Information about a newly detected episode"""
episode_number: int
episode_title: Optional[str] = None
episode_url: str
season_number: Optional[int] = None
anime_title: str
provider_id: str
class AutoDownloadResult(BaseModel):
"""Result of an automatic download check"""
watchlist_item_id: str
anime_title: str
new_episodes_found: int
episodes_downloaded: list[int] = PydanticField(default_factory=list)
episodes_failed: list[tuple[int, str]] = PydanticField(default_factory=list)
checked_at: datetime = PydanticField(default_factory=datetime.now)
class WatchlistSettingsBase(SQLModel):
check_interval_hours: int = Field(default=6)
auto_download_enabled: bool = Field(default=True)
max_concurrent_auto_downloads: int = Field(default=2)
notify_on_new_episodes: bool = Field(default=False)
include_completed_anime: bool = Field(default=False)
class WatchlistSettingsTable(WatchlistSettingsBase, table=True):
"""Database table for global watchlist settings"""
__tablename__ = "watchlist_settings"
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
primary_key=True,
index=True,
nullable=False
)
user_id: str = Field(foreign_key="users.id", index=True, default="default")
class WatchlistSettings(BaseModel):
"""Global watchlist settings"""
check_interval_hours: int = PydanticField(default=6, ge=1, le=168)
auto_download_enabled: bool = PydanticField(default=True)
max_concurrent_auto_downloads: int = PydanticField(default=2, ge=1, le=10)
notify_on_new_episodes: bool = PydanticField(default=False)
include_completed_anime: bool = PydanticField(default=False)
# Import UserTable here to resolve SQLModel Relationship mappings
from .auth import UserTable