feat: switch to pydantic types for api

This commit is contained in:
Benexl
2025-07-14 23:00:20 +03:00
parent 273dd56680
commit b6dd965e49
3 changed files with 58 additions and 168 deletions

View File

@@ -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."""