From 2bd02c7e9948674f1bce653b9f6b37f705bcf0f2 Mon Sep 17 00:00:00 2001 From: Benexl Date: Sun, 6 Jul 2025 14:15:13 +0300 Subject: [PATCH] feat: mass refactor --- fastanime/cli/config/model.py | 18 +-- fastanime/cli/options.py | 6 +- fastanime/{libs => cli}/selectors/__init__.py | 0 fastanime/{libs => cli}/selectors/base.py | 0 .../{libs => cli}/selectors/fzf/__init__.py | 0 .../selectors/fzf/scripts/search.sh | 0 .../{libs => cli}/selectors/fzf/selector.py | 0 fastanime/cli/selectors/inquirer/__init__.py | 0 fastanime/cli/selectors/inquirer/selector.py | 0 .../{libs => cli}/selectors/rofi/__init__.py | 0 .../{libs => cli}/selectors/rofi/selector.py | 0 fastanime/cli/selectors/selector.py | 0 fastanime/libs/players/__init__.py | 0 fastanime/libs/players/base.py | 0 fastanime/libs/players/mpv/__init__.py | 0 .../mpv.py => libs/players/mpv/player.py} | 0 .../{cli/utils => libs/players}/player.py | 0 fastanime/libs/players/syncplay/__init__.py | 0 .../players/syncplay/player.py} | 0 fastanime/libs/players/vlc/__init__.py | 0 fastanime/libs/players/vlc/player.py | 0 .../libs/providers/anime/allanime/__init__.py | 1 + .../anime/allanime/{api.py => provider.py} | 13 +- .../anime/animepahe/{api.py => provider.py} | 0 fastanime/libs/providers/anime/base.py | 63 ++------ .../anime/hianime/{api.py => provider.py} | 0 .../anime/nyaa/{api.py => provider.py} | 0 fastanime/libs/providers/anime/params.py | 43 ++++++ fastanime/libs/providers/anime/provider.py | 139 ++++++------------ .../anime/yugen/{api.py => provider.py} | 0 30 files changed, 120 insertions(+), 163 deletions(-) rename fastanime/{libs => cli}/selectors/__init__.py (100%) rename fastanime/{libs => cli}/selectors/base.py (100%) rename fastanime/{libs => cli}/selectors/fzf/__init__.py (100%) rename fastanime/{libs => cli}/selectors/fzf/scripts/search.sh (100%) rename fastanime/{libs => cli}/selectors/fzf/selector.py (100%) create mode 100644 fastanime/cli/selectors/inquirer/__init__.py create mode 100644 fastanime/cli/selectors/inquirer/selector.py rename fastanime/{libs => cli}/selectors/rofi/__init__.py (100%) rename fastanime/{libs => cli}/selectors/rofi/selector.py (100%) create mode 100644 fastanime/cli/selectors/selector.py create mode 100644 fastanime/libs/players/__init__.py create mode 100644 fastanime/libs/players/base.py create mode 100644 fastanime/libs/players/mpv/__init__.py rename fastanime/{cli/utils/mpv.py => libs/players/mpv/player.py} (100%) rename fastanime/{cli/utils => libs/players}/player.py (100%) create mode 100644 fastanime/libs/players/syncplay/__init__.py rename fastanime/{cli/utils/syncplay.py => libs/players/syncplay/player.py} (100%) create mode 100644 fastanime/libs/players/vlc/__init__.py create mode 100644 fastanime/libs/players/vlc/player.py rename fastanime/libs/providers/anime/allanime/{api.py => provider.py} (92%) rename fastanime/libs/providers/anime/animepahe/{api.py => provider.py} (100%) rename fastanime/libs/providers/anime/hianime/{api.py => provider.py} (100%) rename fastanime/libs/providers/anime/nyaa/{api.py => provider.py} (100%) create mode 100644 fastanime/libs/providers/anime/params.py rename fastanime/libs/providers/anime/yugen/{api.py => provider.py} (100%) diff --git a/fastanime/cli/config/model.py b/fastanime/cli/config/model.py index 79e82a7..e68b246 100644 --- a/fastanime/cli/config/model.py +++ b/fastanime/cli/config/model.py @@ -12,15 +12,15 @@ from ...core.constants import ( ROFI_THEME_PREVIEW, ) from ...libs.anilist.constants import SORTS_AVAILABLE -from ...libs.anime_provider import PROVIDERS_AVAILABLE, SERVERS_AVAILABLE +from ...libs.providers.anime import PROVIDERS_AVAILABLE, SERVERS_AVAILABLE from ..constants import APP_ASCII_ART, USER_VIDEOS_DIR -class External(BaseModel): +class OtherConfig(BaseModel): pass -class FzfConfig(External): +class FzfConfig(OtherConfig): """Configuration specific to the FZF selector.""" opts: str = Field( @@ -48,7 +48,7 @@ class FzfConfig(External): ) -class RofiConfig(External): +class RofiConfig(OtherConfig): """Configuration specific to the Rofi selector.""" theme_main: Path = Field( @@ -69,7 +69,7 @@ class RofiConfig(External): ) -class MpvConfig(External): +class MpvConfig(OtherConfig): """Configuration specific to the MPV player integration.""" args: str = Field( @@ -92,7 +92,7 @@ class MpvConfig(External): ) -class AnilistConfig(External): +class AnilistConfig(OtherConfig): """Configuration for interacting with the AniList API.""" per_page: int = Field( @@ -182,10 +182,10 @@ class GeneralConfig(BaseModel): @field_validator("provider") @classmethod - def validate_server(cls, v: str) -> str: - if v.lower() != "top" and v not in PROVIDERS_AVAILABLE: + def validate_provider(cls, v: str) -> str: + if v not in PROVIDERS_AVAILABLE: raise ValueError( - f"'{v}' is not a valid server. Must be 'top' or one of: {PROVIDERS_AVAILABLE}" + f"'{v}' is not a valid provider. Must be one of: {PROVIDERS_AVAILABLE}" ) return v diff --git a/fastanime/cli/options.py b/fastanime/cli/options.py index ea860da..975ee05 100644 --- a/fastanime/cli/options.py +++ b/fastanime/cli/options.py @@ -1,13 +1,13 @@ from collections.abc import Callable from pathlib import Path -from typing import Any, Literal, get_origin, get_args +from typing import Any, Literal, get_args, get_origin import click from pydantic import BaseModel from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined -from .config.model import External +from .config.model import OtherConfig # Mapping from Python/Pydantic types to Click types TYPE_MAP = { @@ -50,7 +50,7 @@ def options_from_model(model: type[BaseModel], parent_name: str = "") -> Callabl decorators = [] # Check if this model inherits from ExternalTool - is_external_tool = issubclass(model, External) + is_external_tool = issubclass(model, OtherConfig) model_name = model.__name__.lower().replace("config", "") # Introspect the model's fields diff --git a/fastanime/libs/selectors/__init__.py b/fastanime/cli/selectors/__init__.py similarity index 100% rename from fastanime/libs/selectors/__init__.py rename to fastanime/cli/selectors/__init__.py diff --git a/fastanime/libs/selectors/base.py b/fastanime/cli/selectors/base.py similarity index 100% rename from fastanime/libs/selectors/base.py rename to fastanime/cli/selectors/base.py diff --git a/fastanime/libs/selectors/fzf/__init__.py b/fastanime/cli/selectors/fzf/__init__.py similarity index 100% rename from fastanime/libs/selectors/fzf/__init__.py rename to fastanime/cli/selectors/fzf/__init__.py diff --git a/fastanime/libs/selectors/fzf/scripts/search.sh b/fastanime/cli/selectors/fzf/scripts/search.sh similarity index 100% rename from fastanime/libs/selectors/fzf/scripts/search.sh rename to fastanime/cli/selectors/fzf/scripts/search.sh diff --git a/fastanime/libs/selectors/fzf/selector.py b/fastanime/cli/selectors/fzf/selector.py similarity index 100% rename from fastanime/libs/selectors/fzf/selector.py rename to fastanime/cli/selectors/fzf/selector.py diff --git a/fastanime/cli/selectors/inquirer/__init__.py b/fastanime/cli/selectors/inquirer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fastanime/cli/selectors/inquirer/selector.py b/fastanime/cli/selectors/inquirer/selector.py new file mode 100644 index 0000000..e69de29 diff --git a/fastanime/libs/selectors/rofi/__init__.py b/fastanime/cli/selectors/rofi/__init__.py similarity index 100% rename from fastanime/libs/selectors/rofi/__init__.py rename to fastanime/cli/selectors/rofi/__init__.py diff --git a/fastanime/libs/selectors/rofi/selector.py b/fastanime/cli/selectors/rofi/selector.py similarity index 100% rename from fastanime/libs/selectors/rofi/selector.py rename to fastanime/cli/selectors/rofi/selector.py diff --git a/fastanime/cli/selectors/selector.py b/fastanime/cli/selectors/selector.py new file mode 100644 index 0000000..e69de29 diff --git a/fastanime/libs/players/__init__.py b/fastanime/libs/players/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fastanime/libs/players/base.py b/fastanime/libs/players/base.py new file mode 100644 index 0000000..e69de29 diff --git a/fastanime/libs/players/mpv/__init__.py b/fastanime/libs/players/mpv/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fastanime/cli/utils/mpv.py b/fastanime/libs/players/mpv/player.py similarity index 100% rename from fastanime/cli/utils/mpv.py rename to fastanime/libs/players/mpv/player.py diff --git a/fastanime/cli/utils/player.py b/fastanime/libs/players/player.py similarity index 100% rename from fastanime/cli/utils/player.py rename to fastanime/libs/players/player.py diff --git a/fastanime/libs/players/syncplay/__init__.py b/fastanime/libs/players/syncplay/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fastanime/cli/utils/syncplay.py b/fastanime/libs/players/syncplay/player.py similarity index 100% rename from fastanime/cli/utils/syncplay.py rename to fastanime/libs/players/syncplay/player.py diff --git a/fastanime/libs/players/vlc/__init__.py b/fastanime/libs/players/vlc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fastanime/libs/players/vlc/player.py b/fastanime/libs/players/vlc/player.py new file mode 100644 index 0000000..e69de29 diff --git a/fastanime/libs/providers/anime/allanime/__init__.py b/fastanime/libs/providers/anime/allanime/__init__.py index e69de29..8b13789 100644 --- a/fastanime/libs/providers/anime/allanime/__init__.py +++ b/fastanime/libs/providers/anime/allanime/__init__.py @@ -0,0 +1 @@ + diff --git a/fastanime/libs/providers/anime/allanime/api.py b/fastanime/libs/providers/anime/allanime/provider.py similarity index 92% rename from fastanime/libs/providers/anime/allanime/api.py rename to fastanime/libs/providers/anime/allanime/provider.py index 4258ab2..23a86e2 100644 --- a/fastanime/libs/providers/anime/allanime/api.py +++ b/fastanime/libs/providers/anime/allanime/provider.py @@ -1,12 +1,7 @@ import logging from typing import TYPE_CHECKING -from fastanime.libs.anime_provider.allanime.parser import ( - map_to_anime_result, - map_to_search_results, -) - -from ....core.utils.graphql import execute_graphql_query +from .....core.utils.graphql import execute_graphql_query from ..base import AnimeProvider from ..utils.decorators import debug_provider from .constants import ( @@ -18,6 +13,10 @@ from .constants import ( SEARCH_GQL, ) from .extractors import extract_server +from .parser import ( + map_to_anime_result, + map_to_search_results, +) if TYPE_CHECKING: from .types import AllAnimeEpisode @@ -25,7 +24,7 @@ logger = logging.getLogger(__name__) class AllAnime(AnimeProvider): - DEFAULT_HEADERS = {"Referer": API_GRAPHQL_REFERER} + HEADERS = {"Referer": API_GRAPHQL_REFERER} @debug_provider def search_for_anime(self, params): diff --git a/fastanime/libs/providers/anime/animepahe/api.py b/fastanime/libs/providers/anime/animepahe/provider.py similarity index 100% rename from fastanime/libs/providers/anime/animepahe/api.py rename to fastanime/libs/providers/anime/animepahe/provider.py diff --git a/fastanime/libs/providers/anime/base.py b/fastanime/libs/providers/anime/base.py index af698a2..2c1a9fb 100644 --- a/fastanime/libs/providers/anime/base.py +++ b/fastanime/libs/providers/anime/base.py @@ -1,8 +1,9 @@ from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, ClassVar, Dict -from httpx import AsyncClient, Client +from httpx import Client + +from .params import AnimeParams, EpisodeStreamsParams, SearchParams if TYPE_CHECKING: from collections.abc import Iterator @@ -10,61 +11,29 @@ if TYPE_CHECKING: from .types import Anime, SearchResults, Server -@dataclass -class SearchParams: - """Parameters for searching anime.""" - - query: str - - # pagination and sorting - current_page: int = 1 - page_limit: int = 20 - sort_by: str = "relevance" - order: Literal["asc", "desc"] = "desc" - - # filters - translation_type: Literal["sub", "dub"] = "sub" - genre: str | None = None - year: int | None = None - status: str | None = None - allow_nsfw: bool = True - allow_unknown: bool = True - country_of_origin: str | None = None - - -@dataclass -class EpisodeStreamsParams: - """Parameters for fetching episode streams.""" - - anime_id: str - episode: str - translation_type: Literal["sub", "dub"] = "sub" - server: str | None = None - quality: Literal["1080", "720", "480", "360"] = "720" - subtitles: bool = True - - -@dataclass -class AnimeParams: - """Parameters for fetching anime details.""" - - anime_id: str - - class AnimeProvider(ABC): + HEADERS: ClassVar[Dict[str, str]] + + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + if not hasattr(cls, "HEADERS"): + raise TypeError( + f"Subclasses of AnimeProvider must define a 'HEADERS' class attribute." + ) + def __init__(self, client: Client) -> None: self.client = client @abstractmethod - def search_for_anime(self, params: SearchParams) -> "SearchResults | None": + def search(self, params: SearchParams) -> "SearchResults | None": pass @abstractmethod - def get_anime(self, params: AnimeParams) -> "Anime | None": + def get(self, params: AnimeParams) -> "Anime | None": pass @abstractmethod - def get_episode_streams( + def episode_streams( self, params: EpisodeStreamsParams ) -> "Iterator[Server] | None": pass diff --git a/fastanime/libs/providers/anime/hianime/api.py b/fastanime/libs/providers/anime/hianime/provider.py similarity index 100% rename from fastanime/libs/providers/anime/hianime/api.py rename to fastanime/libs/providers/anime/hianime/provider.py diff --git a/fastanime/libs/providers/anime/nyaa/api.py b/fastanime/libs/providers/anime/nyaa/provider.py similarity index 100% rename from fastanime/libs/providers/anime/nyaa/api.py rename to fastanime/libs/providers/anime/nyaa/provider.py diff --git a/fastanime/libs/providers/anime/params.py b/fastanime/libs/providers/anime/params.py new file mode 100644 index 0000000..ea0dd5d --- /dev/null +++ b/fastanime/libs/providers/anime/params.py @@ -0,0 +1,43 @@ +from dataclasses import dataclass +from typing import Literal + + +@dataclass +class SearchParams: + """Parameters for searching anime.""" + + query: str + + # pagination and sorting + current_page: int = 1 + page_limit: int = 20 + sort_by: str = "relevance" + order: Literal["asc", "desc"] = "desc" + + # filters + translation_type: Literal["sub", "dub"] = "sub" + genre: str | None = None + year: int | None = None + status: str | None = None + allow_nsfw: bool = True + allow_unknown: bool = True + country_of_origin: str | None = None + + +@dataclass +class EpisodeStreamsParams: + """Parameters for fetching episode streams.""" + + anime_id: str + episode: str + translation_type: Literal["sub", "dub"] = "sub" + server: str | None = None + quality: Literal["1080", "720", "480", "360"] = "720" + subtitles: bool = True + + +@dataclass +class AnimeParams: + """Parameters for fetching anime details.""" + + anime_id: str diff --git a/fastanime/libs/providers/anime/provider.py b/fastanime/libs/providers/anime/provider.py index 99c7719..0086c54 100644 --- a/fastanime/libs/providers/anime/provider.py +++ b/fastanime/libs/providers/anime/provider.py @@ -1,141 +1,86 @@ -"""An abstraction over all providers offering added features with a simple and well typed api""" - import importlib import logging -import os from typing import TYPE_CHECKING +from yt_dlp.utils.networking import random_user_agent + from .allanime.constants import SERVERS_AVAILABLE as ALLANIME_SERVERS from .animepahe.constants import SERVERS_AVAILABLE as ANIMEPAHE_SERVERS +from .base import AnimeProvider as Base from .hianime.constants import SERVERS_AVAILABLE as HIANIME_SERVERS -from httpx import Client, AsyncClient -from yt_dlp.utils.networking import random_user_agent +from .params import AnimeParams, EpisodeStreamsParams, SearchParams if TYPE_CHECKING: from collections.abc import Iterator + from httpx import AsyncClient, Client + from .types import Anime, SearchResults, Server logger = logging.getLogger(__name__) PROVIDERS_AVAILABLE = { - "allanime": "api.AllAnime", - "animepahe": "api.AnimePahe", - "hianime": "api.HiAnime", - "nyaa": "api.Nyaa", - "yugen": "api.Yugen", + "allanime": "provider.AllAnime", + "animepahe": "provider.AnimePahe", + "hianime": "provider.HiAnime", + "nyaa": "provider.Nyaa", + "yugen": "provider.Yugen", } -SERVERS_AVAILABLE = ["top", *ALLANIME_SERVERS, *ANIMEPAHE_SERVERS, *HIANIME_SERVERS] +SERVERS_AVAILABLE = ["TOP", *ALLANIME_SERVERS, *ANIMEPAHE_SERVERS, *HIANIME_SERVERS] class AnimeProvider: """An abstraction over all anime providers""" PROVIDERS = list(PROVIDERS_AVAILABLE.keys()) - provider = PROVIDERS[0] + current_provider_name = PROVIDERS[0] + current_provider: Base def __init__( self, - provider, - cache_requests=os.environ.get("FASTANIME_CACHE_REQUESTS", "false"), - use_persistent_provider_store=os.environ.get( - "FASTANIME_USE_PERSISTENT_PROVIDER_STORE", "false" - ), + provider: str, + cache_requests=False, + use_persistent_provider_store=False, dynamic=False, retries=0, ) -> None: - self.provider = provider + self.current_provider_name = provider self.dynamic = dynamic self.retries = retries self.cache_requests = cache_requests self.use_persistent_provider_store = use_persistent_provider_store - self.lazyload_provider(self.provider) + self.lazyload(self.current_provider_name) - def setup_httpx_client(self) -> Client: + def search(self, params: SearchParams) -> "SearchResults | None": + results = self.current_provider.search(params) + + return results + + def get(self, params: AnimeParams) -> "Anime | None": + results = self.current_provider.get(params) + + return results + + def episode_streams( + self, params: EpisodeStreamsParams + ) -> "Iterator[Server] | None": + results = self.current_provider.episode_streams(params) + return results + + def setup_httpx_client(self, headers) -> "Client": """Sets up a httpx client with a random user agent""" - client = Client(headers={"User-Agent": random_user_agent()}) + client = Client(headers={"User-Agent": random_user_agent(), **headers}) return client - def setup_httpx_async_client(self) -> AsyncClient: + def setup_httpx_async_client(self) -> "AsyncClient": """Sets up a httpx client with a random user agent""" client = AsyncClient(headers={"User-Agent": random_user_agent()}) return client - def lazyload_provider(self, provider): - """updates the current provider being used""" - try: - self.anime_provider.session.kill_connection_to_db() - except Exception: - pass + def lazyload(self, provider): _, anime_provider_cls_name = PROVIDERS_AVAILABLE[provider].split(".", 1) - package = f"fastanime.libs.anime_provider.{provider}" + package = f"fastanime.libs.providers.anime.{provider}" provider_api = importlib.import_module(".api", package) anime_provider = getattr(provider_api, anime_provider_cls_name) - self.anime_provider = anime_provider( - self.cache_requests, self.use_persistent_provider_store - ) - - def search_for_anime( - self, search_keywords, translation_type, **kwargs - ) -> "SearchResults | None": - """core abstraction over all providers search functionality - - Args: - user_query ([TODO:parameter]): [TODO:description] - translation_type ([TODO:parameter]): [TODO:description] - nsfw ([TODO:parameter]): [TODO:description] - unknown ([TODO:parameter]): [TODO:description] - anilist_obj: [TODO:description] - - Returns: - [TODO:return] - """ - anime_provider = self.anime_provider - results = anime_provider.search_for_anime( - search_keywords, translation_type, **kwargs - ) - - return results - - def get_anime( - self, - anime_id: str, - **kwargs, - ) -> "Anime | None": - """core abstraction over getting info of an anime from all providers - - Args: - anime_id: [TODO:description] - anilist_obj: [TODO:description] - - Returns: - [TODO:return] - """ - anime_provider = self.anime_provider - results = anime_provider.get_anime(anime_id, **kwargs) - - return results - - def get_episode_streams( - self, - anime_id, - episode: str, - translation_type: str, - **kwargs, - ) -> "Iterator[Server] | None": - """core abstractions for getting juicy streams from all providers - - Args: - anime ([TODO:parameter]): [TODO:description] - episode: [TODO:description] - translation_type: [TODO:description] - anilist_obj: [TODO:description] - - Returns: - [TODO:return] - """ - anime_provider = self.anime_provider - results = anime_provider.get_episode_streams( - anime_id, episode, translation_type, **kwargs - ) - return results + client = self.setup_httpx_client(anime_provider.HEADERS) + self.current_provider = anime_provider(client) diff --git a/fastanime/libs/providers/anime/yugen/api.py b/fastanime/libs/providers/anime/yugen/provider.py similarity index 100% rename from fastanime/libs/providers/anime/yugen/api.py rename to fastanime/libs/providers/anime/yugen/provider.py