From bf5592f931677ef9f571774dae81738ffda78c9a Mon Sep 17 00:00:00 2001 From: Ananas Date: Sat, 23 Nov 2024 22:27:52 +0100 Subject: [PATCH 01/21] feat: manga, fullmanga, manga stats --- anmoku/resources/__init__.py | 1 + anmoku/resources/manga/__init__.py | 3 + anmoku/resources/manga/character.py | 54 +++++++++++++ anmoku/resources/manga/manga.py | 103 ++++++++++++++++++++++++ anmoku/resources/manga/statistics.py | 60 ++++++++++++++ anmoku/typing/jikan/__init__.py | 3 +- anmoku/typing/jikan/character.py | 3 +- anmoku/typing/jikan/manga/__init__.py | 3 + anmoku/typing/jikan/manga/characters.py | 13 +++ anmoku/typing/jikan/manga/manga.py | 70 ++++++++++++++++ anmoku/typing/jikan/manga/statistics.py | 20 +++++ anmoku/typing/jikan/person.py | 6 ++ 12 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 anmoku/resources/manga/__init__.py create mode 100644 anmoku/resources/manga/character.py create mode 100644 anmoku/resources/manga/manga.py create mode 100644 anmoku/resources/manga/statistics.py create mode 100644 anmoku/typing/jikan/manga/__init__.py create mode 100644 anmoku/typing/jikan/manga/characters.py create mode 100644 anmoku/typing/jikan/manga/manga.py create mode 100644 anmoku/typing/jikan/manga/statistics.py diff --git a/anmoku/resources/__init__.py b/anmoku/resources/__init__.py index dd82c91..4527931 100644 --- a/anmoku/resources/__init__.py +++ b/anmoku/resources/__init__.py @@ -1,3 +1,4 @@ from .base import * from .anime import * +from .manga import * from .character import * \ No newline at end of file diff --git a/anmoku/resources/manga/__init__.py b/anmoku/resources/manga/__init__.py new file mode 100644 index 0000000..ecdc88e --- /dev/null +++ b/anmoku/resources/manga/__init__.py @@ -0,0 +1,3 @@ +from .manga import * +from .character import * +from .statistics import * \ No newline at end of file diff --git a/anmoku/resources/manga/character.py b/anmoku/resources/manga/character.py new file mode 100644 index 0000000..bc6a960 --- /dev/null +++ b/anmoku/resources/manga/character.py @@ -0,0 +1,54 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, List + +if TYPE_CHECKING: + from ...typing.jikan.manga.characters import MangaCharacterData + from ...typing.jikan.api import JikanResponseData + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers.image import Image + +__all__ = ( + "MangaCharacters", +) + + +@dataclass +class MangaCharacter(): + data: MangaCharacterData = field(repr = False) + + id: int = field(init = False) + """The MyAnimeList ID of this character.""" + name: str = field(init = False) + """The name of this character.""" + url: str = field(init = False) + """The MyAnimeList URL to this character.""" + image: Image = field(init = False) + """The image of this character.""" + + role: str = field(init = False) + """The character's role.""" + + def __post_init__(self): + character = self.data["character"] + + self.id = character["mal_id"] + self.name = character["name"] + self.url = character["url"] + self.image = Image(character["images"]) + + self.role = self.data["role"] + +@dataclass +class MangaCharacters(JikanResource): + """Get data of the characters from a particular manga.""" + _get_endpoint = "/manga/{id}/characters" + + data: JikanResponseData[List[MangaCharacterData]] + + def __iter__(self): + + for character in self.data["data"]: + yield MangaCharacter(character) \ No newline at end of file diff --git a/anmoku/resources/manga/manga.py b/anmoku/resources/manga/manga.py new file mode 100644 index 0000000..df0fc8e --- /dev/null +++ b/anmoku/resources/manga/manga.py @@ -0,0 +1,103 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, List + +if TYPE_CHECKING: + from ...typing.jikan.manga.manga import MangaStatus, RelationData, ExternalSourceData + from ...typing.jikan import ( + MangaData, + FullMangaData, + JikanResponseData, + ) + +from dataclasses import dataclass, field +from enum import Enum + +from ..base import JikanResource +from ..helpers import Title, Image, DateRange + +__all__ = ( + "Manga", + "FullManga" +) + + +class PublishingStatus(Enum): + FINISHED = "Finished" + PUBLISHING = "Publishing" + DISCONTINUED = "Discontinued" + + def __init__(self, value: MangaStatus) -> None: + ... + +@dataclass +class Manga(JikanResource): + _get_endpoint = "/manga/{id}" + _search_endpoint = "/manga" + + data: JikanResponseData[MangaData] = field(repr = False) + + id: int = field(init = False) + """The MyAnimeList ID of the manga.""" + url: str = field(init = False) + """The MyAnimeList URL to this manga.""" + image: Image = field(init = False) + """The banner image of the manga.""" + approved: bool = field(init = False) + """Whether the entry is pending approval on MAL or not.""" + title: Title = field(init = False) + """The manga's title.""" + name: Title = field(init = False) + """Alias to ``Manga.title``.""" + chapters: int = field(init = False, default = 0) + """Chapter count of the manga.""" + volumes: int = field(init = False, default = 0) + """Volumes count of the manga.""" + status: PublishingStatus = field(init = False, default = PublishingStatus.PUBLISHING) + """The airing status of the manga.""" + published: DateRange = field(init = False) + """To when and from this manga was aired.""" + score: float = field(init = False, default = 0.0) + """The manga's score rating.""" + scored_by: int = field(init = False, default = 0) + """Number of users that scored the manga.""" + + def __post_init__(self): + manga = self.data["data"] + + self.id = manga["mal_id"] + self.url = manga["url"] + self.image = Image(manga["images"]) + self.approved = manga["approved"] + self.title = Title(manga["titles"]) + self.name = self.title + + self.chapters = manga.get("chapters") + self.volumes = manga.get("volumes") + + status = manga.get("status") + + if status is not None: + self.status = PublishingStatus(status) + + self.published = DateRange(manga["published"]) + self.score = manga["score"] + self.scored_by = manga["scored_by"] + + +@dataclass +class FullManga(Manga): + _get_endpoint = "/manga/{id}/full" + + data: JikanResponseData[FullMangaData] = field(repr=False) + + title_synonyms: List[str] = field(init = False) + relations: RelationData = field(init = False) + external: List[ExternalSourceData] = field(init = False) + + def __post_init__(self): + super().__post_init__() + manga = self.data["data"] + + self.title_synonyms = manga["title_synonyms"] + self.relations = manga["relations"] + self.external = manga["external"] \ No newline at end of file diff --git a/anmoku/resources/manga/statistics.py b/anmoku/resources/manga/statistics.py new file mode 100644 index 0000000..3f43201 --- /dev/null +++ b/anmoku/resources/manga/statistics.py @@ -0,0 +1,60 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, List + +if TYPE_CHECKING: + from ...typing.jikan.manga.statistics import ScoresData + from ...typing.jikan import ( + MangaStatisticsData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource + +__all__ = ( + "MangaStatistics", +) + +@dataclass +class Scores: + data: ScoresData = field(repr=False) + + score: int = field(init = False) + votes: int = field(init = False) + percentage: int = field(init = False) + + def __post_init__(self): + self.score = self.data["score"] + self.votes = self.data["votes"] + self.percentage = self.data["percentage"] + +@dataclass +class MangaStatistics(JikanResource): + _get_endpoint = "/manga/{id}/statistics" + + data: JikanResponseData[MangaStatisticsData] = field(repr=False) + + reading: int = field(init = False) + completed: int = field(init = False) + on_hold: int = field(init = False) + dropped: int = field(init = False) + plan_to_read: int = field(init = False) + total: int = field(init = False) + scores: List[Scores] = field(init = False) + + def __post_init__(self): + stats = self.data["data"] + + self.reading = stats["reading"] + self.completed = stats["completed"] + self.on_hold = stats["on_hold"] + self.dropped = stats["dropped"] + self.plan_to_read = stats["plan_to_read"] + self.total = stats["total"] + self.scores = [] + + for scores in stats["scores"]: + self.scores.append( + Scores(scores) + ) \ No newline at end of file diff --git a/anmoku/typing/jikan/__init__.py b/anmoku/typing/jikan/__init__.py index 9bdc990..2867357 100644 --- a/anmoku/typing/jikan/__init__.py +++ b/anmoku/typing/jikan/__init__.py @@ -7,4 +7,5 @@ from .title import * from .image import * from .search_result import * -from .name import * \ No newline at end of file +from .name import * +from .manga import * \ No newline at end of file diff --git a/anmoku/typing/jikan/character.py b/anmoku/typing/jikan/character.py index 8b7a9be..e113d7c 100644 --- a/anmoku/typing/jikan/character.py +++ b/anmoku/typing/jikan/character.py @@ -3,6 +3,7 @@ from typing_extensions import NotRequired from .anime import PartialAnimeData +from .manga import PartialMangaData from .person import PartialPersonData T = TypeVar("T") @@ -27,7 +28,7 @@ class CharacterAnimeData(TypedDict): class CharacterMangaData(TypedDict): role: str - manga: PartialAnimeData # Anime, manga same thing lmao. # TODO: We should change this once we have MangaData though. + manga: PartialMangaData class CharacterVoicesData(TypedDict): language: str diff --git a/anmoku/typing/jikan/manga/__init__.py b/anmoku/typing/jikan/manga/__init__.py new file mode 100644 index 0000000..b0b5fe6 --- /dev/null +++ b/anmoku/typing/jikan/manga/__init__.py @@ -0,0 +1,3 @@ +from .manga import * +from .characters import * +from .statistics import * \ No newline at end of file diff --git a/anmoku/typing/jikan/manga/characters.py b/anmoku/typing/jikan/manga/characters.py new file mode 100644 index 0000000..953f667 --- /dev/null +++ b/anmoku/typing/jikan/manga/characters.py @@ -0,0 +1,13 @@ +from __future__ import annotations +from typing import TypedDict, final + +from ..character import PartialCharacterData + +__all__ = ( + "MangaCharacterData", +) + +@final +class MangaCharacterData(TypedDict): + character: PartialCharacterData + role: str \ No newline at end of file diff --git a/anmoku/typing/jikan/manga/manga.py b/anmoku/typing/jikan/manga/manga.py new file mode 100644 index 0000000..6dd26a3 --- /dev/null +++ b/anmoku/typing/jikan/manga/manga.py @@ -0,0 +1,70 @@ +from __future__ import annotations +from typing import List, Literal, Optional, TypedDict + +from ..datetime import DateRangeData +from ..title import TitleData +from ..image import ImagesData + +__all__ = ( + "MangaData", + "FullMangaData", + "PartialMangaData", + "MoreInfoMangaData" +) + +MangaStatus = Literal["Publishing", "Finished", "Discontinued"] + +class EntryData(TypedDict): + mal_id: int + type: str + name: str + url: str + +class RelationData(TypedDict): + relation: str + entry: List[EntryData] + +class ExternalSourceData(TypedDict): + name: str + url: str + +class PartialMangaData(TypedDict): + mal_id: int + url: str + images: ImagesData + title: str + +class MangaData(PartialMangaData): + approved: bool + titles: List[TitleData] + title_english: str + title_japanese: str + title_synonyms: List[str] + type: str + chapters: int + volumes: int + status: MangaStatus + publishing: bool + published: DateRangeData + score: Optional[int] + score_by: Optional[int] + rank: Optional[int] + popularity: Optional[int] + members: Optional[int] + favorites: Optional[int] + synopsis: Optional[str] + background: Optional[str] + authors: List[EntryData] + serializations: List[EntryData] + genres: List[EntryData] + explicit_genres: List[EntryData] + themes: List[EntryData] + demographics: List[EntryData] + +class FullMangaData(MangaData): + title_synonyms: List[str] + relations: RelationData + external: List[ExternalSourceData] + +class MoreInfoMangaData(TypedDict): + moreinfo: str \ No newline at end of file diff --git a/anmoku/typing/jikan/manga/statistics.py b/anmoku/typing/jikan/manga/statistics.py new file mode 100644 index 0000000..6e393a0 --- /dev/null +++ b/anmoku/typing/jikan/manga/statistics.py @@ -0,0 +1,20 @@ +from __future__ import annotations +from typing import List, TypedDict + +__all__ = ( + "MangaStatisticsData", +) + +class ScoresData(TypedDict): + score: int + votes: int + percentage: int + +class MangaStatisticsData(TypedDict): + reading: int + completed: int + on_hold: int + dropped: int + plan_to_read: int + total: int + scores: List[ScoresData] diff --git a/anmoku/typing/jikan/person.py b/anmoku/typing/jikan/person.py index 91bfa2c..f69228a 100644 --- a/anmoku/typing/jikan/person.py +++ b/anmoku/typing/jikan/person.py @@ -17,4 +17,10 @@ class PartialPersonData(TypedDict): mal_id: int url: str images: ImagesData + name: str + +class PartialAuthorMangaData(TypedDict): + mal_id: int + type: str + url: str name: str \ No newline at end of file From f2fbd87f4520207ffd2a1638a87715638be4447c Mon Sep 17 00:00:00 2001 From: Ananas Date: Sun, 24 Nov 2024 18:12:28 +0100 Subject: [PATCH 02/21] feat, docs: forums (topics), news, add manga example --- anmoku/resources/anime/__init__.py | 4 +- anmoku/resources/anime/forum.py | 33 ++++++++++++ anmoku/resources/anime/news.py | 29 +++++++++++ anmoku/resources/helpers/__init__.py | 4 +- anmoku/resources/helpers/forum.py | 77 ++++++++++++++++++++++++++++ anmoku/resources/helpers/news.py | 59 +++++++++++++++++++++ anmoku/resources/manga/__init__.py | 4 +- anmoku/resources/manga/forum.py | 33 ++++++++++++ anmoku/resources/manga/manga.py | 32 +++++++++++- anmoku/resources/manga/news.py | 29 +++++++++++ anmoku/typing/jikan/__init__.py | 4 +- anmoku/typing/jikan/forum.py | 23 +++++++++ anmoku/typing/jikan/news.py | 19 +++++++ 13 files changed, 344 insertions(+), 6 deletions(-) create mode 100644 anmoku/resources/anime/forum.py create mode 100644 anmoku/resources/anime/news.py create mode 100644 anmoku/resources/helpers/forum.py create mode 100644 anmoku/resources/helpers/news.py create mode 100644 anmoku/resources/manga/forum.py create mode 100644 anmoku/resources/manga/news.py create mode 100644 anmoku/typing/jikan/forum.py create mode 100644 anmoku/typing/jikan/news.py diff --git a/anmoku/resources/anime/__init__.py b/anmoku/resources/anime/__init__.py index 306e04d..90cdeff 100644 --- a/anmoku/resources/anime/__init__.py +++ b/anmoku/resources/anime/__init__.py @@ -1,4 +1,6 @@ from .anime import * from .characters import * from .staff import * -from .episodes import * \ No newline at end of file +from .episodes import * +from .news import * +from .forum import * \ No newline at end of file diff --git a/anmoku/resources/anime/forum.py b/anmoku/resources/anime/forum.py new file mode 100644 index 0000000..cc19665 --- /dev/null +++ b/anmoku/resources/anime/forum.py @@ -0,0 +1,33 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + ForumData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Forum + +__all__ = ( + "AnimeTopics", + "AnimeForum" +) + + +@dataclass +class AnimeForum(JikanResource): + _get_endpoint = "/anime/{id}/forum" + + data: JikanResponseData[List[ForumData]] = field(repr = False) + + def __iter__(self): + for forum in self.data["data"]: + yield Forum(forum) + +AnimeTopics = AnimeForum \ No newline at end of file diff --git a/anmoku/resources/anime/news.py b/anmoku/resources/anime/news.py new file mode 100644 index 0000000..18b8cdd --- /dev/null +++ b/anmoku/resources/anime/news.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + NewsData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import News + +__all__ = ( + "AnimeNews", +) + +@dataclass +class AnimeNews(JikanResource): + _get_endpoint = "/anime/{id}/news" + + data: JikanResponseData[List[NewsData]] = field(repr = False) + + def __iter__(self): + for news in self.data["data"]: + yield News(news) \ No newline at end of file diff --git a/anmoku/resources/helpers/__init__.py b/anmoku/resources/helpers/__init__.py index d0c0966..41637ee 100644 --- a/anmoku/resources/helpers/__init__.py +++ b/anmoku/resources/helpers/__init__.py @@ -2,4 +2,6 @@ from .image import * from .title import * from .search_result import * -from .name import * \ No newline at end of file +from .name import * +from .news import * +from .forum import * \ No newline at end of file diff --git a/anmoku/resources/helpers/forum.py b/anmoku/resources/helpers/forum.py new file mode 100644 index 0000000..a43bb58 --- /dev/null +++ b/anmoku/resources/helpers/forum.py @@ -0,0 +1,77 @@ +from __future__ import annotations +from datetime import datetime +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + + from ...typing.jikan import ( + ForumData, + LastCommentData + ) + +from dataclasses import dataclass, field + +__all__ = ( + "Forum", +) + +@dataclass +class LastComment(): + data: LastCommentData = field(repr = False) + + url: str = field(init = False) + """The MyAnimeList URL for this comment.""" + author_username: str = field(init = False) + """The username of the author who wrote this forum article.""" + author_url: str = field(init = False) + """The profile URL of the author on MyAnimeList.""" + date: datetime = field(init = False) + """The date when this was commented.""" + + def __post_init__(self): + comment = self.data + + self.url = comment["url"] + self.author_username = comment["author_username"] + self.author_url = comment["author_url"] + self.date = datetime.fromisoformat(comment["date"]) + +@dataclass +class Forum(): + data: ForumData = field(repr = False) + + id: int = field(init = False) + """The MyAnimeList ID for this forum article.""" + url: str = field(init = False) + """The MyAnimeList URL for this forum article.""" + title: str = field(init = False) + """The title of the forum article.""" + name: str = field(init = False) + """Alias to ``Forum.title``.""" + date: datetime = field(init = False) + """The publication date of this forum article.""" + author_username: str = field(init = False) + """The username of the author who wrote this forum article.""" + author_url: str = field(init = False) + """The profile URL of the author on MyAnimeList.""" + comments: int = field(init = False) + """The amount of comments on this forum article.""" + last_comment: LastComment = field(init = False, default = None) + """Last comment on that forum article.""" + + def __post_init__(self): + forum = self.data + + self.id = forum["mal_id"] + self.url = forum["url"] + self.title = forum["title"] + self.name = self.title + self.date = datetime.fromisoformat(forum["date"]) + self.author_username = forum["author_username"] + self.author_url = forum["author_url"] + self.comments = forum["comments"] + + last_comment = forum["last_comment"] + + if last_comment is not None: + self.last_comment = LastComment(last_comment) \ No newline at end of file diff --git a/anmoku/resources/helpers/news.py b/anmoku/resources/helpers/news.py new file mode 100644 index 0000000..0be26d0 --- /dev/null +++ b/anmoku/resources/helpers/news.py @@ -0,0 +1,59 @@ +from __future__ import annotations +from datetime import datetime +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + + from ...typing.jikan import ( + NewsData, + ) + +from dataclasses import dataclass, field + +from ..helpers import Image + +__all__ = ( + "News", +) + +@dataclass +class News(): + data: NewsData = field(repr = False) + + id: int = field(init = False) + """The MyAnimeList ID for this news article.""" + url: str = field(init = False) + """The MyAnimeList URL for this news article.""" + title: str = field(init = False) + """The title of the news article.""" + name: str = field(init = False) + """Alias to ``News.title``.""" + date: datetime = field(init = False) + """The publication date of this news article.""" + author_username: str = field(init = False) + """The username of the author who wrote this news article.""" + author_url: str = field(init = False) + """The profile URL of the author on MyAnimeList.""" + forum_str: str = field(init = False) + """The profile URL of the author on MyAnimeList.""" + images: Image = field(init = False) + """The banner image of this news article.""" + comments: int = field(init = False) + """The amount of comments on this news article.""" + excerpt: str = field(init = False) + """A brief preview of the news article content.""" + + def __post_init__(self): + news = self.data + + self.id = news["mal_id"] + self.url = news["url"] + self.title = news["title"] + self.name = self.title + self.date = datetime.fromisoformat(news["date"]) + self.author_username = news["author_username"] + self.author_url = news["author_url"] + self.forum_str = news["forum_url"] + self.images = Image(news["images"]) + self.comments = news["comments"] + self.excerpt = news["excerpt"] \ No newline at end of file diff --git a/anmoku/resources/manga/__init__.py b/anmoku/resources/manga/__init__.py index ecdc88e..1e67276 100644 --- a/anmoku/resources/manga/__init__.py +++ b/anmoku/resources/manga/__init__.py @@ -1,3 +1,5 @@ from .manga import * from .character import * -from .statistics import * \ No newline at end of file +from .statistics import * +from .news import * +from .forum import * \ No newline at end of file diff --git a/anmoku/resources/manga/forum.py b/anmoku/resources/manga/forum.py new file mode 100644 index 0000000..a3631b6 --- /dev/null +++ b/anmoku/resources/manga/forum.py @@ -0,0 +1,33 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + ForumData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Forum + +__all__ = ( + "MangaTopics", + "MangaForum" +) + + +@dataclass +class MangaTopics(JikanResource): + _get_endpoint = "/manga/{id}/forum" + + data: JikanResponseData[List[ForumData]] = field(repr = False) + + def __iter__(self): + for forum in self.data["data"]: + yield Forum(forum) + +MangaForum = MangaTopics \ No newline at end of file diff --git a/anmoku/resources/manga/manga.py b/anmoku/resources/manga/manga.py index df0fc8e..da74a95 100644 --- a/anmoku/resources/manga/manga.py +++ b/anmoku/resources/manga/manga.py @@ -1,8 +1,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING if TYPE_CHECKING: - from ...typing.jikan.manga.manga import MangaStatus, RelationData, ExternalSourceData + from typing import List + + from ...typing.jikan.manga.manga import ( + MangaStatus, + RelationData, + ExternalSourceData + ) + from ...typing.jikan import ( MangaData, FullMangaData, @@ -31,6 +38,27 @@ def __init__(self, value: MangaStatus) -> None: @dataclass class Manga(JikanResource): + """ + Get or search for manga. + + ------------ + + ⭐ Example: + ------------ + Here's an example of how to get an anime by ID and display it's cover art:: + from anmoku import Manga, Anmoku + + client = Anmoku() + + manga = client.get(Manga, id = 85) + + print( + f"Got the manga '{manga.name}', it's japanese name is '{manga.name.japanese}' and it was published in {manga.published.from_.year}." + ) + + # Display it's image. + manga.image.get_image().show() + """ _get_endpoint = "/manga/{id}" _search_endpoint = "/manga" diff --git a/anmoku/resources/manga/news.py b/anmoku/resources/manga/news.py new file mode 100644 index 0000000..ed40398 --- /dev/null +++ b/anmoku/resources/manga/news.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + NewsData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import News + +__all__ = ( + "MangaNews", +) + +@dataclass +class MangaNews(JikanResource): + _get_endpoint = "/manga/{id}/news" + + data: JikanResponseData[List[NewsData]] = field(repr = False) + + def __iter__(self): + for news in self.data["data"]: + yield News(news) \ No newline at end of file diff --git a/anmoku/typing/jikan/__init__.py b/anmoku/typing/jikan/__init__.py index 2867357..a78ff45 100644 --- a/anmoku/typing/jikan/__init__.py +++ b/anmoku/typing/jikan/__init__.py @@ -8,4 +8,6 @@ from .image import * from .search_result import * from .name import * -from .manga import * \ No newline at end of file +from .manga import * +from .news import * +from .forum import * \ No newline at end of file diff --git a/anmoku/typing/jikan/forum.py b/anmoku/typing/jikan/forum.py new file mode 100644 index 0000000..3ef89e7 --- /dev/null +++ b/anmoku/typing/jikan/forum.py @@ -0,0 +1,23 @@ +from __future__ import annotations +from typing import Optional, TypedDict + +__all__ = ( + "ForumData", + "LastCommentData", +) + +class LastCommentData(TypedDict): + url: str + author_username: str + author_url: str + date: str + +class ForumData(TypedDict): + mal_id: int + url: str + title: str + date: str + author_username: str + author_url: str + comments: int + last_comment: Optional[LastCommentData] \ No newline at end of file diff --git a/anmoku/typing/jikan/news.py b/anmoku/typing/jikan/news.py new file mode 100644 index 0000000..00fc1bc --- /dev/null +++ b/anmoku/typing/jikan/news.py @@ -0,0 +1,19 @@ +from __future__ import annotations +from typing import TypedDict, TYPE_CHECKING + +if TYPE_CHECKING: + from .image import ImagesData + +__all__ = ("NewsData",) + +class NewsData(TypedDict): + mal_id: int + url: str + title: str + date: str + author_username: str + author_url: str + forum_url: str + images: ImagesData + comments: int + excerpt: str \ No newline at end of file From 857690396ac7514b5a79daa1636e6fa6014ffd9e Mon Sep 17 00:00:00 2001 From: Ananas Date: Sun, 24 Nov 2024 20:05:35 +0100 Subject: [PATCH 03/21] feat: recommendations, pictures, reviews, moreinfo --- anmoku/resources/anime/__init__.py | 5 +- anmoku/resources/anime/pictures.py | 29 +++++++ anmoku/resources/anime/recommendations.py | 28 ++++++ anmoku/resources/anime/reviews.py | 29 +++++++ anmoku/resources/helpers/__init__.py | 4 +- anmoku/resources/helpers/news.py | 4 +- anmoku/resources/helpers/recommendations.py | 35 ++++++++ anmoku/resources/helpers/review.py | 96 +++++++++++++++++++++ anmoku/resources/manga/__init__.py | 5 +- anmoku/resources/manga/manga.py | 22 ++++- anmoku/resources/manga/pictures.py | 29 +++++++ anmoku/resources/manga/recommendations.py | 27 ++++++ anmoku/resources/manga/reviews.py | 29 +++++++ anmoku/typing/jikan/__init__.py | 5 +- anmoku/typing/jikan/moreinfo.py | 9 ++ anmoku/typing/jikan/recommendations.py | 18 ++++ anmoku/typing/jikan/review.py | 39 +++++++++ 17 files changed, 404 insertions(+), 9 deletions(-) create mode 100644 anmoku/resources/anime/pictures.py create mode 100644 anmoku/resources/anime/recommendations.py create mode 100644 anmoku/resources/anime/reviews.py create mode 100644 anmoku/resources/helpers/recommendations.py create mode 100644 anmoku/resources/helpers/review.py create mode 100644 anmoku/resources/manga/pictures.py create mode 100644 anmoku/resources/manga/recommendations.py create mode 100644 anmoku/resources/manga/reviews.py create mode 100644 anmoku/typing/jikan/moreinfo.py create mode 100644 anmoku/typing/jikan/recommendations.py create mode 100644 anmoku/typing/jikan/review.py diff --git a/anmoku/resources/anime/__init__.py b/anmoku/resources/anime/__init__.py index 90cdeff..2f5ca0c 100644 --- a/anmoku/resources/anime/__init__.py +++ b/anmoku/resources/anime/__init__.py @@ -3,4 +3,7 @@ from .staff import * from .episodes import * from .news import * -from .forum import * \ No newline at end of file +from .forum import * +from .pictures import * +from .recommendations import * +from .reviews import * \ No newline at end of file diff --git a/anmoku/resources/anime/pictures.py b/anmoku/resources/anime/pictures.py new file mode 100644 index 0000000..df2cce4 --- /dev/null +++ b/anmoku/resources/anime/pictures.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + ImagesData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Image + +__all__ = ( + "AnimePictures", +) + +@dataclass +class AnimePictures(JikanResource): + _get_endpoint = "/anime/{id}/pictures" + + data: JikanResponseData[List[ImagesData]] = field(repr = False) + + def __iter__(self): + for picture in self.data["data"]: + yield Image(picture) \ No newline at end of file diff --git a/anmoku/resources/anime/recommendations.py b/anmoku/resources/anime/recommendations.py new file mode 100644 index 0000000..0e43ff6 --- /dev/null +++ b/anmoku/resources/anime/recommendations.py @@ -0,0 +1,28 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from ...typing.jikan import ( + RecommendationsData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Recommendation + +__all__ = ( + "AnimeRecommendations", +) + +@dataclass +class AnimeRecommendations(JikanResource): + _get_endpoint = "/anime/{id}/recommendations" + + data: JikanResponseData[RecommendationsData] = field(repr = False) + + def __iter__(self): + for recommendation in self.data["data"]: + yield Recommendation(recommendation) \ No newline at end of file diff --git a/anmoku/resources/anime/reviews.py b/anmoku/resources/anime/reviews.py new file mode 100644 index 0000000..40ad0ba --- /dev/null +++ b/anmoku/resources/anime/reviews.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + ReviewData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Review + +__all__ = ( + "AnimeReviews", +) + +@dataclass +class AnimeReviews(JikanResource): + _get_endpoint = "/anime/{id}/reviews" + + data: JikanResponseData[List[ReviewData]] = field(repr = False) + + def __iter__(self): + for review in self.data["data"]: + yield Review(review) \ No newline at end of file diff --git a/anmoku/resources/helpers/__init__.py b/anmoku/resources/helpers/__init__.py index 41637ee..5983972 100644 --- a/anmoku/resources/helpers/__init__.py +++ b/anmoku/resources/helpers/__init__.py @@ -4,4 +4,6 @@ from .search_result import * from .name import * from .news import * -from .forum import * \ No newline at end of file +from .forum import * +from .recommendations import * +from .review import * \ No newline at end of file diff --git a/anmoku/resources/helpers/news.py b/anmoku/resources/helpers/news.py index 0be26d0..61a2258 100644 --- a/anmoku/resources/helpers/news.py +++ b/anmoku/resources/helpers/news.py @@ -36,7 +36,7 @@ class News(): """The profile URL of the author on MyAnimeList.""" forum_str: str = field(init = False) """The profile URL of the author on MyAnimeList.""" - images: Image = field(init = False) + image: Image = field(init = False) """The banner image of this news article.""" comments: int = field(init = False) """The amount of comments on this news article.""" @@ -54,6 +54,6 @@ def __post_init__(self): self.author_username = news["author_username"] self.author_url = news["author_url"] self.forum_str = news["forum_url"] - self.images = Image(news["images"]) + self.image = Image(news["images"]) self.comments = news["comments"] self.excerpt = news["excerpt"] \ No newline at end of file diff --git a/anmoku/resources/helpers/recommendations.py b/anmoku/resources/helpers/recommendations.py new file mode 100644 index 0000000..ff77016 --- /dev/null +++ b/anmoku/resources/helpers/recommendations.py @@ -0,0 +1,35 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...typing.jikan import RecommendationsData + +from dataclasses import dataclass, field + +from .image import Image + +__all__ = ("Recommendation",) + +@dataclass +class Recommendation(): + data: RecommendationsData = field(repr = False) + + id: int = field(init = False) + """The MyAnimeList ID of the recommendation.""" + url: str = field(init = False) + """The MyAnimeList URL of the recommendation.""" + images: Image = field(init = False) + """The banner image of the recommendation.""" + title: str = field(init = False) + """The title of the recommendation.""" + name: str = field(init = False) + """Alias to `Recommendation.title`""" + + def __post_init__(self): + item = self.data["entry"] + + self.id = item["mal_id"] + self.url = item["url"] + self.images = Image(item["images"]) + self.title = item["title"] + self.name = self.title \ No newline at end of file diff --git a/anmoku/resources/helpers/review.py b/anmoku/resources/helpers/review.py new file mode 100644 index 0000000..9a1dbe1 --- /dev/null +++ b/anmoku/resources/helpers/review.py @@ -0,0 +1,96 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan.review import UserData, ReactionsData + from ...typing.jikan import ReviewData + +from dataclasses import dataclass, field +from datetime import datetime + +from .image import Image + +__all__ = ("Review",) + +@dataclass +class User(): + data: UserData = field(repr = False) + + username: str = field(init = False) + """The MyAnimeList Username of the reviewer.""" + url: str = field(init = False) + """The MyAnimeList Profile URL of the reviewer.""" + image: Image = field(init = False) + """The Profile Picture of the reviewer.""" + + def __post_init__(self): + user = self.data + + self.username = user["username"] + self.url = user["url"] + self.image = Image(user["images"]) + +@dataclass +class Reactions(): + data: ReactionsData = field(repr = False) + + overall: int = field(init = False) + nice: int = field(init = False) + love_it: int = field(init = False) + funny: int = field(init = False) + confusing: int = field(init = False) + informative: int = field(init = False) + well_written: int = field(init = False) + creative: int = field(init = False) + + def __post_init__(self): + reactions = self.data + + self.overall = reactions["overall"] + self.nice = reactions["nice"] + self.love_it = reactions["love_it"] + self.funny = reactions["funny"] + self.confusing = reactions["confusing"] + self.informative = reactions["informative"] + self.well_written = reactions["well_written"] + self.creative = reactions["creative"] + +@dataclass +class Review(): + data: ReviewData = field(repr = False) + + user: User = field(init = False) + """The user reviewing this.""" + url: str = field(init = False) + """The MyAnimeList URL of the review.""" + reactions: Reactions = field(init = False) + """Reactions to this review.""" + date: datetime = field(init = False) + """The date when this review was posted.""" + review: str = field(init = False) + """The review content.""" + content: str = field(init = False) + """Alias for `Review.review`""" + score: int = field(init = False) + """The score on the review.""" + tags: List[str] = field(init = False) + """The tags on the Review.""" + spoiler: bool = field(init = False) + """Review contains spoiler.""" + preliminary: bool = field(init = False) + + def __post_init__(self): + review = self.data + + self.user = User(review["user"]) + self.url = review["url"] + self.reactions = Reactions(review["reactions"]) + self.date = datetime.fromisoformat(review["date"]) + self.review = review["review"] + self.content = self.review + self.score = review["score"] + self.tags = review["tags"] + self.spoiler = review["is_spoiler"] + self.preliminary = review["is_preliminary"] \ No newline at end of file diff --git a/anmoku/resources/manga/__init__.py b/anmoku/resources/manga/__init__.py index 1e67276..5f00642 100644 --- a/anmoku/resources/manga/__init__.py +++ b/anmoku/resources/manga/__init__.py @@ -2,4 +2,7 @@ from .character import * from .statistics import * from .news import * -from .forum import * \ No newline at end of file +from .forum import * +from .pictures import * +from .recommendations import * +from .reviews import * \ No newline at end of file diff --git a/anmoku/resources/manga/manga.py b/anmoku/resources/manga/manga.py index da74a95..905ec7e 100644 --- a/anmoku/resources/manga/manga.py +++ b/anmoku/resources/manga/manga.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Optional from ...typing.jikan.manga.manga import ( MangaStatus, @@ -14,6 +14,7 @@ MangaData, FullMangaData, JikanResponseData, + MoreInfoData ) from dataclasses import dataclass, field @@ -24,7 +25,8 @@ __all__ = ( "Manga", - "FullManga" + "FullManga", + "MangaMoreInfo" ) @@ -128,4 +130,18 @@ def __post_init__(self): self.title_synonyms = manga["title_synonyms"] self.relations = manga["relations"] - self.external = manga["external"] \ No newline at end of file + self.external = manga["external"] + +@dataclass +class MangaMoreInfo(JikanResource): + _get_endpoint = "/manga/{id}/moreinfo" + + data: JikanResponseData[MoreInfoData] = field(repr=False) + + more_info: Optional[str] = field(init = False, default = None) + + def __post_init__(self): + more_info = self.data["data"]["moreinfo"] + + if more_info is not None: + self.more_info = more_info \ No newline at end of file diff --git a/anmoku/resources/manga/pictures.py b/anmoku/resources/manga/pictures.py new file mode 100644 index 0000000..b44b7a4 --- /dev/null +++ b/anmoku/resources/manga/pictures.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + ImagesData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Image + +__all__ = ( + "MangaPictures", +) + +@dataclass +class MangaPictures(JikanResource): + _get_endpoint = "/manga/{id}/pictures" + + data: JikanResponseData[List[ImagesData]] = field(repr = False) + + def __iter__(self): + for picture in self.data["data"]: + yield Image(picture) \ No newline at end of file diff --git a/anmoku/resources/manga/recommendations.py b/anmoku/resources/manga/recommendations.py new file mode 100644 index 0000000..9501623 --- /dev/null +++ b/anmoku/resources/manga/recommendations.py @@ -0,0 +1,27 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...typing.jikan import ( + RecommendationsData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Recommendation + +__all__ = ( + "MangaRecommendations", +) + +@dataclass +class MangaRecommendations(JikanResource): + _get_endpoint = "/manga/{id}/recommendations" + + data: JikanResponseData[RecommendationsData] = field(repr = False) + + def __iter__(self): + for recommendation in self.data["data"]: + yield Recommendation(recommendation) \ No newline at end of file diff --git a/anmoku/resources/manga/reviews.py b/anmoku/resources/manga/reviews.py new file mode 100644 index 0000000..0f65a8a --- /dev/null +++ b/anmoku/resources/manga/reviews.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + ReviewData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Review + +__all__ = ( + "MangaReviews", +) + +@dataclass +class MangaReviews(JikanResource): + _get_endpoint = "/manga/{id}/reviews" + + data: JikanResponseData[List[ReviewData]] = field(repr = False) + + def __iter__(self): + for review in self.data["data"]: + yield Review(review) \ No newline at end of file diff --git a/anmoku/typing/jikan/__init__.py b/anmoku/typing/jikan/__init__.py index a78ff45..37bfb78 100644 --- a/anmoku/typing/jikan/__init__.py +++ b/anmoku/typing/jikan/__init__.py @@ -10,4 +10,7 @@ from .name import * from .manga import * from .news import * -from .forum import * \ No newline at end of file +from .forum import * +from .moreinfo import * +from .recommendations import * +from .review import * \ No newline at end of file diff --git a/anmoku/typing/jikan/moreinfo.py b/anmoku/typing/jikan/moreinfo.py new file mode 100644 index 0000000..bbef1e1 --- /dev/null +++ b/anmoku/typing/jikan/moreinfo.py @@ -0,0 +1,9 @@ +from __future__ import annotations +from typing import TypedDict, Optional + +__all__ = ( + "MoreInfoData", +) + +class MoreInfoData(TypedDict): + moreinfo: Optional[str] \ No newline at end of file diff --git a/anmoku/typing/jikan/recommendations.py b/anmoku/typing/jikan/recommendations.py new file mode 100644 index 0000000..35b559f --- /dev/null +++ b/anmoku/typing/jikan/recommendations.py @@ -0,0 +1,18 @@ +from __future__ import annotations +from typing import TypedDict, TYPE_CHECKING + +if TYPE_CHECKING: + from .image import ImageData + +__all__ = ( + "RecommendationsData", +) + +class EntryData(TypedDict): + mal_id: int + url: str + images: ImageData + title: str + +class RecommendationsData(TypedDict): + entry: EntryData \ No newline at end of file diff --git a/anmoku/typing/jikan/review.py b/anmoku/typing/jikan/review.py new file mode 100644 index 0000000..b6c9b38 --- /dev/null +++ b/anmoku/typing/jikan/review.py @@ -0,0 +1,39 @@ +from __future__ import annotations +from typing import TypedDict, TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from .image import ImageData + +__all__ = ( + "ReviewData", +) + +class UserData(TypedDict): + username: str + url: str + images: ImageData + +class ReactionsData(TypedDict): + overall: int + nice: int + love_it: int + funny: int + confusing: int + informative: int + well_written: int + creative: int + +class ReviewData(TypedDict): + user: UserData + mal_id: int + url: str + type: str + reactions: ReactionsData + date: str + review: str + score: int + tags: List[str] + is_spoiler: bool + is_preliminary: bool \ No newline at end of file From 3ce4f949c9e677471e5acbcb8f4b1a877b6a1423 Mon Sep 17 00:00:00 2001 From: Ananas Date: Sun, 24 Nov 2024 20:06:08 +0100 Subject: [PATCH 04/21] feat: forgor moreinfo for anime --- anmoku/resources/anime/anime.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/anmoku/resources/anime/anime.py b/anmoku/resources/anime/anime.py index c78129c..f1c52c1 100644 --- a/anmoku/resources/anime/anime.py +++ b/anmoku/resources/anime/anime.py @@ -13,6 +13,7 @@ AnimeData, FullAnimeData, JikanResponseData, + MoreInfoData ) from ...typing.mal import MALRatings @@ -173,6 +174,19 @@ class FullAnime(Anime): # TODO: Finish this. You can use the FullAnimeData type data: JikanResponseData[FullAnimeData] = field(repr=False) +@dataclass +class AnimeMoreInfo(JikanResource): + _get_endpoint = "/anime/{id}/moreinfo" + + data: JikanResponseData[MoreInfoData] = field(repr=False) + + more_info: Optional[str] = field(init = False, default = None) + + def __post_init__(self): + more_info = self.data["data"]["moreinfo"] + + if more_info is not None: + self.more_info = more_info @dataclass class Trailer(): From 92c56411503d169b1ece65a1888167a8dcc3d26c Mon Sep 17 00:00:00 2001 From: Ananas Date: Sun, 24 Nov 2024 20:10:01 +0100 Subject: [PATCH 05/21] refactor: images should be image --- anmoku/resources/helpers/recommendations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anmoku/resources/helpers/recommendations.py b/anmoku/resources/helpers/recommendations.py index ff77016..a34065d 100644 --- a/anmoku/resources/helpers/recommendations.py +++ b/anmoku/resources/helpers/recommendations.py @@ -18,7 +18,7 @@ class Recommendation(): """The MyAnimeList ID of the recommendation.""" url: str = field(init = False) """The MyAnimeList URL of the recommendation.""" - images: Image = field(init = False) + image: Image = field(init = False) """The banner image of the recommendation.""" title: str = field(init = False) """The title of the recommendation.""" @@ -30,6 +30,6 @@ def __post_init__(self): self.id = item["mal_id"] self.url = item["url"] - self.images = Image(item["images"]) + self.image = Image(item["images"]) self.title = item["title"] self.name = self.title \ No newline at end of file From f44dfc989e44d7a097a9adc2f88d4002425af975 Mon Sep 17 00:00:00 2001 From: Goldy <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Sun, 24 Nov 2024 22:15:40 +0000 Subject: [PATCH 06/21] fix, style: invalid rst and manga resource type hinting not working for client.search because it's missing from `SearchResourceGenericT` --- anmoku/clients/base.py | 3 ++- anmoku/resources/manga/manga.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/anmoku/clients/base.py b/anmoku/clients/base.py index 806e311..7a38f04 100644 --- a/anmoku/clients/base.py +++ b/anmoku/clients/base.py @@ -15,8 +15,9 @@ SearchResourceGenericT = TypeVar( "SearchResourceGenericT", - resources.Anime, + resources.Anime, resources.Character, + resources.Manga ) import logging diff --git a/anmoku/resources/manga/manga.py b/anmoku/resources/manga/manga.py index 905ec7e..02fb436 100644 --- a/anmoku/resources/manga/manga.py +++ b/anmoku/resources/manga/manga.py @@ -48,6 +48,7 @@ class Manga(JikanResource): ⭐ Example: ------------ Here's an example of how to get an anime by ID and display it's cover art:: + from anmoku import Manga, Anmoku client = Anmoku() From 5be587f818e3e908ea085aede7db36da001693c0 Mon Sep 17 00:00:00 2001 From: Ananas Date: Mon, 25 Nov 2024 04:54:37 +0100 Subject: [PATCH 07/21] feat: random manga and anime --- anmoku/clients/async_.py | 13 ++++++++++++- anmoku/clients/base.py | 11 +++++++++++ anmoku/clients/sync.py | 13 ++++++++++++- anmoku/resources/anime/anime.py | 2 ++ anmoku/resources/base.py | 2 ++ anmoku/resources/manga/manga.py | 1 + 6 files changed, 40 insertions(+), 2 deletions(-) diff --git a/anmoku/clients/async_.py b/anmoku/clients/async_.py index 49a90ab..e1bb2ab 100644 --- a/anmoku/clients/async_.py +++ b/anmoku/clients/async_.py @@ -7,7 +7,7 @@ from ..typing.anmoku import SnowflakeT from ..typing.jikan import SearchResultData - from .base import ResourceGenericT, SearchResourceGenericT + from .base import ResourceGenericT, SearchResourceGenericT, RandomResourceGenericT from aiohttp import ClientSession from devgoldyutils import Colours @@ -77,6 +77,17 @@ async def search(self, resource: Type[SearchResourceGenericT], query: str) -> Se return SearchResult(json_data, resource) + async def random(self, resource: Type[RandomResourceGenericT]) -> RandomResourceGenericT: + """Fetches a random object of the specified resource.""" + url = resource._random_endpoint + + if url is None: + raise ResourceNotSupportedError(resource, "random") + + json_data = await self._request(url) + + return resource(json_data) + async def _request( self, route: str, diff --git a/anmoku/clients/base.py b/anmoku/clients/base.py index 7a38f04..d5eda30 100644 --- a/anmoku/clients/base.py +++ b/anmoku/clients/base.py @@ -20,6 +20,12 @@ resources.Manga ) + RandomResourceGenericT = TypeVar( + "RandomResourceGenericT", + resources.Anime, + resources.Manga + ) + import logging from urllib.parse import quote from abc import ABC, abstractmethod @@ -65,6 +71,11 @@ def get(self, resource: Type[ResourceGenericT], id: SnowflakeT) -> ResourceGener def search(self, resource: Type[SearchResourceGenericT], query: str) -> SearchResult[SearchResourceGenericT]: """Searches for the resource and returns a list of the results.""" ... + + @abstractmethod + def random(self, resource: Type[RandomResourceGenericT]) -> RandomResourceGenericT: + """Fetches a random object of the specified resource.""" + ... def _format_url(self, unformatted_url: str, resource: Type[ResourceGenericT], *args: str, **kwargs: str) -> str: """Formats the URL while also taking URL encoding into account.""" diff --git a/anmoku/clients/sync.py b/anmoku/clients/sync.py index aaaf872..4ab4b6b 100644 --- a/anmoku/clients/sync.py +++ b/anmoku/clients/sync.py @@ -7,7 +7,7 @@ from ..typing.anmoku import SnowflakeT from ..typing.jikan import SearchResultData - from .base import ResourceGenericT, SearchResourceGenericT + from .base import ResourceGenericT, SearchResourceGenericT, RandomResourceGenericT from requests import Session from devgoldyutils import Colours @@ -74,6 +74,17 @@ def search(self, resource: Type[SearchResourceGenericT], query: str) -> SearchRe json_data: SearchResultData[Any] = self._request(url, params = {"q": query}) return SearchResult(json_data, resource) + + def random(self, resource: Type[RandomResourceGenericT]) -> RandomResourceGenericT: + """Fetches a random object of the specified resource.""" + url = resource._random_endpoint + + if url is None: + raise ResourceNotSupportedError(resource, "random") + + json_data = self._request(url) + + return resource(json_data) def _request( self, diff --git a/anmoku/resources/anime/anime.py b/anmoku/resources/anime/anime.py index f1c52c1..ff5b324 100644 --- a/anmoku/resources/anime/anime.py +++ b/anmoku/resources/anime/anime.py @@ -71,6 +71,8 @@ class Anime(JikanResource): """ _get_endpoint = "/anime/{id}" _search_endpoint = "/anime" + _random_endpoint = "/random/anime" + data: JikanResponseData[AnimeData] = field(repr = False) diff --git a/anmoku/resources/base.py b/anmoku/resources/base.py index 8222f2f..7a48f28 100644 --- a/anmoku/resources/base.py +++ b/anmoku/resources/base.py @@ -14,5 +14,7 @@ class JikanResource(): """The jikan api endpoint where you can get this object.""" _search_endpoint: Optional[str] = field(init = False, default = None) """The jikan api endpoint to search with this resource.""" + _random_endpoint: Optional[str] = field(init = False, default = None) + """The jikan api endpoint to get a random object with this resource.""" data: JikanResponseData[Any] \ No newline at end of file diff --git a/anmoku/resources/manga/manga.py b/anmoku/resources/manga/manga.py index 02fb436..4e40b0f 100644 --- a/anmoku/resources/manga/manga.py +++ b/anmoku/resources/manga/manga.py @@ -64,6 +64,7 @@ class Manga(JikanResource): """ _get_endpoint = "/manga/{id}" _search_endpoint = "/manga" + _random_endpoint = "/random/manga" data: JikanResponseData[MangaData] = field(repr = False) From 5fef8d0b233aca796993baf4138ee58fd449fa0f Mon Sep 17 00:00:00 2001 From: Ananas Date: Mon, 25 Nov 2024 05:14:26 +0100 Subject: [PATCH 08/21] feat: add characters --- anmoku/clients/base.py | 1 + anmoku/resources/character/character.py | 1 + 2 files changed, 2 insertions(+) diff --git a/anmoku/clients/base.py b/anmoku/clients/base.py index d5eda30..0fb5ee8 100644 --- a/anmoku/clients/base.py +++ b/anmoku/clients/base.py @@ -23,6 +23,7 @@ RandomResourceGenericT = TypeVar( "RandomResourceGenericT", resources.Anime, + resources.Character, resources.Manga ) diff --git a/anmoku/resources/character/character.py b/anmoku/resources/character/character.py index 3a9908b..cf0960a 100644 --- a/anmoku/resources/character/character.py +++ b/anmoku/resources/character/character.py @@ -20,6 +20,7 @@ class Character(JikanResource): _get_endpoint = "/characters/{id}" _search_endpoint = "/characters" + _random_endpoint = "/random/characters" data: JikanResponseData[CharacterData] = field(repr = False) From 7292eb4a09d395bfc1c41cfaed76681979d7f15f Mon Sep 17 00:00:00 2001 From: Ananas Date: Mon, 25 Nov 2024 11:23:32 +0100 Subject: [PATCH 09/21] feat: relations, external, user_updates AUGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH --- anmoku/resources/anime/__init__.py | 4 +- anmoku/resources/anime/external.py | 29 +++++++++ anmoku/resources/anime/relations.py | 29 +++++++++ anmoku/resources/helpers/__init__.py | 5 +- anmoku/resources/helpers/external.py | 26 ++++++++ anmoku/resources/helpers/relations.py | 49 +++++++++++++++ anmoku/resources/helpers/review.py | 22 +------ anmoku/resources/helpers/user.py | 31 ++++++++++ anmoku/resources/manga/__init__.py | 5 +- anmoku/resources/manga/external.py | 29 +++++++++ anmoku/resources/manga/relations.py | 29 +++++++++ anmoku/resources/manga/user_updates.py | 60 +++++++++++++++++++ anmoku/typing/jikan/__init__.py | 7 ++- anmoku/typing/jikan/anime/anime.py | 18 +----- anmoku/typing/jikan/entry.py | 20 +++++++ anmoku/typing/jikan/external.py | 10 ++++ anmoku/typing/jikan/manga/__init__.py | 3 +- anmoku/typing/jikan/manga/manga.py | 16 +---- anmoku/typing/jikan/manga/user_updates.py | 19 ++++++ .../jikan/{moreinfo.py => more_info.py} | 0 anmoku/typing/jikan/review.py | 9 +-- anmoku/typing/jikan/user.py | 14 +++++ 22 files changed, 372 insertions(+), 62 deletions(-) create mode 100644 anmoku/resources/anime/external.py create mode 100644 anmoku/resources/anime/relations.py create mode 100644 anmoku/resources/helpers/external.py create mode 100644 anmoku/resources/helpers/relations.py create mode 100644 anmoku/resources/helpers/user.py create mode 100644 anmoku/resources/manga/external.py create mode 100644 anmoku/resources/manga/relations.py create mode 100644 anmoku/resources/manga/user_updates.py create mode 100644 anmoku/typing/jikan/entry.py create mode 100644 anmoku/typing/jikan/external.py create mode 100644 anmoku/typing/jikan/manga/user_updates.py rename anmoku/typing/jikan/{moreinfo.py => more_info.py} (100%) create mode 100644 anmoku/typing/jikan/user.py diff --git a/anmoku/resources/anime/__init__.py b/anmoku/resources/anime/__init__.py index 2f5ca0c..bddcea4 100644 --- a/anmoku/resources/anime/__init__.py +++ b/anmoku/resources/anime/__init__.py @@ -6,4 +6,6 @@ from .forum import * from .pictures import * from .recommendations import * -from .reviews import * \ No newline at end of file +from .reviews import * +from .relations import * +from .external import * \ No newline at end of file diff --git a/anmoku/resources/anime/external.py b/anmoku/resources/anime/external.py new file mode 100644 index 0000000..db22cf9 --- /dev/null +++ b/anmoku/resources/anime/external.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + ExternalSourceData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import ExternalSource + +__all__ = ( + "AnimeExternal", +) + +@dataclass +class AnimeExternal(JikanResource): + _get_endpoint = "/anime/{id}/external" + + data: JikanResponseData[List[ExternalSourceData]] = field(repr = False) + + def __iter__(self): + for relation in self.data["data"]: + yield ExternalSource(relation) \ No newline at end of file diff --git a/anmoku/resources/anime/relations.py b/anmoku/resources/anime/relations.py new file mode 100644 index 0000000..cb99c29 --- /dev/null +++ b/anmoku/resources/anime/relations.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + RelationData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Relation + +__all__ = ( + "AnimeRelations", +) + +@dataclass +class AnimeRelations(JikanResource): + _get_endpoint = "/anime/{id}/relations" + + data: JikanResponseData[List[RelationData]] = field(repr = False) + + def __iter__(self): + for relation in self.data["data"]: + yield Relation(relation) \ No newline at end of file diff --git a/anmoku/resources/helpers/__init__.py b/anmoku/resources/helpers/__init__.py index 5983972..32db2c0 100644 --- a/anmoku/resources/helpers/__init__.py +++ b/anmoku/resources/helpers/__init__.py @@ -6,4 +6,7 @@ from .news import * from .forum import * from .recommendations import * -from .review import * \ No newline at end of file +from .review import * +from .user import * +from .relations import * +from .external import * \ No newline at end of file diff --git a/anmoku/resources/helpers/external.py b/anmoku/resources/helpers/external.py new file mode 100644 index 0000000..43db59e --- /dev/null +++ b/anmoku/resources/helpers/external.py @@ -0,0 +1,26 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...typing.jikan import ( + ExternalSourceData, + ) + +from dataclasses import dataclass, field + +__all__ = ( + "ExternalSource", +) + +@dataclass +class ExternalSource(): + data: ExternalSourceData = field(repr = False) + + name: str = field(init = False) + url: str = field(init = False) + + def __post_init__(self): + external = self.data + + self.name = external["name"] + self.url = external["url"] \ No newline at end of file diff --git a/anmoku/resources/helpers/relations.py b/anmoku/resources/helpers/relations.py new file mode 100644 index 0000000..a88ff6a --- /dev/null +++ b/anmoku/resources/helpers/relations.py @@ -0,0 +1,49 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + RelationData, + EntryData, + ) + +from dataclasses import dataclass, field + +__all__ = ( + "Entry", + "Relation", +) + +@dataclass +class Entry(): + data: EntryData = field(repr = False) + + id: int = field(init = False) + type: str = field(init = False) + name: str = field(init = False) + url: str = field(init = False) + + def __post_init__(self): + entry = self.data + + self.id = entry["mal_id"] + self.type = entry["type"] + self.name = entry["name"] + self.url = entry["url"] + +@dataclass +class Relation(): + data: RelationData = field(repr = False) + + relation: str = field(init = False) + + entries: List[Entry] = field(init = False) + + def __post_init__(self): + self.relation = self.data["relation"] + + entries = self.data["entry"] + + self.entries = [Entry(entry) for entry in entries] \ No newline at end of file diff --git a/anmoku/resources/helpers/review.py b/anmoku/resources/helpers/review.py index 9a1dbe1..2545683 100644 --- a/anmoku/resources/helpers/review.py +++ b/anmoku/resources/helpers/review.py @@ -4,34 +4,16 @@ if TYPE_CHECKING: from typing import List - from ...typing.jikan.review import UserData, ReactionsData + from ...typing.jikan.review import ReactionsData from ...typing.jikan import ReviewData from dataclasses import dataclass, field from datetime import datetime -from .image import Image +from .user import User __all__ = ("Review",) -@dataclass -class User(): - data: UserData = field(repr = False) - - username: str = field(init = False) - """The MyAnimeList Username of the reviewer.""" - url: str = field(init = False) - """The MyAnimeList Profile URL of the reviewer.""" - image: Image = field(init = False) - """The Profile Picture of the reviewer.""" - - def __post_init__(self): - user = self.data - - self.username = user["username"] - self.url = user["url"] - self.image = Image(user["images"]) - @dataclass class Reactions(): data: ReactionsData = field(repr = False) diff --git a/anmoku/resources/helpers/user.py b/anmoku/resources/helpers/user.py new file mode 100644 index 0000000..ff38bbf --- /dev/null +++ b/anmoku/resources/helpers/user.py @@ -0,0 +1,31 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...typing.jikan import PartialUserData + +from dataclasses import dataclass, field + +from .image import Image + +__all__ = ( + "User", +) + +@dataclass +class User(): + data: PartialUserData = field(repr = False) + + username: str = field(init = False) + """The MyAnimeList Username of the reviewer.""" + url: str = field(init = False) + """The MyAnimeList Profile URL of the reviewer.""" + image: Image = field(init = False) + """The Profile Picture of the reviewer.""" + + def __post_init__(self): + user = self.data + + self.username = user["username"] + self.url = user["url"] + self.image = Image(user["images"]) \ No newline at end of file diff --git a/anmoku/resources/manga/__init__.py b/anmoku/resources/manga/__init__.py index 5f00642..7d3a4ec 100644 --- a/anmoku/resources/manga/__init__.py +++ b/anmoku/resources/manga/__init__.py @@ -5,4 +5,7 @@ from .forum import * from .pictures import * from .recommendations import * -from .reviews import * \ No newline at end of file +from .reviews import * +from .user_updates import * +from .relations import * +from .external import * \ No newline at end of file diff --git a/anmoku/resources/manga/external.py b/anmoku/resources/manga/external.py new file mode 100644 index 0000000..da392ca --- /dev/null +++ b/anmoku/resources/manga/external.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + ExternalSourceData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import ExternalSource + +__all__ = ( + "MangaExternal", +) + +@dataclass +class MangaExternal(JikanResource): + _get_endpoint = "/manga/{id}/external" + + data: JikanResponseData[List[ExternalSourceData]] = field(repr = False) + + def __iter__(self): + for relation in self.data["data"]: + yield ExternalSource(relation) \ No newline at end of file diff --git a/anmoku/resources/manga/relations.py b/anmoku/resources/manga/relations.py new file mode 100644 index 0000000..2e4a874 --- /dev/null +++ b/anmoku/resources/manga/relations.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + RelationData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Relation + +__all__ = ( + "MangaRelations", +) + +@dataclass +class MangaRelations(JikanResource): + _get_endpoint = "/manga/{id}/relations" + + data: JikanResponseData[List[RelationData]] = field(repr = False) + + def __iter__(self): + for relation in self.data["data"]: + yield Relation(relation) \ No newline at end of file diff --git a/anmoku/resources/manga/user_updates.py b/anmoku/resources/manga/user_updates.py new file mode 100644 index 0000000..1363bee --- /dev/null +++ b/anmoku/resources/manga/user_updates.py @@ -0,0 +1,60 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, List + +if TYPE_CHECKING: + from ...typing.jikan.manga import MangaUserUpdatesData + from ...typing.jikan.api import JikanResponseData + +from dataclasses import dataclass, field +from datetime import datetime + +from ..base import JikanResource +from ..helpers import User + +__all__ = ( + "MangaUserUpdates", +) + +@dataclass +class UserUpdate(): + data: MangaUserUpdatesData + + user: User = field(init = False) + """The user in this update.""" + score: int = field(init = False) + """The score the user gave the manga.""" + status: str = field(init = False) + """The status, e.g. `Completed`.""" + volumes_read: int = field(init = False) + """The amount of volumes read.""" + volumes_total: int = field(init = False) + """The amount of total volumes.""" + chapters_read: int = field(init = False) + """The amount of chapters read.""" + chapters_total: int = field(init = False) + """The amount of total chapters.""" + date: datetime = field(init = False) + """When the update was made.""" + + def __post_init__(self): + update = self.data + + self.user = User(update["user"]) + self.score = update["score"] + self.status = update["status"] + self.volumes_read = update["volumes_read"] + self.volumes_total = update["volumes_total"] + self.chapters_read = update["chapters_read"] + self.chapters_total = update["chapters_total"] + self.date = datetime.fromisoformat(update["date"]) + +@dataclass +class MangaUserUpdates(JikanResource): + _get_endpoint = "/manga/{id}/userupdates" + + data: JikanResponseData[List[MangaUserUpdatesData]] = field(repr = False) + + def __iter__(self): + + for update in self.data["data"]: + yield UserUpdate(update) \ No newline at end of file diff --git a/anmoku/typing/jikan/__init__.py b/anmoku/typing/jikan/__init__.py index 37bfb78..58c1b7e 100644 --- a/anmoku/typing/jikan/__init__.py +++ b/anmoku/typing/jikan/__init__.py @@ -11,6 +11,9 @@ from .manga import * from .news import * from .forum import * -from .moreinfo import * +from .more_info import * from .recommendations import * -from .review import * \ No newline at end of file +from .review import * +from .user import * +from .entry import * +from .external import * \ No newline at end of file diff --git a/anmoku/typing/jikan/anime/anime.py b/anmoku/typing/jikan/anime/anime.py index 48d3cde..107b5e5 100644 --- a/anmoku/typing/jikan/anime/anime.py +++ b/anmoku/typing/jikan/anime/anime.py @@ -3,6 +3,8 @@ from ...mal import MALRatings from ..datetime import DateRangeData from ..title import TitleData +from ..entry import EntryData, RelationData +from ..external import ExternalSourceData __all__ = ( "AnimeData", @@ -91,20 +93,6 @@ class BroadcastData(TypedDict): timezone: str string: str -class EntryData(TypedDict): - mal_id: int - type: str - name: str - url: str - -class RelationData(TypedDict): - relation: str - entry: List[EntryData] - class ThemeData(TypedDict): openings: List[str] - endings: List[str] - -class ExternalSourceData(TypedDict): - name: str - url: str \ No newline at end of file + endings: List[str] \ No newline at end of file diff --git a/anmoku/typing/jikan/entry.py b/anmoku/typing/jikan/entry.py new file mode 100644 index 0000000..8b8216a --- /dev/null +++ b/anmoku/typing/jikan/entry.py @@ -0,0 +1,20 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, TypedDict + +if TYPE_CHECKING: + from typing import List + +__all__ = ( + "EntryData", + "RelationData", +) + +class EntryData(TypedDict): + mal_id: int + type: str + name: str + url: str + +class RelationData(TypedDict): + relation: str + entry: List[EntryData] \ No newline at end of file diff --git a/anmoku/typing/jikan/external.py b/anmoku/typing/jikan/external.py new file mode 100644 index 0000000..b1281fb --- /dev/null +++ b/anmoku/typing/jikan/external.py @@ -0,0 +1,10 @@ +from __future__ import annotations +from typing import TypedDict + +__all__ = ( + "ExternalSourceData", +) + +class ExternalSourceData(TypedDict): + name: str + url: str \ No newline at end of file diff --git a/anmoku/typing/jikan/manga/__init__.py b/anmoku/typing/jikan/manga/__init__.py index b0b5fe6..7f09881 100644 --- a/anmoku/typing/jikan/manga/__init__.py +++ b/anmoku/typing/jikan/manga/__init__.py @@ -1,3 +1,4 @@ from .manga import * from .characters import * -from .statistics import * \ No newline at end of file +from .statistics import * +from .user_updates import * \ No newline at end of file diff --git a/anmoku/typing/jikan/manga/manga.py b/anmoku/typing/jikan/manga/manga.py index 6dd26a3..fb2ba49 100644 --- a/anmoku/typing/jikan/manga/manga.py +++ b/anmoku/typing/jikan/manga/manga.py @@ -4,6 +4,8 @@ from ..datetime import DateRangeData from ..title import TitleData from ..image import ImagesData +from ..entry import EntryData, RelationData +from ..external import ExternalSourceData __all__ = ( "MangaData", @@ -14,20 +16,6 @@ MangaStatus = Literal["Publishing", "Finished", "Discontinued"] -class EntryData(TypedDict): - mal_id: int - type: str - name: str - url: str - -class RelationData(TypedDict): - relation: str - entry: List[EntryData] - -class ExternalSourceData(TypedDict): - name: str - url: str - class PartialMangaData(TypedDict): mal_id: int url: str diff --git a/anmoku/typing/jikan/manga/user_updates.py b/anmoku/typing/jikan/manga/user_updates.py new file mode 100644 index 0000000..2aa2e62 --- /dev/null +++ b/anmoku/typing/jikan/manga/user_updates.py @@ -0,0 +1,19 @@ +from __future__ import annotations +from typing import TypedDict, TYPE_CHECKING + +if TYPE_CHECKING: + from .. import PartialUserData + +__all__ = ( + "MangaUserUpdatesData", +) + +class MangaUserUpdatesData(TypedDict): + user: PartialUserData + score: int + status: str + volumes_read: int + volumes_total: int + chapters_read: int + chapters_total: int + date: str diff --git a/anmoku/typing/jikan/moreinfo.py b/anmoku/typing/jikan/more_info.py similarity index 100% rename from anmoku/typing/jikan/moreinfo.py rename to anmoku/typing/jikan/more_info.py diff --git a/anmoku/typing/jikan/review.py b/anmoku/typing/jikan/review.py index b6c9b38..124d2ff 100644 --- a/anmoku/typing/jikan/review.py +++ b/anmoku/typing/jikan/review.py @@ -4,17 +4,12 @@ if TYPE_CHECKING: from typing import List - from .image import ImageData + from .user import PartialUserData __all__ = ( "ReviewData", ) -class UserData(TypedDict): - username: str - url: str - images: ImageData - class ReactionsData(TypedDict): overall: int nice: int @@ -26,7 +21,7 @@ class ReactionsData(TypedDict): creative: int class ReviewData(TypedDict): - user: UserData + user: PartialUserData mal_id: int url: str type: str diff --git a/anmoku/typing/jikan/user.py b/anmoku/typing/jikan/user.py new file mode 100644 index 0000000..8fd9086 --- /dev/null +++ b/anmoku/typing/jikan/user.py @@ -0,0 +1,14 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, TypedDict + +if TYPE_CHECKING: + from .image import ImageData + +__all__ = ( + "PartialUserData", +) + +class PartialUserData(TypedDict): + username: str + url: str + images: ImageData \ No newline at end of file From 42b33b3754717185fd667ebe4675e5be5112cd93 Mon Sep 17 00:00:00 2001 From: Ananas Date: Mon, 25 Nov 2024 11:50:23 +0100 Subject: [PATCH 10/21] feat: genres --- anmoku/clients/async_.py | 18 ++++++++++++++++- anmoku/clients/base.py | 11 ++++++++++ anmoku/clients/sync.py | 18 ++++++++++++++++- anmoku/resources/anime/__init__.py | 3 ++- anmoku/resources/anime/anime.py | 2 +- anmoku/resources/anime/genre.py | 29 +++++++++++++++++++++++++++ anmoku/resources/base.py | 2 ++ anmoku/resources/helpers/__init__.py | 3 ++- anmoku/resources/helpers/genre.py | 30 ++++++++++++++++++++++++++++ anmoku/resources/manga/__init__.py | 3 ++- anmoku/resources/manga/genre.py | 29 +++++++++++++++++++++++++++ anmoku/typing/jikan/__init__.py | 3 ++- anmoku/typing/jikan/genre.py | 12 +++++++++++ 13 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 anmoku/resources/anime/genre.py create mode 100644 anmoku/resources/helpers/genre.py create mode 100644 anmoku/resources/manga/genre.py create mode 100644 anmoku/typing/jikan/genre.py diff --git a/anmoku/clients/async_.py b/anmoku/clients/async_.py index e1bb2ab..c99147b 100644 --- a/anmoku/clients/async_.py +++ b/anmoku/clients/async_.py @@ -7,7 +7,12 @@ from ..typing.anmoku import SnowflakeT from ..typing.jikan import SearchResultData - from .base import ResourceGenericT, SearchResourceGenericT, RandomResourceGenericT + from .base import ( + ResourceGenericT, + SearchResourceGenericT, + RandomResourceGenericT, + GenresResourceGenericT + ) from aiohttp import ClientSession from devgoldyutils import Colours @@ -88,6 +93,17 @@ async def random(self, resource: Type[RandomResourceGenericT]) -> RandomResource return resource(json_data) + async def genres(self, resource: Type[GenresResourceGenericT]) -> GenresResourceGenericT: + """Fetches a random object of the specified resource.""" + url = resource._random_endpoint + + if url is None: + raise ResourceNotSupportedError(resource, "random") + + json_data = await self._request(url) + + return resource(json_data) + async def _request( self, route: str, diff --git a/anmoku/clients/base.py b/anmoku/clients/base.py index 0fb5ee8..3efaf6a 100644 --- a/anmoku/clients/base.py +++ b/anmoku/clients/base.py @@ -27,6 +27,12 @@ resources.Manga ) + GenresResourceGenericT = TypeVar( + "GenresResourceGenericT", + resources.AnimeGenres, + resources.MangaGenres + ) + import logging from urllib.parse import quote from abc import ABC, abstractmethod @@ -77,6 +83,11 @@ def search(self, resource: Type[SearchResourceGenericT], query: str) -> SearchRe def random(self, resource: Type[RandomResourceGenericT]) -> RandomResourceGenericT: """Fetches a random object of the specified resource.""" ... + + @abstractmethod + def genres(self, resource: Type[GenresResourceGenericT]) -> GenresResourceGenericT: + """Fetches a genres of the specified resource.""" + ... def _format_url(self, unformatted_url: str, resource: Type[ResourceGenericT], *args: str, **kwargs: str) -> str: """Formats the URL while also taking URL encoding into account.""" diff --git a/anmoku/clients/sync.py b/anmoku/clients/sync.py index 4ab4b6b..1b423f1 100644 --- a/anmoku/clients/sync.py +++ b/anmoku/clients/sync.py @@ -7,7 +7,12 @@ from ..typing.anmoku import SnowflakeT from ..typing.jikan import SearchResultData - from .base import ResourceGenericT, SearchResourceGenericT, RandomResourceGenericT + from .base import ( + ResourceGenericT, + SearchResourceGenericT, + RandomResourceGenericT, + GenresResourceGenericT + ) from requests import Session from devgoldyutils import Colours @@ -86,6 +91,17 @@ def random(self, resource: Type[RandomResourceGenericT]) -> RandomResourceGeneri return resource(json_data) + def genres(self, resource: Type[GenresResourceGenericT]) -> GenresResourceGenericT: + """Fetches a random object of the specified resource.""" + url = resource._genres_endpoint + + if url is None: + raise ResourceNotSupportedError(resource, "random") + + json_data = self._request(url) + + return resource(json_data) + def _request( self, route: str, diff --git a/anmoku/resources/anime/__init__.py b/anmoku/resources/anime/__init__.py index bddcea4..8b04898 100644 --- a/anmoku/resources/anime/__init__.py +++ b/anmoku/resources/anime/__init__.py @@ -8,4 +8,5 @@ from .recommendations import * from .reviews import * from .relations import * -from .external import * \ No newline at end of file +from .external import * +from .genre import * \ No newline at end of file diff --git a/anmoku/resources/anime/anime.py b/anmoku/resources/anime/anime.py index ff5b324..c1addfa 100644 --- a/anmoku/resources/anime/anime.py +++ b/anmoku/resources/anime/anime.py @@ -72,7 +72,7 @@ class Anime(JikanResource): _get_endpoint = "/anime/{id}" _search_endpoint = "/anime" _random_endpoint = "/random/anime" - + _genres_endpoint = "/genres/anime" data: JikanResponseData[AnimeData] = field(repr = False) diff --git a/anmoku/resources/anime/genre.py b/anmoku/resources/anime/genre.py new file mode 100644 index 0000000..82b92ed --- /dev/null +++ b/anmoku/resources/anime/genre.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + GenreData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Genre + +__all__ = ( + "AnimeGenres", +) + +@dataclass +class AnimeGenres(JikanResource): + _genres_endpoint = "/genres/anime" + + data: JikanResponseData[List[GenreData]] = field(repr = False) + + def __iter__(self): + for genre in self.data["data"]: + yield Genre(genre) \ No newline at end of file diff --git a/anmoku/resources/base.py b/anmoku/resources/base.py index 7a48f28..54ad459 100644 --- a/anmoku/resources/base.py +++ b/anmoku/resources/base.py @@ -16,5 +16,7 @@ class JikanResource(): """The jikan api endpoint to search with this resource.""" _random_endpoint: Optional[str] = field(init = False, default = None) """The jikan api endpoint to get a random object with this resource.""" + _genres_endpoint: Optional[str] = field(init = False, default = None) + """The jikan api endpoint where you can get the genres of this resource.""" data: JikanResponseData[Any] \ No newline at end of file diff --git a/anmoku/resources/helpers/__init__.py b/anmoku/resources/helpers/__init__.py index 32db2c0..a37009e 100644 --- a/anmoku/resources/helpers/__init__.py +++ b/anmoku/resources/helpers/__init__.py @@ -9,4 +9,5 @@ from .review import * from .user import * from .relations import * -from .external import * \ No newline at end of file +from .external import * +from .genre import * \ No newline at end of file diff --git a/anmoku/resources/helpers/genre.py b/anmoku/resources/helpers/genre.py new file mode 100644 index 0000000..b7d3a32 --- /dev/null +++ b/anmoku/resources/helpers/genre.py @@ -0,0 +1,30 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...typing.jikan import ( + GenreData, + ) + +from dataclasses import dataclass, field + +__all__ = ( + "Genre", +) + +@dataclass +class Genre(): + data: GenreData = field(repr = False) + + id: int = field(init = False) + name: str = field(init = False) + url: str = field(init = False) + count: int = field(init = False) + + def __post_init__(self): + genre = self.data + + self.id = genre["mal_id"] + self.name = genre["name"] + self.url = genre["url"] + self.count = genre["count"] \ No newline at end of file diff --git a/anmoku/resources/manga/__init__.py b/anmoku/resources/manga/__init__.py index 7d3a4ec..254b6c6 100644 --- a/anmoku/resources/manga/__init__.py +++ b/anmoku/resources/manga/__init__.py @@ -8,4 +8,5 @@ from .reviews import * from .user_updates import * from .relations import * -from .external import * \ No newline at end of file +from .external import * +from .genre import * \ No newline at end of file diff --git a/anmoku/resources/manga/genre.py b/anmoku/resources/manga/genre.py new file mode 100644 index 0000000..e64601f --- /dev/null +++ b/anmoku/resources/manga/genre.py @@ -0,0 +1,29 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + from ...typing.jikan import ( + GenreData, + JikanResponseData, + ) + +from dataclasses import dataclass, field + +from ..base import JikanResource +from ..helpers import Genre + +__all__ = ( + "MangaGenres", +) + +@dataclass +class MangaGenres(JikanResource): + _genres_endpoint = "/genres/manga" + + data: JikanResponseData[List[GenreData]] = field(repr = False) + + def __iter__(self): + for genre in self.data["data"]: + yield Genre(genre) \ No newline at end of file diff --git a/anmoku/typing/jikan/__init__.py b/anmoku/typing/jikan/__init__.py index 58c1b7e..18ad5d0 100644 --- a/anmoku/typing/jikan/__init__.py +++ b/anmoku/typing/jikan/__init__.py @@ -16,4 +16,5 @@ from .review import * from .user import * from .entry import * -from .external import * \ No newline at end of file +from .external import * +from .genre import * \ No newline at end of file diff --git a/anmoku/typing/jikan/genre.py b/anmoku/typing/jikan/genre.py new file mode 100644 index 0000000..ad8f33a --- /dev/null +++ b/anmoku/typing/jikan/genre.py @@ -0,0 +1,12 @@ +from __future__ import annotations +from typing import TypedDict + +__all__ = ( + "GenreData", +) + +class GenreData(TypedDict): + mal_id: int + name: str + url: str + count: int \ No newline at end of file From 31f1e2f0671a4885fdd313ecd2809c11e25f4f87 Mon Sep 17 00:00:00 2001 From: Ananas Date: Mon, 25 Nov 2024 12:01:19 +0100 Subject: [PATCH 11/21] fix: async using random endpoint for genres --- anmoku/clients/async_.py | 4 ++-- anmoku/clients/sync.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/anmoku/clients/async_.py b/anmoku/clients/async_.py index c99147b..2f6dce2 100644 --- a/anmoku/clients/async_.py +++ b/anmoku/clients/async_.py @@ -95,10 +95,10 @@ async def random(self, resource: Type[RandomResourceGenericT]) -> RandomResource async def genres(self, resource: Type[GenresResourceGenericT]) -> GenresResourceGenericT: """Fetches a random object of the specified resource.""" - url = resource._random_endpoint + url = resource._genres_endpoint if url is None: - raise ResourceNotSupportedError(resource, "random") + raise ResourceNotSupportedError(resource, "genres") json_data = await self._request(url) diff --git a/anmoku/clients/sync.py b/anmoku/clients/sync.py index 1b423f1..787e31d 100644 --- a/anmoku/clients/sync.py +++ b/anmoku/clients/sync.py @@ -96,7 +96,7 @@ def genres(self, resource: Type[GenresResourceGenericT]) -> GenresResourceGeneri url = resource._genres_endpoint if url is None: - raise ResourceNotSupportedError(resource, "random") + raise ResourceNotSupportedError(resource, "genres") json_data = self._request(url) From 399a2f663aa49782dbdf489b9d0d0fad766c9cfe Mon Sep 17 00:00:00 2001 From: Goldy <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:41:04 +0000 Subject: [PATCH 12/21] refactor!: merge client.genres functionality into client.get --- anmoku/clients/async_.py | 32 +++++++++++++-------------- anmoku/clients/base.py | 15 +++++-------- anmoku/clients/sync.py | 38 ++++++++++++++++----------------- anmoku/resources/anime/anime.py | 1 - anmoku/resources/anime/genre.py | 2 +- anmoku/resources/base.py | 2 -- anmoku/resources/manga/genre.py | 4 ++-- 7 files changed, 43 insertions(+), 51 deletions(-) diff --git a/anmoku/clients/async_.py b/anmoku/clients/async_.py index 2f6dce2..409ea5f 100644 --- a/anmoku/clients/async_.py +++ b/anmoku/clients/async_.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, overload if TYPE_CHECKING: from typing import Any, Optional, Type, Dict, Tuple @@ -11,7 +11,7 @@ ResourceGenericT, SearchResourceGenericT, RandomResourceGenericT, - GenresResourceGenericT + NoArgsResourceGenericT ) from aiohttp import ClientSession @@ -61,10 +61,21 @@ def __init__( } ) - async def get(self, resource: Type[ResourceGenericT], id: SnowflakeT, **kwargs) -> ResourceGenericT: - """Get's the exact resource by id.""" + @overload + def get(self, resource: Type[NoArgsResourceGenericT]) -> NoArgsResourceGenericT: + ... + + @overload + def get(self, resource: Type[ResourceGenericT], id: SnowflakeT, **kwargs) -> ResourceGenericT: + ... + + async def get(self, resource: Type[ResourceGenericT], id: Optional[SnowflakeT] = None, **kwargs) -> ResourceGenericT: + """Get's the exact resource typically by id.""" + if id is not None: + kwargs["id"] = id + url = self._format_url( - resource._get_endpoint, resource, id = id, **kwargs + resource._get_endpoint, resource, **kwargs ) json_data = await self._request(url) @@ -93,17 +104,6 @@ async def random(self, resource: Type[RandomResourceGenericT]) -> RandomResource return resource(json_data) - async def genres(self, resource: Type[GenresResourceGenericT]) -> GenresResourceGenericT: - """Fetches a random object of the specified resource.""" - url = resource._genres_endpoint - - if url is None: - raise ResourceNotSupportedError(resource, "genres") - - json_data = await self._request(url) - - return resource(json_data) - async def _request( self, route: str, diff --git a/anmoku/clients/base.py b/anmoku/clients/base.py index 3efaf6a..acb6b6b 100644 --- a/anmoku/clients/base.py +++ b/anmoku/clients/base.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, cast if TYPE_CHECKING: - from typing import Any, Mapping, TypeVar, Type + from typing import Any, Mapping, TypeVar, Type, Optional from .. import resources from ..typing.anmoku import SnowflakeT @@ -27,8 +27,8 @@ resources.Manga ) - GenresResourceGenericT = TypeVar( - "GenresResourceGenericT", + NoArgsResourceGenericT = TypeVar( + "NoArgsResourceGenericT", resources.AnimeGenres, resources.MangaGenres ) @@ -70,7 +70,7 @@ def __init__(self, debug: bool = False) -> None: super().__init__() @abstractmethod - def get(self, resource: Type[ResourceGenericT], id: SnowflakeT) -> ResourceGenericT: + def get(self, resource: Type[ResourceGenericT], id: Optional[SnowflakeT] = None, **kwargs) -> ResourceGenericT: """Get's the exact resource by id.""" ... @@ -78,16 +78,11 @@ def get(self, resource: Type[ResourceGenericT], id: SnowflakeT) -> ResourceGener def search(self, resource: Type[SearchResourceGenericT], query: str) -> SearchResult[SearchResourceGenericT]: """Searches for the resource and returns a list of the results.""" ... - + @abstractmethod def random(self, resource: Type[RandomResourceGenericT]) -> RandomResourceGenericT: """Fetches a random object of the specified resource.""" ... - - @abstractmethod - def genres(self, resource: Type[GenresResourceGenericT]) -> GenresResourceGenericT: - """Fetches a genres of the specified resource.""" - ... def _format_url(self, unformatted_url: str, resource: Type[ResourceGenericT], *args: str, **kwargs: str) -> str: """Formats the URL while also taking URL encoding into account.""" diff --git a/anmoku/clients/sync.py b/anmoku/clients/sync.py index 787e31d..1505472 100644 --- a/anmoku/clients/sync.py +++ b/anmoku/clients/sync.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, overload if TYPE_CHECKING: from typing import Any, Optional, Type, Tuple @@ -8,10 +8,10 @@ from ..typing.jikan import SearchResultData from .base import ( - ResourceGenericT, - SearchResourceGenericT, - RandomResourceGenericT, - GenresResourceGenericT + ResourceGenericT, + SearchResourceGenericT, + NoArgsResourceGenericT, + RandomResourceGenericT ) from requests import Session @@ -59,10 +59,21 @@ def __init__( self._second_rate_limiter = TimesPerRateLimiter(second_rate_limits[0], second_rate_limits[1]) self._minute_rate_limiter = TimesPerRateLimiter(minute_rate_limits[0], minute_rate_limits[1]) + @overload + def get(self, resource: Type[NoArgsResourceGenericT]) -> NoArgsResourceGenericT: + ... + + @overload def get(self, resource: Type[ResourceGenericT], id: SnowflakeT, **kwargs) -> ResourceGenericT: - """Get's the exact resource by id.""" + ... + + def get(self, resource: Type[ResourceGenericT], id: Optional[SnowflakeT] = None, **kwargs) -> ResourceGenericT: + """Get's the exact resource typically by id.""" + if id is not None: + kwargs["id"] = id + url = self._format_url( - resource._get_endpoint, resource, id = id, **kwargs + resource._get_endpoint, resource, **kwargs ) json_data = self._request(url) @@ -79,7 +90,7 @@ def search(self, resource: Type[SearchResourceGenericT], query: str) -> SearchRe json_data: SearchResultData[Any] = self._request(url, params = {"q": query}) return SearchResult(json_data, resource) - + def random(self, resource: Type[RandomResourceGenericT]) -> RandomResourceGenericT: """Fetches a random object of the specified resource.""" url = resource._random_endpoint @@ -91,17 +102,6 @@ def random(self, resource: Type[RandomResourceGenericT]) -> RandomResourceGeneri return resource(json_data) - def genres(self, resource: Type[GenresResourceGenericT]) -> GenresResourceGenericT: - """Fetches a random object of the specified resource.""" - url = resource._genres_endpoint - - if url is None: - raise ResourceNotSupportedError(resource, "genres") - - json_data = self._request(url) - - return resource(json_data) - def _request( self, route: str, diff --git a/anmoku/resources/anime/anime.py b/anmoku/resources/anime/anime.py index c1addfa..4742dd7 100644 --- a/anmoku/resources/anime/anime.py +++ b/anmoku/resources/anime/anime.py @@ -72,7 +72,6 @@ class Anime(JikanResource): _get_endpoint = "/anime/{id}" _search_endpoint = "/anime" _random_endpoint = "/random/anime" - _genres_endpoint = "/genres/anime" data: JikanResponseData[AnimeData] = field(repr = False) diff --git a/anmoku/resources/anime/genre.py b/anmoku/resources/anime/genre.py index 82b92ed..19e51f5 100644 --- a/anmoku/resources/anime/genre.py +++ b/anmoku/resources/anime/genre.py @@ -20,7 +20,7 @@ @dataclass class AnimeGenres(JikanResource): - _genres_endpoint = "/genres/anime" + _get_endpoint = "/genres/anime" data: JikanResponseData[List[GenreData]] = field(repr = False) diff --git a/anmoku/resources/base.py b/anmoku/resources/base.py index 54ad459..7a48f28 100644 --- a/anmoku/resources/base.py +++ b/anmoku/resources/base.py @@ -16,7 +16,5 @@ class JikanResource(): """The jikan api endpoint to search with this resource.""" _random_endpoint: Optional[str] = field(init = False, default = None) """The jikan api endpoint to get a random object with this resource.""" - _genres_endpoint: Optional[str] = field(init = False, default = None) - """The jikan api endpoint where you can get the genres of this resource.""" data: JikanResponseData[Any] \ No newline at end of file diff --git a/anmoku/resources/manga/genre.py b/anmoku/resources/manga/genre.py index e64601f..f1a158b 100644 --- a/anmoku/resources/manga/genre.py +++ b/anmoku/resources/manga/genre.py @@ -20,10 +20,10 @@ @dataclass class MangaGenres(JikanResource): - _genres_endpoint = "/genres/manga" + _get_endpoint = "/genres/manga" data: JikanResponseData[List[GenreData]] = field(repr = False) - + def __iter__(self): for genre in self.data["data"]: yield Genre(genre) \ No newline at end of file From 6c33db2a356cd0af7ae19441a93ef2ca438dbb2d Mon Sep 17 00:00:00 2001 From: Ananas Date: Mon, 25 Nov 2024 14:42:43 +0100 Subject: [PATCH 13/21] feat: add 'On Hiatus' --- anmoku/resources/manga/manga.py | 1 + anmoku/typing/jikan/manga/manga.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/anmoku/resources/manga/manga.py b/anmoku/resources/manga/manga.py index 4e40b0f..7bd49aa 100644 --- a/anmoku/resources/manga/manga.py +++ b/anmoku/resources/manga/manga.py @@ -34,6 +34,7 @@ class PublishingStatus(Enum): FINISHED = "Finished" PUBLISHING = "Publishing" DISCONTINUED = "Discontinued" + ON_HIATUS = "On Hiatus" def __init__(self, value: MangaStatus) -> None: ... diff --git a/anmoku/typing/jikan/manga/manga.py b/anmoku/typing/jikan/manga/manga.py index fb2ba49..2f98c91 100644 --- a/anmoku/typing/jikan/manga/manga.py +++ b/anmoku/typing/jikan/manga/manga.py @@ -14,7 +14,7 @@ "MoreInfoMangaData" ) -MangaStatus = Literal["Publishing", "Finished", "Discontinued"] +MangaStatus = Literal["Publishing", "Finished", "Discontinued", "On Hiatus"] class PartialMangaData(TypedDict): mal_id: int From d680cd19ed61e52b9b67bc2e4932e13c0103b05f Mon Sep 17 00:00:00 2001 From: Goldy <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:49:53 +0000 Subject: [PATCH 14/21] feat: add `JikanIterableResource` object --- anmoku/resources/anime/characters.py | 23 +++++++++-------- anmoku/resources/anime/episodes.py | 16 +++++++----- anmoku/resources/base.py | 38 +++++++++++++++++++++++----- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/anmoku/resources/anime/characters.py b/anmoku/resources/anime/characters.py index 54bcea4..f8b7c12 100644 --- a/anmoku/resources/anime/characters.py +++ b/anmoku/resources/anime/characters.py @@ -2,25 +2,22 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List - from ...typing.jikan import ( - AnimeCharacterData, - JikanResponseData - ) + from typing import List, Generator, Any + from ...typing.jikan import AnimeCharacterData, JikanResponseData from ...typing.jikan.anime.characters import VoiceActorData from dataclasses import dataclass, field from ..helpers import Image -from ..base import JikanResource +from ..base import JikanIterableResource __all__ = ( "AnimeCharacters", ) @dataclass -class AnimeCharacters(JikanResource): +class AnimeCharacters(JikanIterableResource): """ Get data of the characters from a particular anime. @@ -37,7 +34,7 @@ class AnimeCharacters(JikanResource): anime_characters = client.get(AnimeCharacters, id = 28851) # ID for the anime film "A Silent Voice". for character in anime_characters: - print(f"{character.name} ({character.url})") + print(f"{character.name} ({character.url})") client.close() """ @@ -45,10 +42,14 @@ class AnimeCharacters(JikanResource): data: JikanResponseData[List[AnimeCharacterData]] - def __iter__(self): + def __post_init__(self): + super().__post_init__(AnimeCharacter) + + def __next__(self) -> AnimeCharacter: + return super().__next__() - for character in self.data["data"]: - yield AnimeCharacter(character) + def __iter__(self) -> Generator[AnimeCharacter, Any, None]: + return super().__iter__() @dataclass class AnimeCharacter(): diff --git a/anmoku/resources/anime/episodes.py b/anmoku/resources/anime/episodes.py index ab40e60..a4ad918 100644 --- a/anmoku/resources/anime/episodes.py +++ b/anmoku/resources/anime/episodes.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List, Optional + from typing import List, Optional, Generator, Any from ...typing.jikan import ( AnimeEpisodeData, SingleAnimeEpisodeData, @@ -14,7 +14,7 @@ from dataclasses import dataclass, field from ..helpers import Title -from ..base import JikanResource +from ..base import JikanResource, JikanIterableResource __all__ = ( "AnimeEpisodes", @@ -22,16 +22,20 @@ ) @dataclass -class AnimeEpisodes(JikanResource): +class AnimeEpisodes(JikanIterableResource): """Get an anime's episodes with anime's ID.""" _get_endpoint = "/anime/{id}/episodes" data: JikanPageResponseData[List[AnimeEpisodeData]] - def __iter__(self): + def __post_init__(self): + super().__post_init__(AnimeEpisode) + + def __next__(self) -> AnimeEpisode: + return super().__next__() - for episode in self.data["data"]: - yield AnimeEpisode(episode) + def __iter__(self) -> Generator[AnimeEpisode, Any, None]: + return super().__iter__() @dataclass class SingleAnimeEpisode(JikanResource): diff --git a/anmoku/resources/base.py b/anmoku/resources/base.py index 7a48f28..e4281b0 100644 --- a/anmoku/resources/base.py +++ b/anmoku/resources/base.py @@ -1,20 +1,46 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, TypeVar, List, Type if TYPE_CHECKING: + from typing import Generator + from ..typing.jikan import JikanResponseData from dataclasses import dataclass, field -__all__ = ("JikanResource",) +__all__ = ( + "JikanResource", + "JikanIterableResource" +) + +T = TypeVar("T") @dataclass class JikanResource(): - _get_endpoint: Optional[str] = field(init = False, default = None) + _get_endpoint: Optional[str] = field(init = False, repr = False, default = None) """The jikan api endpoint where you can get this object.""" - _search_endpoint: Optional[str] = field(init = False, default = None) + _search_endpoint: Optional[str] = field(init = False, repr = False, default = None) """The jikan api endpoint to search with this resource.""" - _random_endpoint: Optional[str] = field(init = False, default = None) + _random_endpoint: Optional[str] = field(init = False, repr = False, default = None) """The jikan api endpoint to get a random object with this resource.""" - data: JikanResponseData[Any] \ No newline at end of file + data: JikanResponseData[Any] + +@dataclass +class JikanIterableResource(JikanResource): + __object: Type[T] = field(repr = False, init = False) + __object_iter: List[Any] = field(repr = False, init = False) + + def __post_init__(self, object: object): + self.__object = object + self.__object_iter = iter(self.data["data"]) + + def __next__(self) -> T: + return self.__object( + next(self.__object_iter) + ) + + def __iter__(self) -> Generator[T, Any, None]: + + for data in self.__object_iter: + yield self.__object(data) \ No newline at end of file From 0699950331376168b25ae00231193e66ec0ff7ab Mon Sep 17 00:00:00 2001 From: Goldy <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:55:37 +0000 Subject: [PATCH 15/21] docs: add required params for resources --- anmoku/resources/anime/anime.py | 4 ++++ anmoku/resources/anime/characters.py | 4 ++++ anmoku/resources/anime/episodes.py | 10 +++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/anmoku/resources/anime/anime.py b/anmoku/resources/anime/anime.py index 4742dd7..8397fe5 100644 --- a/anmoku/resources/anime/anime.py +++ b/anmoku/resources/anime/anime.py @@ -50,6 +50,10 @@ class Anime(JikanResource): """ Get or search for anime. + Required Params + ----------------- + * `id` - Anime ID + ------------ ⭐ Example: diff --git a/anmoku/resources/anime/characters.py b/anmoku/resources/anime/characters.py index f8b7c12..4794151 100644 --- a/anmoku/resources/anime/characters.py +++ b/anmoku/resources/anime/characters.py @@ -21,6 +21,10 @@ class AnimeCharacters(JikanIterableResource): """ Get data of the characters from a particular anime. + Required Params + ----------------- + * `id` - Anime ID + ------------ ⭐ Example: diff --git a/anmoku/resources/anime/episodes.py b/anmoku/resources/anime/episodes.py index a4ad918..d7a99ab 100644 --- a/anmoku/resources/anime/episodes.py +++ b/anmoku/resources/anime/episodes.py @@ -23,7 +23,15 @@ @dataclass class AnimeEpisodes(JikanIterableResource): - """Get an anime's episodes with anime's ID.""" + """ + Get an anime's episodes with anime's ID. + + ----------------- + + Required Params + ----------------- + * `id` - Anime ID + """ _get_endpoint = "/anime/{id}/episodes" data: JikanPageResponseData[List[AnimeEpisodeData]] From ceefd1502a8dd4feecda032f9b62d3d07b6d3ecc Mon Sep 17 00:00:00 2001 From: Goldy <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:51:03 +0000 Subject: [PATCH 16/21] refactor!, docs: improve attributes and type hinting and add some docs --- anmoku/resources/helpers/author.py | 12 +++++ anmoku/resources/helpers/forum.py | 60 ++++++++++++----------- anmoku/resources/helpers/news.py | 23 ++++----- anmoku/resources/manga/external.py | 2 +- anmoku/resources/manga/forum.py | 2 +- anmoku/resources/manga/manga.py | 16 ++++-- anmoku/resources/manga/news.py | 2 +- anmoku/resources/manga/user_updates.py | 12 ++--- anmoku/typing/jikan/manga/user_updates.py | 4 +- 9 files changed, 77 insertions(+), 56 deletions(-) create mode 100644 anmoku/resources/helpers/author.py diff --git a/anmoku/resources/helpers/author.py b/anmoku/resources/helpers/author.py new file mode 100644 index 0000000..50367fc --- /dev/null +++ b/anmoku/resources/helpers/author.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass + +__all__ = ( + "Author", +) + +@dataclass +class Author(): + url: str + """The profile URL of the author""" + username: str + """The username of the author.""" \ No newline at end of file diff --git a/anmoku/resources/helpers/forum.py b/anmoku/resources/helpers/forum.py index a43bb58..5a65b99 100644 --- a/anmoku/resources/helpers/forum.py +++ b/anmoku/resources/helpers/forum.py @@ -11,31 +11,12 @@ from dataclasses import dataclass, field +from .author import Author + __all__ = ( "Forum", ) -@dataclass -class LastComment(): - data: LastCommentData = field(repr = False) - - url: str = field(init = False) - """The MyAnimeList URL for this comment.""" - author_username: str = field(init = False) - """The username of the author who wrote this forum article.""" - author_url: str = field(init = False) - """The profile URL of the author on MyAnimeList.""" - date: datetime = field(init = False) - """The date when this was commented.""" - - def __post_init__(self): - comment = self.data - - self.url = comment["url"] - self.author_username = comment["author_username"] - self.author_url = comment["author_url"] - self.date = datetime.fromisoformat(comment["date"]) - @dataclass class Forum(): data: ForumData = field(repr = False) @@ -50,15 +31,13 @@ class Forum(): """Alias to ``Forum.title``.""" date: datetime = field(init = False) """The publication date of this forum article.""" - author_username: str = field(init = False) - """The username of the author who wrote this forum article.""" - author_url: str = field(init = False) - """The profile URL of the author on MyAnimeList.""" + author: Author = field(init = False) + """The author who wrote this forum article.""" comments: int = field(init = False) """The amount of comments on this forum article.""" last_comment: LastComment = field(init = False, default = None) """Last comment on that forum article.""" - + def __post_init__(self): forum = self.data @@ -67,11 +46,34 @@ def __post_init__(self): self.title = forum["title"] self.name = self.title self.date = datetime.fromisoformat(forum["date"]) - self.author_username = forum["author_username"] - self.author_url = forum["author_url"] + self.author = Author( + url = forum["author_url"], + username = forum["author_username"] + ) self.comments = forum["comments"] last_comment = forum["last_comment"] if last_comment is not None: - self.last_comment = LastComment(last_comment) \ No newline at end of file + self.last_comment = LastComment(last_comment) + +@dataclass +class LastComment(): + data: LastCommentData = field(repr = False) + + url: str = field(init = False) + """The MyAnimeList URL for this comment.""" + author: Author = field(init = False) + """The author who wrote this forum article.""" + date: datetime = field(init = False) + """The date when this was commented.""" + + def __post_init__(self): + comment = self.data + + self.url = comment["url"] + self.author = Author( + url = comment["author_url"], + username = comment["author_username"] + ) + self.date = datetime.fromisoformat(comment["date"]) \ No newline at end of file diff --git a/anmoku/resources/helpers/news.py b/anmoku/resources/helpers/news.py index 61a2258..3087b55 100644 --- a/anmoku/resources/helpers/news.py +++ b/anmoku/resources/helpers/news.py @@ -10,7 +10,8 @@ from dataclasses import dataclass, field -from ..helpers import Image +from .image import Image +from .author import Author __all__ = ( "News", @@ -30,19 +31,17 @@ class News(): """Alias to ``News.title``.""" date: datetime = field(init = False) """The publication date of this news article.""" - author_username: str = field(init = False) - """The username of the author who wrote this news article.""" - author_url: str = field(init = False) - """The profile URL of the author on MyAnimeList.""" - forum_str: str = field(init = False) - """The profile URL of the author on MyAnimeList.""" + author: Author = field(init = False) + """The author who wrote this news article.""" + forum_url: str = field(init = False) + """The URL to the news article's forum post.""" image: Image = field(init = False) """The banner image of this news article.""" comments: int = field(init = False) """The amount of comments on this news article.""" excerpt: str = field(init = False) """A brief preview of the news article content.""" - + def __post_init__(self): news = self.data @@ -51,9 +50,11 @@ def __post_init__(self): self.title = news["title"] self.name = self.title self.date = datetime.fromisoformat(news["date"]) - self.author_username = news["author_username"] - self.author_url = news["author_url"] - self.forum_str = news["forum_url"] + self.author = Author( + url = news["author_url"], + username = news["author_username"] + ) + self.forum_url = news["forum_url"] self.image = Image(news["images"]) self.comments = news["comments"] self.excerpt = news["excerpt"] \ No newline at end of file diff --git a/anmoku/resources/manga/external.py b/anmoku/resources/manga/external.py index da392ca..d51f1f2 100644 --- a/anmoku/resources/manga/external.py +++ b/anmoku/resources/manga/external.py @@ -23,7 +23,7 @@ class MangaExternal(JikanResource): _get_endpoint = "/manga/{id}/external" data: JikanResponseData[List[ExternalSourceData]] = field(repr = False) - + def __iter__(self): for relation in self.data["data"]: yield ExternalSource(relation) \ No newline at end of file diff --git a/anmoku/resources/manga/forum.py b/anmoku/resources/manga/forum.py index a3631b6..054acc2 100644 --- a/anmoku/resources/manga/forum.py +++ b/anmoku/resources/manga/forum.py @@ -25,7 +25,7 @@ class MangaTopics(JikanResource): _get_endpoint = "/manga/{id}/forum" data: JikanResponseData[List[ForumData]] = field(repr = False) - + def __iter__(self): for forum in self.data["data"]: yield Forum(forum) diff --git a/anmoku/resources/manga/manga.py b/anmoku/resources/manga/manga.py index 7bd49aa..141610c 100644 --- a/anmoku/resources/manga/manga.py +++ b/anmoku/resources/manga/manga.py @@ -44,6 +44,14 @@ class Manga(JikanResource): """ Get or search for manga. + [`jikan`_] + + .. _jikan: https://docs.api.jikan.moe/#tag/manga/operation/getMangaById + + Required Params + ----------------- + * `id` - Manga ID + ------------ ⭐ Example: @@ -103,11 +111,10 @@ def __post_init__(self): self.approved = manga["approved"] self.title = Title(manga["titles"]) self.name = self.title + self.chapters = manga["chapters"] + self.volumes = manga["volumes"] - self.chapters = manga.get("chapters") - self.volumes = manga.get("volumes") - - status = manga.get("status") + status = manga["status"] if status is not None: self.status = PublishingStatus(status) @@ -116,7 +123,6 @@ def __post_init__(self): self.score = manga["score"] self.scored_by = manga["scored_by"] - @dataclass class FullManga(Manga): _get_endpoint = "/manga/{id}/full" diff --git a/anmoku/resources/manga/news.py b/anmoku/resources/manga/news.py index ed40398..94b3e0c 100644 --- a/anmoku/resources/manga/news.py +++ b/anmoku/resources/manga/news.py @@ -23,7 +23,7 @@ class MangaNews(JikanResource): _get_endpoint = "/manga/{id}/news" data: JikanResponseData[List[NewsData]] = field(repr = False) - + def __iter__(self): for news in self.data["data"]: yield News(news) \ No newline at end of file diff --git a/anmoku/resources/manga/user_updates.py b/anmoku/resources/manga/user_updates.py index 1363bee..7f5140a 100644 --- a/anmoku/resources/manga/user_updates.py +++ b/anmoku/resources/manga/user_updates.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional if TYPE_CHECKING: from ...typing.jikan.manga import MangaUserUpdatesData @@ -21,17 +21,17 @@ class UserUpdate(): user: User = field(init = False) """The user in this update.""" - score: int = field(init = False) + score: Optional[int] = field(init = False) """The score the user gave the manga.""" status: str = field(init = False) """The status, e.g. `Completed`.""" volumes_read: int = field(init = False) """The amount of volumes read.""" - volumes_total: int = field(init = False) + total_volumes: int = field(init = False) """The amount of total volumes.""" chapters_read: int = field(init = False) """The amount of chapters read.""" - chapters_total: int = field(init = False) + total_chapters: int = field(init = False) """The amount of total chapters.""" date: datetime = field(init = False) """When the update was made.""" @@ -43,9 +43,9 @@ def __post_init__(self): self.score = update["score"] self.status = update["status"] self.volumes_read = update["volumes_read"] - self.volumes_total = update["volumes_total"] + self.total_volumes = update["volumes_total"] self.chapters_read = update["chapters_read"] - self.chapters_total = update["chapters_total"] + self.total_chapters = update["chapters_total"] self.date = datetime.fromisoformat(update["date"]) @dataclass diff --git a/anmoku/typing/jikan/manga/user_updates.py b/anmoku/typing/jikan/manga/user_updates.py index 2aa2e62..9823ccf 100644 --- a/anmoku/typing/jikan/manga/user_updates.py +++ b/anmoku/typing/jikan/manga/user_updates.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TypedDict, TYPE_CHECKING +from typing import TypedDict, TYPE_CHECKING, Optional if TYPE_CHECKING: from .. import PartialUserData @@ -10,7 +10,7 @@ class MangaUserUpdatesData(TypedDict): user: PartialUserData - score: int + score: Optional[int] status: str volumes_read: int volumes_total: int From 1ec3414c1a405346a3a6bb63d1d420d6fadb1e35 Mon Sep 17 00:00:00 2001 From: Ananas Date: Mon, 25 Nov 2024 22:04:13 +0100 Subject: [PATCH 17/21] refactor: switch to JikanIterableResource --- anmoku/resources/anime/external.py | 21 +++++++++++++-------- anmoku/resources/anime/forum.py | 22 +++++++++++++--------- anmoku/resources/anime/genre.py | 23 ++++++++++++++--------- anmoku/resources/anime/news.py | 23 ++++++++++++++--------- anmoku/resources/anime/pictures.py | 21 +++++++++++++-------- anmoku/resources/anime/recommendations.py | 22 ++++++++++++++-------- anmoku/resources/anime/relations.py | 21 +++++++++++++-------- anmoku/resources/anime/reviews.py | 21 +++++++++++++-------- anmoku/resources/manga/character.py | 18 ++++++++++++------ anmoku/resources/manga/external.py | 21 +++++++++++++-------- anmoku/resources/manga/forum.py | 21 +++++++++++++-------- anmoku/resources/manga/genre.py | 21 +++++++++++++-------- anmoku/resources/manga/news.py | 21 +++++++++++++-------- anmoku/resources/manga/pictures.py | 21 +++++++++++++-------- anmoku/resources/manga/recommendations.py | 21 ++++++++++++++------- anmoku/resources/manga/relations.py | 21 +++++++++++++-------- anmoku/resources/manga/reviews.py | 21 +++++++++++++-------- anmoku/resources/manga/user_updates.py | 20 +++++++++++++------- 18 files changed, 237 insertions(+), 143 deletions(-) diff --git a/anmoku/resources/anime/external.py b/anmoku/resources/anime/external.py index db22cf9..76ad4ba 100644 --- a/anmoku/resources/anime/external.py +++ b/anmoku/resources/anime/external.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( ExternalSourceData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import ExternalSource __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class AnimeExternal(JikanResource): +class AnimeExternal(JikanIterableResource): _get_endpoint = "/anime/{id}/external" - data: JikanResponseData[List[ExternalSourceData]] = field(repr = False) + data: JikanResponseData[List[ExternalSourceData]] - def __iter__(self): - for relation in self.data["data"]: - yield ExternalSource(relation) \ No newline at end of file + def __post_init__(self): + super().__post_init__(ExternalSource) + + def __next__(self) -> ExternalSource: + return super().__next__() + + def __iter__(self) -> Generator[ExternalSource, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/anime/forum.py b/anmoku/resources/anime/forum.py index cc19665..1bb3f59 100644 --- a/anmoku/resources/anime/forum.py +++ b/anmoku/resources/anime/forum.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( ForumData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Forum __all__ = ( @@ -19,15 +19,19 @@ "AnimeForum" ) - @dataclass -class AnimeForum(JikanResource): +class AnimeForum(JikanIterableResource): _get_endpoint = "/anime/{id}/forum" - data: JikanResponseData[List[ForumData]] = field(repr = False) + data: JikanResponseData[List[ForumData]] - def __iter__(self): - for forum in self.data["data"]: - yield Forum(forum) + def __post_init__(self): + super().__post_init__(Forum) + + def __next__(self) -> Forum: + return super().__next__() + + def __iter__(self) -> Generator[Forum, Any, None]: + return super().__iter__() AnimeTopics = AnimeForum \ No newline at end of file diff --git a/anmoku/resources/anime/genre.py b/anmoku/resources/anime/genre.py index 19e51f5..e44d80c 100644 --- a/anmoku/resources/anime/genre.py +++ b/anmoku/resources/anime/genre.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( GenreData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Genre __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class AnimeGenres(JikanResource): +class AnimeGenres(JikanIterableResource): _get_endpoint = "/genres/anime" - data: JikanResponseData[List[GenreData]] = field(repr = False) - - def __iter__(self): - for genre in self.data["data"]: - yield Genre(genre) \ No newline at end of file + data: JikanResponseData[List[GenreData]] + + def __post_init__(self): + super().__post_init__(Genre) + + def __next__(self) -> Genre: + return super().__next__() + + def __iter__(self) -> Generator[Genre, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/anime/news.py b/anmoku/resources/anime/news.py index 18b8cdd..2c092da 100644 --- a/anmoku/resources/anime/news.py +++ b/anmoku/resources/anime/news.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( NewsData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import News __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class AnimeNews(JikanResource): +class AnimeNews(JikanIterableResource): _get_endpoint = "/anime/{id}/news" - data: JikanResponseData[List[NewsData]] = field(repr = False) - - def __iter__(self): - for news in self.data["data"]: - yield News(news) \ No newline at end of file + data: JikanResponseData[List[NewsData]] + + def __post_init__(self): + super().__post_init__(News) + + def __next__(self) -> News: + return super().__next__() + + def __iter__(self) -> Generator[News, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/anime/pictures.py b/anmoku/resources/anime/pictures.py index df2cce4..a833e6b 100644 --- a/anmoku/resources/anime/pictures.py +++ b/anmoku/resources/anime/pictures.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( ImagesData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Image __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class AnimePictures(JikanResource): +class AnimePictures(JikanIterableResource): _get_endpoint = "/anime/{id}/pictures" - data: JikanResponseData[List[ImagesData]] = field(repr = False) + data: JikanResponseData[List[ImagesData]] - def __iter__(self): - for picture in self.data["data"]: - yield Image(picture) \ No newline at end of file + def __post_init__(self): + super().__post_init__(Image) + + def __next__(self) -> Image: + return super().__next__() + + def __iter__(self) -> Generator[Image, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/anime/recommendations.py b/anmoku/resources/anime/recommendations.py index 0e43ff6..23635eb 100644 --- a/anmoku/resources/anime/recommendations.py +++ b/anmoku/resources/anime/recommendations.py @@ -1,16 +1,17 @@ from __future__ import annotations from typing import TYPE_CHECKING - if TYPE_CHECKING: + from typing import List, Generator, Any + from ...typing.jikan import ( RecommendationsData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Recommendation __all__ = ( @@ -18,11 +19,16 @@ ) @dataclass -class AnimeRecommendations(JikanResource): +class AnimeRecommendations(JikanIterableResource): _get_endpoint = "/anime/{id}/recommendations" - data: JikanResponseData[RecommendationsData] = field(repr = False) + data: JikanResponseData[List[RecommendationsData]] - def __iter__(self): - for recommendation in self.data["data"]: - yield Recommendation(recommendation) \ No newline at end of file + def __post_init__(self): + super().__post_init__(Recommendation) + + def __next__(self) -> Recommendation: + return super().__next__() + + def __iter__(self) -> Generator[Recommendation, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/anime/relations.py b/anmoku/resources/anime/relations.py index cb99c29..d29b499 100644 --- a/anmoku/resources/anime/relations.py +++ b/anmoku/resources/anime/relations.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( RelationData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Relation __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class AnimeRelations(JikanResource): +class AnimeRelations(JikanIterableResource): _get_endpoint = "/anime/{id}/relations" - data: JikanResponseData[List[RelationData]] = field(repr = False) + data: JikanResponseData[List[RelationData]] - def __iter__(self): - for relation in self.data["data"]: - yield Relation(relation) \ No newline at end of file + def __post_init__(self): + super().__post_init__(Relation) + + def __next__(self) -> Relation: + return super().__next__() + + def __iter__(self) -> Generator[Relation, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/anime/reviews.py b/anmoku/resources/anime/reviews.py index 40ad0ba..a5ec2d2 100644 --- a/anmoku/resources/anime/reviews.py +++ b/anmoku/resources/anime/reviews.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( ReviewData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Review __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class AnimeReviews(JikanResource): +class AnimeReviews(JikanIterableResource): _get_endpoint = "/anime/{id}/reviews" - data: JikanResponseData[List[ReviewData]] = field(repr = False) + data: JikanResponseData[List[ReviewData]] - def __iter__(self): - for review in self.data["data"]: - yield Review(review) \ No newline at end of file + def __post_init__(self): + super().__post_init__(Review) + + def __next__(self) -> Review: + return super().__next__() + + def __iter__(self) -> Generator[Review, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/manga/character.py b/anmoku/resources/manga/character.py index bc6a960..5824cdc 100644 --- a/anmoku/resources/manga/character.py +++ b/anmoku/resources/manga/character.py @@ -1,13 +1,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING if TYPE_CHECKING: + from typing import List, Generator, Any + from ...typing.jikan.manga.characters import MangaCharacterData from ...typing.jikan.api import JikanResponseData from dataclasses import dataclass, field -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers.image import Image __all__ = ( @@ -42,13 +44,17 @@ def __post_init__(self): self.role = self.data["role"] @dataclass -class MangaCharacters(JikanResource): +class MangaCharacters(JikanIterableResource): """Get data of the characters from a particular manga.""" _get_endpoint = "/manga/{id}/characters" data: JikanResponseData[List[MangaCharacterData]] - def __iter__(self): + def __post_init__(self): + super().__post_init__(MangaCharacter) + + def __next__(self) -> MangaCharacter: + return super().__next__() - for character in self.data["data"]: - yield MangaCharacter(character) \ No newline at end of file + def __iter__(self) -> Generator[MangaCharacter, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/manga/external.py b/anmoku/resources/manga/external.py index d51f1f2..818c9f0 100644 --- a/anmoku/resources/manga/external.py +++ b/anmoku/resources/manga/external.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( ExternalSourceData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import ExternalSource __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class MangaExternal(JikanResource): +class MangaExternal(JikanIterableResource): _get_endpoint = "/manga/{id}/external" - data: JikanResponseData[List[ExternalSourceData]] = field(repr = False) + data: JikanResponseData[List[ExternalSourceData]] - def __iter__(self): - for relation in self.data["data"]: - yield ExternalSource(relation) \ No newline at end of file + def __post_init__(self): + super().__post_init__(ExternalSource) + + def __next__(self) -> ExternalSource: + return super().__next__() + + def __iter__(self) -> Generator[ExternalSource, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/manga/forum.py b/anmoku/resources/manga/forum.py index 054acc2..78b6295 100644 --- a/anmoku/resources/manga/forum.py +++ b/anmoku/resources/manga/forum.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( ForumData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Forum __all__ = ( @@ -21,13 +21,18 @@ @dataclass -class MangaTopics(JikanResource): +class MangaTopics(JikanIterableResource): _get_endpoint = "/manga/{id}/forum" - data: JikanResponseData[List[ForumData]] = field(repr = False) + data: JikanResponseData[List[ForumData]] - def __iter__(self): - for forum in self.data["data"]: - yield Forum(forum) + def __post_init__(self): + super().__post_init__(Forum) + + def __next__(self) -> Forum: + return super().__next__() + + def __iter__(self) -> Generator[Forum, Any, None]: + return super().__iter__() MangaForum = MangaTopics \ No newline at end of file diff --git a/anmoku/resources/manga/genre.py b/anmoku/resources/manga/genre.py index f1a158b..8d1a2d5 100644 --- a/anmoku/resources/manga/genre.py +++ b/anmoku/resources/manga/genre.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( GenreData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Genre __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class MangaGenres(JikanResource): +class MangaGenres(JikanIterableResource): _get_endpoint = "/genres/manga" - data: JikanResponseData[List[GenreData]] = field(repr = False) + data: JikanResponseData[List[GenreData]] - def __iter__(self): - for genre in self.data["data"]: - yield Genre(genre) \ No newline at end of file + def __post_init__(self): + super().__post_init__(Genre) + + def __next__(self) -> Genre: + return super().__next__() + + def __iter__(self) -> Generator[Genre, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/manga/news.py b/anmoku/resources/manga/news.py index 94b3e0c..b381093 100644 --- a/anmoku/resources/manga/news.py +++ b/anmoku/resources/manga/news.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( NewsData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import News __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class MangaNews(JikanResource): +class MangaNews(JikanIterableResource): _get_endpoint = "/manga/{id}/news" - data: JikanResponseData[List[NewsData]] = field(repr = False) + data: JikanResponseData[List[NewsData]] - def __iter__(self): - for news in self.data["data"]: - yield News(news) \ No newline at end of file + def __post_init__(self): + super().__post_init__(News) + + def __next__(self) -> News: + return super().__next__() + + def __iter__(self) -> Generator[News, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/manga/pictures.py b/anmoku/resources/manga/pictures.py index b44b7a4..615fa8c 100644 --- a/anmoku/resources/manga/pictures.py +++ b/anmoku/resources/manga/pictures.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( ImagesData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Image __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class MangaPictures(JikanResource): +class MangaPictures(JikanIterableResource): _get_endpoint = "/manga/{id}/pictures" - data: JikanResponseData[List[ImagesData]] = field(repr = False) + data: JikanResponseData[List[ImagesData]] - def __iter__(self): - for picture in self.data["data"]: - yield Image(picture) \ No newline at end of file + def __post_init__(self): + super().__post_init__(Image) + + def __next__(self) -> Image: + return super().__next__() + + def __iter__(self) -> Generator[Image, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/manga/recommendations.py b/anmoku/resources/manga/recommendations.py index 9501623..39af2b6 100644 --- a/anmoku/resources/manga/recommendations.py +++ b/anmoku/resources/manga/recommendations.py @@ -2,14 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from typing import List, Generator, Any + from ...typing.jikan import ( RecommendationsData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Recommendation __all__ = ( @@ -17,11 +19,16 @@ ) @dataclass -class MangaRecommendations(JikanResource): +class MangaRecommendations(JikanIterableResource): _get_endpoint = "/manga/{id}/recommendations" - data: JikanResponseData[RecommendationsData] = field(repr = False) + data: JikanResponseData[List[RecommendationsData]] - def __iter__(self): - for recommendation in self.data["data"]: - yield Recommendation(recommendation) \ No newline at end of file + def __post_init__(self): + super().__post_init__(Recommendation) + + def __next__(self) -> Recommendation: + return super().__next__() + + def __iter__(self) -> Generator[Recommendation, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/manga/relations.py b/anmoku/resources/manga/relations.py index 2e4a874..b97005d 100644 --- a/anmoku/resources/manga/relations.py +++ b/anmoku/resources/manga/relations.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( RelationData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Relation __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class MangaRelations(JikanResource): +class MangaRelations(JikanIterableResource): _get_endpoint = "/manga/{id}/relations" - data: JikanResponseData[List[RelationData]] = field(repr = False) + data: JikanResponseData[List[RelationData]] - def __iter__(self): - for relation in self.data["data"]: - yield Relation(relation) \ No newline at end of file + def __post_init__(self): + super().__post_init__(Relation) + + def __next__(self) -> Relation: + return super().__next__() + + def __iter__(self) -> Generator[Relation, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/manga/reviews.py b/anmoku/resources/manga/reviews.py index 0f65a8a..d83b05c 100644 --- a/anmoku/resources/manga/reviews.py +++ b/anmoku/resources/manga/reviews.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import List, Generator, Any from ...typing.jikan import ( ReviewData, JikanResponseData, ) -from dataclasses import dataclass, field +from dataclasses import dataclass -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import Review __all__ = ( @@ -19,11 +19,16 @@ ) @dataclass -class MangaReviews(JikanResource): +class MangaReviews(JikanIterableResource): _get_endpoint = "/manga/{id}/reviews" - data: JikanResponseData[List[ReviewData]] = field(repr = False) + data: JikanResponseData[List[ReviewData]] - def __iter__(self): - for review in self.data["data"]: - yield Review(review) \ No newline at end of file + def __post_init__(self): + super().__post_init__(Review) + + def __next__(self) -> Review: + return super().__next__() + + def __iter__(self) -> Generator[Review, Any, None]: + return super().__iter__() \ No newline at end of file diff --git a/anmoku/resources/manga/user_updates.py b/anmoku/resources/manga/user_updates.py index 7f5140a..79e9ba2 100644 --- a/anmoku/resources/manga/user_updates.py +++ b/anmoku/resources/manga/user_updates.py @@ -1,14 +1,16 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING if TYPE_CHECKING: + from typing import List, Generator, Any, Optional + from ...typing.jikan.manga import MangaUserUpdatesData from ...typing.jikan.api import JikanResponseData from dataclasses import dataclass, field from datetime import datetime -from ..base import JikanResource +from ..base import JikanIterableResource from ..helpers import User __all__ = ( @@ -49,12 +51,16 @@ def __post_init__(self): self.date = datetime.fromisoformat(update["date"]) @dataclass -class MangaUserUpdates(JikanResource): +class MangaUserUpdates(JikanIterableResource): _get_endpoint = "/manga/{id}/userupdates" - data: JikanResponseData[List[MangaUserUpdatesData]] = field(repr = False) + data: JikanResponseData[List[MangaUserUpdatesData]] + + def __post_init__(self): + super().__post_init__(UserUpdate) - def __iter__(self): + def __next__(self) -> UserUpdate: + return super().__next__() - for update in self.data["data"]: - yield UserUpdate(update) \ No newline at end of file + def __iter__(self) -> Generator[UserUpdate, Any, None]: + return super().__iter__() \ No newline at end of file From fb598dfe151e03b4f04afd5b8a63fb45133e279c Mon Sep 17 00:00:00 2001 From: Goldy <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:59:32 +0000 Subject: [PATCH 18/21] chore: only run test workflows on push --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eb77eae..4a258ca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,6 @@ name: Test anmoku (pytest & ruff) on: push: - pull_request: workflow_dispatch: jobs: From 334fee8717da416ed45ea244e0468defabb86782 Mon Sep 17 00:00:00 2001 From: Goldy <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Mon, 25 Nov 2024 22:30:16 +0000 Subject: [PATCH 19/21] style, chore: overload functions in async client should also be async and version bump --- anmoku/__init__.py | 2 +- anmoku/clients/async_.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/anmoku/__init__.py b/anmoku/__init__.py index cbc6a95..2f40441 100644 --- a/anmoku/__init__.py +++ b/anmoku/__init__.py @@ -6,4 +6,4 @@ from .resources import * from .errors import * -__version__ = "1.0.0dev3" \ No newline at end of file +__version__ = "1.0.0alpha1" \ No newline at end of file diff --git a/anmoku/clients/async_.py b/anmoku/clients/async_.py index ad2c1b6..10debbe 100644 --- a/anmoku/clients/async_.py +++ b/anmoku/clients/async_.py @@ -62,11 +62,11 @@ def __init__( ) @overload - def get(self, resource: Type[NoArgsResourceGenericT]) -> NoArgsResourceGenericT: + async def get(self, resource: Type[NoArgsResourceGenericT]) -> NoArgsResourceGenericT: ... @overload - def get(self, resource: Type[ResourceGenericT], id: SnowflakeT, **kwargs) -> ResourceGenericT: + async def get(self, resource: Type[ResourceGenericT], id: SnowflakeT, **kwargs) -> ResourceGenericT: ... async def get(self, resource: Type[ResourceGenericT], id: Optional[SnowflakeT] = None, **kwargs) -> ResourceGenericT: From b9ec517ad35a6f7022a485ac652b6d951e360064 Mon Sep 17 00:00:00 2001 From: Goldy <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Mon, 25 Nov 2024 22:47:15 +0000 Subject: [PATCH 20/21] refactor: rename `SnowflakeT` --- anmoku/clients/async_.py | 6 +++--- anmoku/clients/base.py | 4 ++-- anmoku/clients/sync.py | 6 +++--- anmoku/typing/anmoku.py | 6 ++++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/anmoku/clients/async_.py b/anmoku/clients/async_.py index 10debbe..c8e42d4 100644 --- a/anmoku/clients/async_.py +++ b/anmoku/clients/async_.py @@ -4,7 +4,7 @@ if TYPE_CHECKING: from typing import Any, Optional, Type, Dict, Tuple - from ..typing.anmoku import SnowflakeT + from ..typing.anmoku import StrOrIntT from ..typing.jikan import SearchResultData from .base import ( @@ -66,10 +66,10 @@ async def get(self, resource: Type[NoArgsResourceGenericT]) -> NoArgsResourceGen ... @overload - async def get(self, resource: Type[ResourceGenericT], id: SnowflakeT, **kwargs) -> ResourceGenericT: + async def get(self, resource: Type[ResourceGenericT], id: StrOrIntT, **kwargs) -> ResourceGenericT: ... - async def get(self, resource: Type[ResourceGenericT], id: Optional[SnowflakeT] = None, **kwargs) -> ResourceGenericT: + async def get(self, resource: Type[ResourceGenericT], id: Optional[StrOrIntT] = None, **kwargs) -> ResourceGenericT: """Get's the exact resource typically by id.""" if id is not None: kwargs["id"] = id diff --git a/anmoku/clients/base.py b/anmoku/clients/base.py index 9fb278e..2700acd 100644 --- a/anmoku/clients/base.py +++ b/anmoku/clients/base.py @@ -5,7 +5,7 @@ from typing import Any, Mapping, TypeVar, Type, Optional from .. import resources - from ..typing.anmoku import SnowflakeT + from ..typing.anmoku import StrOrIntT from ..resources.helpers import SearchResult ResourceGenericT = TypeVar( @@ -70,7 +70,7 @@ def __init__(self, debug: bool = False) -> None: super().__init__() @abstractmethod - def get(self, resource: Type[ResourceGenericT], id: Optional[SnowflakeT] = None, **kwargs) -> ResourceGenericT: + def get(self, resource: Type[ResourceGenericT], id: Optional[StrOrIntT] = None, **kwargs) -> ResourceGenericT: """Get's the exact resource by id.""" ... diff --git a/anmoku/clients/sync.py b/anmoku/clients/sync.py index f0e36c2..5493eec 100644 --- a/anmoku/clients/sync.py +++ b/anmoku/clients/sync.py @@ -4,7 +4,7 @@ if TYPE_CHECKING: from typing import Any, Optional, Type, Tuple - from ..typing.anmoku import SnowflakeT + from ..typing.anmoku import StrOrIntT from ..typing.jikan import SearchResultData from .base import ( @@ -64,10 +64,10 @@ def get(self, resource: Type[NoArgsResourceGenericT]) -> NoArgsResourceGenericT: ... @overload - def get(self, resource: Type[ResourceGenericT], id: SnowflakeT, **kwargs) -> ResourceGenericT: + def get(self, resource: Type[ResourceGenericT], id: StrOrIntT, **kwargs) -> ResourceGenericT: ... - def get(self, resource: Type[ResourceGenericT], id: Optional[SnowflakeT] = None, **kwargs) -> ResourceGenericT: + def get(self, resource: Type[ResourceGenericT], id: Optional[StrOrIntT] = None, **kwargs) -> ResourceGenericT: """Get's the exact resource typically by id.""" if id is not None: kwargs["id"] = id diff --git a/anmoku/typing/anmoku.py b/anmoku/typing/anmoku.py index f122ef3..db9996b 100644 --- a/anmoku/typing/anmoku.py +++ b/anmoku/typing/anmoku.py @@ -1,5 +1,7 @@ from __future__ import annotations from typing import Union -# TODO: tf, that's not what a snowflake is, what was I on. Rename this. -SnowflakeT = Union[str, int] \ No newline at end of file +StrOrIntT = Union[str, int] +""" +StrOrIntT can be either a number (int) or string. Commonly used in methods that accept ID. +""" \ No newline at end of file From 6ece46219134d8a71adb7bd162d470030ba24e48 Mon Sep 17 00:00:00 2001 From: Goldy <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Mon, 25 Nov 2024 22:50:24 +0000 Subject: [PATCH 21/21] docs: add jikan docs link to Anime resource --- anmoku/resources/anime/anime.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/anmoku/resources/anime/anime.py b/anmoku/resources/anime/anime.py index 8397fe5..7dfb9c6 100644 --- a/anmoku/resources/anime/anime.py +++ b/anmoku/resources/anime/anime.py @@ -50,9 +50,13 @@ class Anime(JikanResource): """ Get or search for anime. + [`jikan`_] + + .. _jikan: https://docs.api.jikan.moe/#tag/anime/operation/getAnimeById + Required Params ----------------- - * `id` - Anime ID + * `id` - Manga ID ------------