29c7040b20
- 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
158 lines
5.0 KiB
Python
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)
|