mirror of
https://github.com/Benexl/FastAnime.git
synced 2026-01-10 20:23:32 -08:00
feat: switch to pydantic types for api
This commit is contained in:
@@ -6,17 +6,17 @@ Provides comprehensive data structures for tracking and managing local watch his
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...libs.api.types import MediaItem
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class WatchHistoryEntry:
|
||||
class WatchHistoryEntry(BaseModel):
|
||||
"""
|
||||
Represents a single entry in the watch history.
|
||||
Contains media information and viewing progress.
|
||||
@@ -26,104 +26,16 @@ class WatchHistoryEntry:
|
||||
last_watched_episode: int = 0
|
||||
watch_progress: float = 0.0 # Progress within the episode (0.0-1.0)
|
||||
times_watched: int = 1
|
||||
first_watched: datetime = field(default_factory=datetime.now)
|
||||
last_watched: datetime = field(default_factory=datetime.now)
|
||||
first_watched: datetime = Field(default_factory=datetime.now)
|
||||
last_watched: datetime = Field(default_factory=datetime.now)
|
||||
status: str = "watching" # watching, completed, dropped, paused
|
||||
notes: str = ""
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert entry to dictionary for JSON serialization."""
|
||||
return {
|
||||
"media_item": {
|
||||
"id": self.media_item.id,
|
||||
"id_mal": self.media_item.id_mal,
|
||||
"type": self.media_item.type,
|
||||
"title": {
|
||||
"romaji": self.media_item.title.romaji,
|
||||
"english": self.media_item.title.english,
|
||||
"native": self.media_item.title.native,
|
||||
},
|
||||
"status": self.media_item.status,
|
||||
"format": self.media_item.format,
|
||||
"cover_image": {
|
||||
"large": self.media_item.cover_image.large if self.media_item.cover_image else None,
|
||||
"medium": self.media_item.cover_image.medium if self.media_item.cover_image else None,
|
||||
} if self.media_item.cover_image else None,
|
||||
"banner_image": self.media_item.banner_image,
|
||||
"description": self.media_item.description,
|
||||
"episodes": self.media_item.episodes,
|
||||
"duration": self.media_item.duration,
|
||||
"genres": self.media_item.genres,
|
||||
"synonyms": self.media_item.synonyms,
|
||||
"average_score": self.media_item.average_score,
|
||||
"popularity": self.media_item.popularity,
|
||||
"favourites": self.media_item.favourites,
|
||||
},
|
||||
"last_watched_episode": self.last_watched_episode,
|
||||
"watch_progress": self.watch_progress,
|
||||
"times_watched": self.times_watched,
|
||||
"first_watched": self.first_watched.isoformat(),
|
||||
"last_watched": self.last_watched.isoformat(),
|
||||
"status": self.status,
|
||||
"notes": self.notes,
|
||||
}
|
||||
# With Pydantic, serialization is automatic!
|
||||
# No need for manual to_dict() and from_dict() methods
|
||||
# Use: entry.model_dump() and WatchHistoryEntry.model_validate(data)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "WatchHistoryEntry":
|
||||
"""Create entry from dictionary."""
|
||||
from ...libs.api.types import MediaImage, MediaTitle
|
||||
|
||||
media_data = data["media_item"]
|
||||
|
||||
# Reconstruct MediaTitle
|
||||
title_data = media_data.get("title", {})
|
||||
title = MediaTitle(
|
||||
romaji=title_data.get("romaji"),
|
||||
english=title_data.get("english"),
|
||||
native=title_data.get("native"),
|
||||
)
|
||||
|
||||
# Reconstruct MediaImage if present
|
||||
cover_data = media_data.get("cover_image")
|
||||
cover_image = None
|
||||
if cover_data:
|
||||
cover_image = MediaImage(
|
||||
large=cover_data.get("large", ""),
|
||||
medium=cover_data.get("medium"),
|
||||
)
|
||||
|
||||
# Reconstruct MediaItem
|
||||
media_item = MediaItem(
|
||||
id=media_data["id"],
|
||||
id_mal=media_data.get("id_mal"),
|
||||
type=media_data.get("type", "ANIME"),
|
||||
title=title,
|
||||
status=media_data.get("status"),
|
||||
format=media_data.get("format"),
|
||||
cover_image=cover_image,
|
||||
banner_image=media_data.get("banner_image"),
|
||||
description=media_data.get("description"),
|
||||
episodes=media_data.get("episodes"),
|
||||
duration=media_data.get("duration"),
|
||||
genres=media_data.get("genres", []),
|
||||
synonyms=media_data.get("synonyms", []),
|
||||
average_score=media_data.get("average_score"),
|
||||
popularity=media_data.get("popularity"),
|
||||
favourites=media_data.get("favourites"),
|
||||
)
|
||||
|
||||
return cls(
|
||||
media_item=media_item,
|
||||
last_watched_episode=data.get("last_watched_episode", 0),
|
||||
watch_progress=data.get("watch_progress", 0.0),
|
||||
times_watched=data.get("times_watched", 1),
|
||||
first_watched=datetime.fromisoformat(data.get("first_watched", datetime.now().isoformat())),
|
||||
last_watched=datetime.fromisoformat(data.get("last_watched", datetime.now().isoformat())),
|
||||
status=data.get("status", "watching"),
|
||||
notes=data.get("notes", ""),
|
||||
)
|
||||
|
||||
def update_progress(self, episode: int, progress: float = 0.0, status: str = None):
|
||||
def update_progress(self, episode: int, progress: float = 0.0, status: Optional[str] = None):
|
||||
"""Update watch progress for this entry."""
|
||||
self.last_watched_episode = max(self.last_watched_episode, episode)
|
||||
self.watch_progress = progress
|
||||
@@ -169,41 +81,16 @@ class WatchHistoryEntry:
|
||||
return status_emojis.get(self.status, "❓")
|
||||
|
||||
|
||||
@dataclass
|
||||
class WatchHistoryData:
|
||||
class WatchHistoryData(BaseModel):
|
||||
"""Complete watch history data container."""
|
||||
|
||||
entries: Dict[int, WatchHistoryEntry] = field(default_factory=dict)
|
||||
last_updated: datetime = field(default_factory=datetime.now)
|
||||
entries: Dict[int, WatchHistoryEntry] = Field(default_factory=dict)
|
||||
last_updated: datetime = Field(default_factory=datetime.now)
|
||||
format_version: str = "1.0"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
"entries": {str(k): v.to_dict() for k, v in self.entries.items()},
|
||||
"last_updated": self.last_updated.isoformat(),
|
||||
"format_version": self.format_version,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "WatchHistoryData":
|
||||
"""Create from dictionary."""
|
||||
entries = {}
|
||||
entries_data = data.get("entries", {})
|
||||
|
||||
for media_id_str, entry_data in entries_data.items():
|
||||
try:
|
||||
media_id = int(media_id_str)
|
||||
entry = WatchHistoryEntry.from_dict(entry_data)
|
||||
entries[media_id] = entry
|
||||
except (ValueError, KeyError) as e:
|
||||
logger.warning(f"Skipping invalid watch history entry {media_id_str}: {e}")
|
||||
|
||||
return cls(
|
||||
entries=entries,
|
||||
last_updated=datetime.fromisoformat(data.get("last_updated", datetime.now().isoformat())),
|
||||
format_version=data.get("format_version", "1.0"),
|
||||
)
|
||||
# With Pydantic, serialization is automatic!
|
||||
# No need for manual to_dict() and from_dict() methods
|
||||
# Use: data.model_dump() and WatchHistoryData.model_validate(data)
|
||||
|
||||
def add_or_update_entry(self, media_item: MediaItem, episode: int = 0, progress: float = 0.0, status: str = "watching") -> WatchHistoryEntry:
|
||||
"""Add or update a watch history entry."""
|
||||
|
||||
Reference in New Issue
Block a user