""" media_resolver.py ================= Abstract base class for tracker-specific manga metadata resolvers. Concrete implementations (MALResolver, AniListResolver) must implement every abstract method, ensuring a uniform interface regardless of the underlying data source (Jikan/MAL, AniList GraphQL, …). """ from __future__ import annotations from abc import ABC, abstractmethod class MediaResolver(ABC): """ Abstract base for tracker-specific manga metadata resolvers. Subclasses connect to a specific tracker API and expose a common interface for: - Searching a manga by title → tracker-specific numeric ID - Fetching summary statistics (score, rank, popularity, …) - Listing characters and staff (name-only and detailed forms) - Fetching full details for a single character or person Methods that accept a tracker ID treat None as "unknown" and return a safe empty value rather than raising. """ @abstractmethod def find_id(self, title: str) -> "int | None": """ Searches the tracker for a manga by title. Returns the best-matching tracker ID, or None on failure. """ @abstractmethod def get_stats(self, tracker_id: "int | None") -> "dict | None": """ Returns a statistics dict for the given tracker ID: {score, rank, scored_by, popularity, members, favorites, url, title, as_of (DD-MM-YYYY)} Returns None if tracker_id is None or on network failure. """ @abstractmethod def get_characters(self, tracker_id: "int | None") -> "list[str]": """ Returns a flat list of character name strings for the manga. Used to populate the ComicInfo XML element. """ @abstractmethod def get_characters_detailed(self, tracker_id: "int | None") -> "list[dict]": """ Returns detailed character entries for a manga: [{id, name, image_url, role, about=None, ...}, ...] 'about' is not populated here; call get_character_details() lazily. """ @abstractmethod def get_staff_detailed(self, tracker_id: "int | None") -> "list[dict]": """ Returns detailed staff/author entries for a manga: [{id, name, image_url, positions, about=None, ...}, ...] 'about' is not populated here; call get_person_details() lazily. """ @abstractmethod def get_character_details(self, char_id: "int | None") -> "dict | None": """ Returns full details for a single character, including description. Implementations should cache the result. """ @abstractmethod def get_person_details(self, person_id: "int | None") -> "dict | None": """ Returns full details for a single person (staff), including description. Implementations should cache the result. """ @abstractmethod def clear_cache(self) -> None: """Clears all internal caches."""