Source code for hondana.collections

"""
The MIT License (MIT)

Copyright (c) 2021-Present AbstractUmbra

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Generic, TypeVar


if TYPE_CHECKING:
    from .author import Author
    from .chapter import Chapter, PreviouslyReadChapter
    from .cover import Cover
    from .custom_list import CustomList
    from .http import HTTPClient
    from .legacy import LegacyItem
    from .manga import Manga, MangaRelation
    from .report import Report, UserReport
    from .scanlator_group import ScanlatorGroup
    from .types_.author import GetMultiAuthorResponse
    from .types_.chapter import ChapterReadHistoryResponse, GetMultiChapterResponse
    from .types_.cover import GetMultiCoverResponse
    from .types_.custom_list import GetMultiCustomListResponse
    from .types_.legacy import GetLegacyMappingResponse
    from .types_.manga import MangaRelationResponse, MangaSearchResponse
    from .types_.report import GetReportReasonResponse, GetUserReportReasonResponse
    from .types_.scanlator_group import GetMultiScanlationGroupResponse
    from .types_.user import GetMultiUserResponse
    from .user import User

__all__ = (
    "MangaCollection",
    "MangaRelationCollection",
    "ChapterFeed",
    "AuthorCollection",
    "CoverCollection",
    "ScanlatorGroupCollection",
    "ReportCollection",
    "UserReportCollection",
    "UserCollection",
    "CustomListCollection",
    "LegacyMappingCollection",
    "ChapterReadHistoryCollection",
)

T = TypeVar("T")


class BaseCollection(ABC, Generic[T]):
    """
    The base class for all collections. This class serves to make it easier to create functions that process
    arbitrary collections without needing to set up ``isinstance()`` checks.

    Attributes
    -----------
    total: :class:`int`
        The total possible results with this query could return.
    offset: :class:`int`
        The offset used in this query.
    limit: :class:`int`
        The limit used in this query.
    """

    total: int
    offset: int
    limit: int

    @property
    @abstractmethod
    def items(self) -> list[T]:
        """
        Returns the items in the collection.

        Returns
        -------

        :class:`list`
        """


[docs]class MangaCollection(BaseCollection["Manga"]): """ A collection object type to represent manga. Attributes ----------- manga: List[:class:`~hondana.Manga`] The manga returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "manga", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: MangaSearchResponse, manga: list[Manga]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: MangaSearchResponse = payload self.manga: list[Manga] = manga self.total: int = payload.get("total", 0) self.offset: int = payload.get("offset", 0) self.limit: int = payload.get("limit", 0) super().__init__() def __repr__(self) -> str: return f"<MangaFeed manga={len(self.manga)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[Manga]: """ Returns the mangas in the collection. Returns ------- List[:class:`~hondana.Manga`] """ return self.manga
[docs]class MangaRelationCollection(BaseCollection["MangaRelation"]): """ A collection object type to represent manga relations. Attributes ----------- relations: List[:class:`~hondana.MangaRelation`] The manga relations returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "relations", "total", "offset", "limit", ) def __init__(self, http: HTTPClient, payload: MangaRelationResponse, relations: list[MangaRelation]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data = payload self.relations: list[MangaRelation] = relations self.total: int = payload.get("total", 0) self.offset: int = payload.get("offset", 0) self.limit: int = payload.get("limit", 0) super().__init__() def __repr__(self) -> str: return f"<MangaRelationCollection authors={len(self.relations)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[MangaRelation]: """ Returns the manga relations in the collection. Returns ------- List[:class:`~hondana.MangaRelation`] """ return self.relations
[docs]class ChapterFeed(BaseCollection["Chapter"]): """ A collection object type to represent chapters. Attributes ----------- chapters: List[:class:`~hondana.Chapter`] The chapters returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "chapters", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: GetMultiChapterResponse, chapters: list[Chapter]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: GetMultiChapterResponse = payload self.chapters: list[Chapter] = chapters self.total: int = payload.get("total", 0) self.offset: int = payload.get("offset", 0) self.limit: int = payload.get("limit", 0) super().__init__() def __repr__(self) -> str: return f"<ChapterFeed chapters={len(self.chapters)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[Chapter]: """ Returns the chapters in the collection. Returns ------- List[:class:`~hondana.Chapter`] """ return self.chapters
[docs]class AuthorCollection(BaseCollection["Author"]): """ A collection object type to represent authors. Attributes ----------- authors: List[:class:`~hondana.Author`] The authors returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "authors", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: GetMultiAuthorResponse, authors: list[Author]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: GetMultiAuthorResponse = payload self.authors: list[Author] = authors self.total: int = payload.get("total", 0) self.offset: int = payload.get("offset", 0) self.limit: int = payload.get("limit", 0) super().__init__() def __repr__(self) -> str: return f"<ArtistCollection authors={len(self.authors)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[Author]: """ Returns the authors in the collection. Returns ------- List[:class:`~hondana.Author`] """ return self.authors
[docs]class CoverCollection(BaseCollection["Cover"]): """ A collection object type to represent covers. Attributes ----------- covers: List[:class:`~hondana.Cover`] The covers returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "covers", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: GetMultiCoverResponse, covers: list[Cover]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: GetMultiCoverResponse = payload self.covers: list[Cover] = covers self.total: int = payload.get("total", 0) self.offset: int = payload.get("offset", 0) self.limit: int = payload.get("limit", 0) super().__init__() def __repr__(self) -> str: return f"<CoverCollection covers={len(self.covers)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[Cover]: """ Returns the covers in the collection. Returns ------- List[:class:`~hondana.Cover`] """ return self.covers
[docs]class ScanlatorGroupCollection(BaseCollection["ScanlatorGroup"]): """ A collection object type to represent scanlator groups. Attributes ----------- groups: List[:class:`~hondana.ScanlatorGroup`] The groups returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "groups", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: GetMultiScanlationGroupResponse, groups: list[ScanlatorGroup]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: GetMultiScanlationGroupResponse = payload self.groups: list[ScanlatorGroup] = groups self.total: int = payload.get("total", 0) self.limit: int = payload.get("limit", 0) self.offset: int = payload.get("offset", 0) super().__init__() def __repr__(self) -> str: return f"<ScanlatorGroupCollection groups={len(self.groups)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[ScanlatorGroup]: """ Returns the groups in the collection. Returns ------- List[:class:`~hondana.ScanlatorGroup`] """ return self.groups
[docs]class ReportCollection(BaseCollection["Report"]): """ A collection object type to represent reports. Attributes ----------- reports: List[:class:`~hondana.Report`] The reports returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "reports", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: GetReportReasonResponse, reports: list[Report]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: GetReportReasonResponse = payload self.reports: list[Report] = reports self.total: int = payload.get("total", 0) self.limit: int = payload.get("limit", 0) self.offset: int = payload.get("offset", 0) super().__init__() def __repr__(self) -> str: return f"<ReportCollection reports={len(self.reports)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[Report]: """ Returns the reports in the collection. Returns ------- List[:class:`~hondana.Report`] """ return self.reports
class UserReportCollection(BaseCollection["UserReport"]): """ A collection object type to represent reports. Attributes ----------- reports: List[:class:`~hondana.UserReport`] The reports returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "reports", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: GetUserReportReasonResponse, reports: list[UserReport]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: GetUserReportReasonResponse = payload self.reports: list[UserReport] = reports self.total: int = payload.get("total", 0) self.limit: int = payload.get("limit", 0) self.offset: int = payload.get("offset", 0) super().__init__() def __repr__(self) -> str: return ( f"<UserReportCollection reports={len(self.reports)} total={self.total} offset={self.offset} limit={self.limit}>" ) @property def items(self) -> list[UserReport]: """ Returns the reports in the collection. Returns ------- List[:class:`~hondana.UserReport`] """ return self.reports
[docs]class UserCollection(BaseCollection["User"]): """ A collection object type to represent users. Attributes ----------- users: List[:class:`~hondana.User`] The users returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "users", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: GetMultiUserResponse, users: list[User]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: GetMultiUserResponse = payload self.users: list[User] = users self.total: int = payload.get("total", 0) self.limit: int = payload.get("limit", 0) self.offset: int = payload.get("offset", 0) super().__init__() def __repr__(self) -> str: return f"<UserCollection users={len(self.users)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[User]: """ Returns the users in the collection. Returns ------- List[:class:`~hondana.User`] """ return self.users
[docs]class CustomListCollection(BaseCollection["CustomList"]): """ A collection object type to represent custom lists. Attributes ----------- lists: List[:class:`~hondana.CustomList`] The custom lists returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "lists", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: GetMultiCustomListResponse, lists: list[CustomList]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: GetMultiCustomListResponse = payload self.lists: list[CustomList] = lists self.total: int = payload.get("total", 0) self.limit: int = payload.get("limit", 0) self.offset: int = payload.get("offset", 0) super().__init__() def __repr__(self) -> str: return f"<CustomListCollection lists={len(self.lists)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[CustomList]: """ Returns the custom lists in the collection. Returns ------- List[:class:`~hondana.CustomList`] """ return self.lists
[docs]class LegacyMappingCollection(BaseCollection["LegacyItem"]): """ A collection object type to represent custom lists. Attributes ----------- legacy_mappings: List[:class:`~hondana.LegacyItem`] The legacy mappings returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "legacy_mappings", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: GetLegacyMappingResponse, mappings: list[LegacyItem]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data: GetLegacyMappingResponse = payload self.legacy_mappings: list[LegacyItem] = mappings self.total: int = payload.get("total", 0) self.limit: int = payload.get("limit", 0) self.offset: int = payload.get("offset", 0) super().__init__() def __repr__(self) -> str: return f"<LegacyMappingCollection legacy_mappings={len(self.legacy_mappings)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[LegacyItem]: """ Returns the legacy mappings in the collection. Returns ------- List[:class:`~hondana.LegacyItem`] """ return self.legacy_mappings
[docs]class ChapterReadHistoryCollection(BaseCollection["PreviouslyReadChapter"]): """ A collection object type to represent chapter read history. Attributes ----------- chapter_read_histories: List[:class:`~hondana.PreviouslyReadChapter`] The chapter read histories returned from this collection. total: :class:`int` The total possible results with this query could return. offset: :class:`int` The offset used in this query. limit: :class:`int` The limit used in this query. """ __slots__ = ( "_http", "_data", "chapter_read_histories", "total", "limit", "offset", ) def __init__(self, http: HTTPClient, payload: ChapterReadHistoryResponse, history: list[PreviouslyReadChapter]) -> None: self._http: HTTPClient = http payload.pop("data", []) # type: ignore # can't pop from a TypedDict self._data = payload self.history: list[PreviouslyReadChapter] = history self.total: int = payload.get("total", 0) self.limit: int = payload.get("limit", 0) self.offset: int = payload.get("offset", 0) super().__init__() def __repr__(self) -> str: return f"<ChapterReadHistoryCollection history={len(self.history)} total={self.total} offset={self.offset} limit={self.limit}>" @property def items(self) -> list[PreviouslyReadChapter]: """ Returns the legacy mappings in the collection. Returns ------- List[:class:`~hondana.PreviouslyReadChapter`] """ return self.history