Files
ohm_streaming/app/models/watchlist.py
T
root 29c7040b20
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
feat: migrate persistence from JSON to SQLModel (Phase 1)
- Integrated SQLModel with SQLite for robust data persistence
- Refactored UserManager and WatchlistManager to use SQL queries
- Migrated models to SQLModel with relationships and primary keys
- Updated test suite with in-memory database isolation
- Removed deprecated JSON storage files
2026-03-24 10:40:36 +00:00

158 lines
5.0 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
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 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)