Skip to content

services

Contains services that makes the actual requests to MAL api.

AnimeService

Bases: BaseService

Hanldes all the methods related to Anime.

Source code in aniwrap/services/anime.py
class AnimeService(BaseService):
    """Hanldes all the methods related to Anime."""

    _fields = "id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,created_at,updated_at,media_type,status,genres,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,related_manga,recommendations,studios,statistics"
    __slots__ = ()

    async def search_anime(
        self, anime_name: str, *, limit: int | None = 10, offset: int | None = 0
    ) -> ResultT[list[Anime]]:
        """Search for anime by title.

        Args:
            anime_name: Title or name of the anime.

        Keyword Args:
            limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
                Defaults to `10`

            offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
                Defaults to `0`

        Returns:
            [`Result`][aniwrap.Result] containing `list[Anime]` on success or error data on error.

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.anime.search_anime("blue lock", limit=5, offset=0)

            if result.is_success:
                anime_list = result.value

            if result.is_error:
                error = result.error

            await client.close()
            ```
        """

        params = {
            "fields": self._fields,
            "limit": 100 if limit > 100 else limit,
            "offset": 0 if offset < 0 else offset,
            "q": anime_name,
        }

        route = endpoints.SEARCH_ANIME.generate_route().with_params(params)
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(self._serializer.deserialize_anime_results(result.data))

    async def get_anime(self, id: int | str) -> ResultT[Anime]:
        """Get details of anime by the anime id.

        Args:
            id: The Id of the anime.

        Returns:
            [`Result`][aniwrap.Result] containing `Anime` on success or error data on error.

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.anime.get_anime(16498)

            if result.is_success:
                anime_details = result.value

            if result.is_error:
                error = result.error

            await client.close()
            ```
        """

        params = {"fields": self._fields}
        route = endpoints.GET_ANIME.generate_route(id).with_params(params)
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(self._serializer.deserialize_anime(result.data))

    async def get_anime_ranking(
        self,
        ranking_type: AnimeRankingType,
        *,
        limit: int | None = 10,
        offset: int | None = 0,
    ) -> ResultT[list[AnimeRanking]]:
        """Get different types of anime rankings.

        Args:
            ranking_type: The type of ranking. Check enum `AnimeRankingType` for all the possible types.

        Keyword Args:
            limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
                Defaults to `10`

            offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
                Defaults to `0`

        Returns:
            [`Result`][aniwrap.Result] containing `list[AnimeRanking]` model on success or error data on error.

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.anime.get_anime_ranking(AnimeRankingType.All, limit=5, offset=0)

            if result.is_success:
                anime_list = result.value

            if result.is_error:
                error = result.error

            await client.close()
            ```
        """

        params = {
            "fields": self._fields,
            "limit": 100 if limit > 100 else limit,
            "offset": 0 if offset < 0 else offset,
            "ranking_type": ranking_type.value,
        }

        route = endpoints.GET_ANIME_RANKING.generate_route().with_params(params)
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(self._serializer.deserialize_anime_ranking(result.data))

    async def get_seasonal_anime(
        self,
        year: int,
        season: AnimeSeason,
        *,
        sort_type: AnimeSortType = AnimeSortType.AnimeScore,
        limit: int | None = 10,
        offset: int | None = 0,
    ) -> ResultT[list[Anime]]:
        """Get anime by season and year.

        Args:
            year: The year.
            season: The anime season in the year.

        Keyword Args:
            sort_type: The optional sort_type based on which the anime results will be sorted.
                Defaults to `AnimeSortType.AnimeScore`.

            limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
                Defaults to `10`

            offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
                Defaults to `0`

        Returns:
            [`Result`][aniwrap.Result] containing `list[Anime]` model on success or error data on error.

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.anime.get_seasonal_anime(
                2020, AnimeSeason.Fall, sort_type=AnimeSortType.NumberOfUsers
            )

            if result.is_success:
                anime_list = result.value

            if result.is_error:
                error = result.error
            ```
        """

        params = {
            "fields": self._fields,
            "limit": 100 if limit > 100 else limit,
            "offset": 0 if offset < 0 else offset,
            "sort": sort_type.value,
        }

        route = endpoints.GET_SEASONAL_ANIME.generate_route(
            year, season.value
        ).with_params(params)
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(self._serializer.deserialize_anime_results(result.data))

    # Used when requesting specific fields is implemented
    def _generate_fields_map(self, fields: list[Anime]) -> dict[str, str]:
        """Generate a dict of field params."""

        return {"fields": ",".join([f.value for f in fields])}

get_anime async

get_anime(id: int | str) -> ResultT[Anime]

Get details of anime by the anime id.

Parameters:

Name Type Description Default
id int | str

The Id of the anime.

required

Returns:

Type Description
ResultT[Anime]

Result containing Anime on success or error data on error.

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.anime.get_anime(16498)

if result.is_success:
    anime_details = result.value

if result.is_error:
    error = result.error

await client.close()
Source code in aniwrap/services/anime.py
async def get_anime(self, id: int | str) -> ResultT[Anime]:
    """Get details of anime by the anime id.

    Args:
        id: The Id of the anime.

    Returns:
        [`Result`][aniwrap.Result] containing `Anime` on success or error data on error.

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.anime.get_anime(16498)

        if result.is_success:
            anime_details = result.value

        if result.is_error:
            error = result.error

        await client.close()
        ```
    """

    params = {"fields": self._fields}
    route = endpoints.GET_ANIME.generate_route(id).with_params(params)
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(self._serializer.deserialize_anime(result.data))

get_anime_ranking async

get_anime_ranking(
    ranking_type: AnimeRankingType,
    *,
    limit: int | None = 10,
    offset: int | None = 0
) -> ResultT[list[AnimeRanking]]

Get different types of anime rankings.

Parameters:

Name Type Description Default
ranking_type AnimeRankingType

The type of ranking. Check enum AnimeRankingType for all the possible types.

required

Other Parameters:

Name Type Description
limit int | None

The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100. Defaults to 10

offset int | None

The optional offset to use with requests, which specifies the offset from the start when fetching response. Defaults to 0

Returns:

Type Description
ResultT[list[AnimeRanking]]

Result containing list[AnimeRanking] model on success or error data on error.

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.anime.get_anime_ranking(AnimeRankingType.All, limit=5, offset=0)

if result.is_success:
    anime_list = result.value

if result.is_error:
    error = result.error

await client.close()
Source code in aniwrap/services/anime.py
async def get_anime_ranking(
    self,
    ranking_type: AnimeRankingType,
    *,
    limit: int | None = 10,
    offset: int | None = 0,
) -> ResultT[list[AnimeRanking]]:
    """Get different types of anime rankings.

    Args:
        ranking_type: The type of ranking. Check enum `AnimeRankingType` for all the possible types.

    Keyword Args:
        limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
            Defaults to `10`

        offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
            Defaults to `0`

    Returns:
        [`Result`][aniwrap.Result] containing `list[AnimeRanking]` model on success or error data on error.

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.anime.get_anime_ranking(AnimeRankingType.All, limit=5, offset=0)

        if result.is_success:
            anime_list = result.value

        if result.is_error:
            error = result.error

        await client.close()
        ```
    """

    params = {
        "fields": self._fields,
        "limit": 100 if limit > 100 else limit,
        "offset": 0 if offset < 0 else offset,
        "ranking_type": ranking_type.value,
    }

    route = endpoints.GET_ANIME_RANKING.generate_route().with_params(params)
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(self._serializer.deserialize_anime_ranking(result.data))

get_seasonal_anime async

get_seasonal_anime(
    year: int,
    season: AnimeSeason,
    *,
    sort_type: AnimeSortType = AnimeSortType.AnimeScore,
    limit: int | None = 10,
    offset: int | None = 0
) -> ResultT[list[Anime]]

Get anime by season and year.

Parameters:

Name Type Description Default
year int

The year.

required
season AnimeSeason

The anime season in the year.

required

Other Parameters:

Name Type Description
sort_type AnimeSortType

The optional sort_type based on which the anime results will be sorted. Defaults to AnimeSortType.AnimeScore.

limit int | None

The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100. Defaults to 10

offset int | None

The optional offset to use with requests, which specifies the offset from the start when fetching response. Defaults to 0

Returns:

Type Description
ResultT[list[Anime]]

Result containing list[Anime] model on success or error data on error.

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.anime.get_seasonal_anime(
    2020, AnimeSeason.Fall, sort_type=AnimeSortType.NumberOfUsers
)

if result.is_success:
    anime_list = result.value

if result.is_error:
    error = result.error
Source code in aniwrap/services/anime.py
async def get_seasonal_anime(
    self,
    year: int,
    season: AnimeSeason,
    *,
    sort_type: AnimeSortType = AnimeSortType.AnimeScore,
    limit: int | None = 10,
    offset: int | None = 0,
) -> ResultT[list[Anime]]:
    """Get anime by season and year.

    Args:
        year: The year.
        season: The anime season in the year.

    Keyword Args:
        sort_type: The optional sort_type based on which the anime results will be sorted.
            Defaults to `AnimeSortType.AnimeScore`.

        limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
            Defaults to `10`

        offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
            Defaults to `0`

    Returns:
        [`Result`][aniwrap.Result] containing `list[Anime]` model on success or error data on error.

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.anime.get_seasonal_anime(
            2020, AnimeSeason.Fall, sort_type=AnimeSortType.NumberOfUsers
        )

        if result.is_success:
            anime_list = result.value

        if result.is_error:
            error = result.error
        ```
    """

    params = {
        "fields": self._fields,
        "limit": 100 if limit > 100 else limit,
        "offset": 0 if offset < 0 else offset,
        "sort": sort_type.value,
    }

    route = endpoints.GET_SEASONAL_ANIME.generate_route(
        year, season.value
    ).with_params(params)
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(self._serializer.deserialize_anime_results(result.data))

search_anime async

search_anime(
    anime_name: str,
    *,
    limit: int | None = 10,
    offset: int | None = 0
) -> ResultT[list[Anime]]

Search for anime by title.

Parameters:

Name Type Description Default
anime_name str

Title or name of the anime.

required

Other Parameters:

Name Type Description
limit int | None

The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100. Defaults to 10

offset int | None

The optional offset to use with requests, which specifies the offset from the start when fetching response. Defaults to 0

Returns:

Type Description
ResultT[list[Anime]]

Result containing list[Anime] on success or error data on error.

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.anime.search_anime("blue lock", limit=5, offset=0)

if result.is_success:
    anime_list = result.value

if result.is_error:
    error = result.error

await client.close()
Source code in aniwrap/services/anime.py
async def search_anime(
    self, anime_name: str, *, limit: int | None = 10, offset: int | None = 0
) -> ResultT[list[Anime]]:
    """Search for anime by title.

    Args:
        anime_name: Title or name of the anime.

    Keyword Args:
        limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
            Defaults to `10`

        offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
            Defaults to `0`

    Returns:
        [`Result`][aniwrap.Result] containing `list[Anime]` on success or error data on error.

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.anime.search_anime("blue lock", limit=5, offset=0)

        if result.is_success:
            anime_list = result.value

        if result.is_error:
            error = result.error

        await client.close()
        ```
    """

    params = {
        "fields": self._fields,
        "limit": 100 if limit > 100 else limit,
        "offset": 0 if offset < 0 else offset,
        "q": anime_name,
    }

    route = endpoints.SEARCH_ANIME.generate_route().with_params(params)
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(self._serializer.deserialize_anime_results(result.data))

BaseService

Bases: ABC

The base service from which all the other services inherit.

Parameters:

Name Type Description Default
http_service HttpService

The http service to use for requests.

required
Source code in aniwrap/services/base.py
class BaseService(abc.ABC):
    """The base service from which all the other services inherit.

    Args:
        http_service: The http service to use for requests.
    """

    __slots__ = ("_http", "_serializer")

    def __init__(
        self, http_service: HttpService, serializer: serializer.Serializer
    ) -> None:
        self._http = http_service
        self._serializer = serializer

ForumService

Bases: BaseService

Handles all forum related methods.

Source code in aniwrap/services/forum.py
class ForumService(BaseService):
    """Handles all forum related methods."""

    __slots__ = ()

    async def get_forum_boards(self) -> ResultT[list[Forum]]:
        """Get list of all the fourm boards filtered by category.

        Returns:
            [`Result`][aniwrap.Result] containing `list[Forum]` on success or error data on error.

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.forum.get_forum_boards()

            if result.is_success:
                boards = result.value

            if result.is_error:
                error = result.error

            await client.close()
            ```
        """

        route = endpoints.GET_FORUM_BOARDS.generate_route()
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(
            [
                self._serializer.deserialize_forum_board(c)
                for c in result.data.get("categories", [])
            ]
        )

    async def get_forum_topics(
        self,
        *,
        query: str | None = None,
        board_id: int | None = None,
        subboard_id: int | None = None,
        limit: int | None = 10,
        offset: int | None = 0,
        topic_user_name: str | None = None,
        username: str | None = None,
    ) -> ResultT[list[ForumTopic]]:
        """Get topics by different parameters. At least one of the arguments must be specified.

        Keyword Args:
            query: The query parameter to search - usually matches the title of the topic
                Defaults to `None`

            board_id: The Id of specific board
                Defaults to `None`

            subboard_id: The Id of any specific sub board
                Defaults to `None`

            limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
                Defaults to `10`

            offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
                Defaults to `0`

            topic_user_name: The username of the user that started the topic
                Defaults to `None`

            username: The username of the users that participated in the topic
                Defaults to `None`

        Returns:
            [`Result`][aniwrap.Result] containing `ForumTopic` on success or error data on error.

        Raises:
            ValueError: When no arguments are given

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.forum.get_forum_topics(query="new anime")

            if result.is_success:
                topics = result.value

            if result.is_error:
                error = result.error

            await client.close()
            ```
        """

        if query or board_id or subboard_id or topic_user_name or username:
            params = {
                "q": query if query else "",
                "board_id": board_id if board_id else "",
                "subboard_id": subboard_id if subboard_id else "",
                "topic_user_name": topic_user_name if topic_user_name else "",
                "username": username if username else "",
                "limit": limit,
                "offset": offset,
            }

            route = endpoints.GET_FORUM_TOPICS.generate_route().with_params(params)
            result = await self._http.fetch(route, HttpMethod.Get)

            if isinstance(result, HttpErrorResponse):
                return Error(result)

            return Success(
                [
                    self._serializer.deserialize_forum_topic(t)
                    for t in result.data.get("data", [])
                ]
            )

        else:
            raise ValueError("Atleast one parameter must be specified.")

    async def get_forum_topic_details(
        self, topic_id: int, *, limit: int | None = 10, offset: int | None = 0
    ) -> ResultT[ForumTopicDetails]:
        """Get topic details by topic id.

        Args:
            topic_id: The Id of the topic

        Keyword Args:
            limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
                Defaults to `10`

            offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
                Defaults to `0`

        Returns:
            [`Result`][aniwrap.Result] containing `ForumTopicDetails` on success or error data on error.

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.forum.get_forum_topic_details(2070198)

            if result.is_success:
                topic_details = result.value

            if result.is_error:
                error = result.error

            await client.close()
            ```
        """

        params = {"limit": limit, "offset": offset}

        route = endpoints.GET_FORUM_TOPIC_DETAILS.generate_route(topic_id).with_params(
            params
        )
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(
            self._serializer.deserialize_forum_topic_details(
                result.data.get("data", {})
            )
        )

get_forum_boards async

get_forum_boards() -> ResultT[list[Forum]]

Get list of all the fourm boards filtered by category.

Returns:

Type Description
ResultT[list[Forum]]

Result containing list[Forum] on success or error data on error.

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.forum.get_forum_boards()

if result.is_success:
    boards = result.value

if result.is_error:
    error = result.error

await client.close()
Source code in aniwrap/services/forum.py
async def get_forum_boards(self) -> ResultT[list[Forum]]:
    """Get list of all the fourm boards filtered by category.

    Returns:
        [`Result`][aniwrap.Result] containing `list[Forum]` on success or error data on error.

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.forum.get_forum_boards()

        if result.is_success:
            boards = result.value

        if result.is_error:
            error = result.error

        await client.close()
        ```
    """

    route = endpoints.GET_FORUM_BOARDS.generate_route()
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(
        [
            self._serializer.deserialize_forum_board(c)
            for c in result.data.get("categories", [])
        ]
    )

get_forum_topic_details async

get_forum_topic_details(
    topic_id: int,
    *,
    limit: int | None = 10,
    offset: int | None = 0
) -> ResultT[ForumTopicDetails]

Get topic details by topic id.

Parameters:

Name Type Description Default
topic_id int

The Id of the topic

required

Other Parameters:

Name Type Description
limit int | None

The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100. Defaults to 10

offset int | None

The optional offset to use with requests, which specifies the offset from the start when fetching response. Defaults to 0

Returns:

Type Description
ResultT[ForumTopicDetails]

Result containing ForumTopicDetails on success or error data on error.

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.forum.get_forum_topic_details(2070198)

if result.is_success:
    topic_details = result.value

if result.is_error:
    error = result.error

await client.close()
Source code in aniwrap/services/forum.py
async def get_forum_topic_details(
    self, topic_id: int, *, limit: int | None = 10, offset: int | None = 0
) -> ResultT[ForumTopicDetails]:
    """Get topic details by topic id.

    Args:
        topic_id: The Id of the topic

    Keyword Args:
        limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
            Defaults to `10`

        offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
            Defaults to `0`

    Returns:
        [`Result`][aniwrap.Result] containing `ForumTopicDetails` on success or error data on error.

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.forum.get_forum_topic_details(2070198)

        if result.is_success:
            topic_details = result.value

        if result.is_error:
            error = result.error

        await client.close()
        ```
    """

    params = {"limit": limit, "offset": offset}

    route = endpoints.GET_FORUM_TOPIC_DETAILS.generate_route(topic_id).with_params(
        params
    )
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(
        self._serializer.deserialize_forum_topic_details(
            result.data.get("data", {})
        )
    )

get_forum_topics async

get_forum_topics(
    *,
    query: str | None = None,
    board_id: int | None = None,
    subboard_id: int | None = None,
    limit: int | None = 10,
    offset: int | None = 0,
    topic_user_name: str | None = None,
    username: str | None = None
) -> ResultT[list[ForumTopic]]

Get topics by different parameters. At least one of the arguments must be specified.

Other Parameters:

Name Type Description
query str | None

The query parameter to search - usually matches the title of the topic Defaults to None

board_id int | None

The Id of specific board Defaults to None

subboard_id int | None

The Id of any specific sub board Defaults to None

limit int | None

The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100. Defaults to 10

offset int | None

The optional offset to use with requests, which specifies the offset from the start when fetching response. Defaults to 0

topic_user_name str | None

The username of the user that started the topic Defaults to None

username str | None

The username of the users that participated in the topic Defaults to None

Returns:

Type Description
ResultT[list[ForumTopic]]

Result containing ForumTopic on success or error data on error.

Raises:

Type Description
ValueError

When no arguments are given

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.forum.get_forum_topics(query="new anime")

if result.is_success:
    topics = result.value

if result.is_error:
    error = result.error

await client.close()
Source code in aniwrap/services/forum.py
async def get_forum_topics(
    self,
    *,
    query: str | None = None,
    board_id: int | None = None,
    subboard_id: int | None = None,
    limit: int | None = 10,
    offset: int | None = 0,
    topic_user_name: str | None = None,
    username: str | None = None,
) -> ResultT[list[ForumTopic]]:
    """Get topics by different parameters. At least one of the arguments must be specified.

    Keyword Args:
        query: The query parameter to search - usually matches the title of the topic
            Defaults to `None`

        board_id: The Id of specific board
            Defaults to `None`

        subboard_id: The Id of any specific sub board
            Defaults to `None`

        limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
            Defaults to `10`

        offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
            Defaults to `0`

        topic_user_name: The username of the user that started the topic
            Defaults to `None`

        username: The username of the users that participated in the topic
            Defaults to `None`

    Returns:
        [`Result`][aniwrap.Result] containing `ForumTopic` on success or error data on error.

    Raises:
        ValueError: When no arguments are given

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.forum.get_forum_topics(query="new anime")

        if result.is_success:
            topics = result.value

        if result.is_error:
            error = result.error

        await client.close()
        ```
    """

    if query or board_id or subboard_id or topic_user_name or username:
        params = {
            "q": query if query else "",
            "board_id": board_id if board_id else "",
            "subboard_id": subboard_id if subboard_id else "",
            "topic_user_name": topic_user_name if topic_user_name else "",
            "username": username if username else "",
            "limit": limit,
            "offset": offset,
        }

        route = endpoints.GET_FORUM_TOPICS.generate_route().with_params(params)
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(
            [
                self._serializer.deserialize_forum_topic(t)
                for t in result.data.get("data", [])
            ]
        )

    else:
        raise ValueError("Atleast one parameter must be specified.")

HttpService

The HTTP service that is used to make requests to MAL API.

Parameters:

Name Type Description Default
client_id str | None

X-MAL-CLIENT-ID.

None
Source code in aniwrap/services/http.py
class HttpService:
    """The HTTP service that is used to make requests to MAL API.

    Args:
        client_id: X-MAL-CLIENT-ID.
    """

    __slots__ = ("_headers", "_session", "_method_mapping")

    def __init__(
        self, client_id: str | None = None, access_token: str | None = None
    ) -> None:
        self._set_headers(client_id, access_token)
        self._session = aiohttp.ClientSession()

    def _set_headers(
        self, client_id: str | None = None, access_token: str | None = None
    ) -> None:
        """Set headers - either client_id or bearer token"""

        if not client_id and not access_token:
            raise ValueError("Either Client id or bearer token need to be specified.")

        if client_id:
            self._headers = {"X-MAL-CLIENT-ID": client_id}

        if access_token:
            self._headers = {"Authorization": f"Bearer {access_token}"}

    def _get_session_method(self, method: HttpMethod, session: Any) -> Any:
        """Get the session with method type.

        Returns:
            The session with respective method.
        """

        _method_mapping = {
            HttpMethod.Get: session.get,
            HttpMethod.Post: session.post,
            HttpMethod.Put: session.put,
            HttpMethod.Patch: session.patch,
            HttpMethod.Delete: session.delete,
        }

        return _method_mapping[method]

    async def _request(
        self,
        session: Any,
        uri: str,
        params: dict[str, str | int],
        data: dict[str, str | int],
    ) -> HttpErrorResponse | HttpSuccessResponse:
        """Make the actual request to the MAL API based on given params.

        Returns:
            The response from the API call.
        """
        try:
            async with session(
                uri, headers=self._headers, params=params, data=data
            ) as r:
                response = await r.json()
                if r.status == 200:
                    return HttpSuccessResponse(r.status, "Success.", response)

                return HttpErrorResponse(r.status, response.get("error"))

        except Exception as e:
            return HttpErrorResponse(500, str(e))

    async def fetch(
        self, route: GenerateRoute, method: HttpMethod
    ) -> HttpErrorResponse | HttpSuccessResponse:
        """Makes a request to the given route.

        Returns:
            The HTTP response [`HttpSuccessResponse`] or [`HttpErrorResponse`] of the API call.
        """
        try:
            # async with self._session as session:
            return await self._request(
                self._get_session_method(method, self._session),
                route.uri,
                route.params,
                route.data,
            )
        except Exception as e:
            return HttpErrorResponse(500, str(e))

    async def close(self) -> None:
        """Close the open aiohttp clientsession."""

        if self._session and not self._session.closed:
            await self._session.close()

close async

close() -> None

Close the open aiohttp clientsession.

Source code in aniwrap/services/http.py
async def close(self) -> None:
    """Close the open aiohttp clientsession."""

    if self._session and not self._session.closed:
        await self._session.close()

fetch async

fetch(
    route: GenerateRoute, method: HttpMethod
) -> HttpErrorResponse | HttpSuccessResponse

Makes a request to the given route.

Returns:

Type Description
HttpErrorResponse | HttpSuccessResponse

The HTTP response [HttpSuccessResponse] or [HttpErrorResponse] of the API call.

Source code in aniwrap/services/http.py
async def fetch(
    self, route: GenerateRoute, method: HttpMethod
) -> HttpErrorResponse | HttpSuccessResponse:
    """Makes a request to the given route.

    Returns:
        The HTTP response [`HttpSuccessResponse`] or [`HttpErrorResponse`] of the API call.
    """
    try:
        # async with self._session as session:
        return await self._request(
            self._get_session_method(method, self._session),
            route.uri,
            route.params,
            route.data,
        )
    except Exception as e:
        return HttpErrorResponse(500, str(e))

MangaService

Bases: BaseService

Handles all methods related to Manga.

Source code in aniwrap/services/manga.py
class MangaService(BaseService):
    """Handles all methods related to Manga."""

    _fields = "id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,created_at,updated_at,media_type,status,genres,my_list_status,num_volumes,num_chapters,authors{first_name,last_name},pictures,background,related_anime,related_manga,recommendations,serialization"
    __slots__ = ()

    async def search_manga(
        self, manga_name: str, *, limit: int | None = 10, offset: int | None = 0
    ) -> ResultT[list[Manga]]:
        """Search for Manga by title.

        Args:
            manga_name: Title or name of the manga.

        Keyword Args:
            limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
                Defaults to `10`

            offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
                Defaults to `0`

        Returns:
            [`Result`][aniwrap.Result] containing `list[Manga]` on success or error data on error.

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.manga.search_manga("shingeki no kyojin", limit=5, offset=0)

            if result.is_success:
                manga_list = result.value

            if result.is_error:
                error = result.error

            await client.close()
            ```
        """

        params = {
            "fields": self._fields,
            "limit": 100 if limit > 100 else limit,
            "offset": 0 if offset < 0 else offset,
            "q": manga_name,
        }

        route = endpoints.SEARCH_MANGA.generate_route().with_params(params)
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(self._serializer.deserialize_manga_results(result.data))

    async def get_manga(self, id: int | str) -> ResultT[Manga]:
        """Get details of manga by the manga id.

        Args:
            id: The Id of the manga.

        Returns:
            [`Result`][aniwrap.Result] containing `Manga` on success or error data on error.

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.manga.get_manga(23390)

            if result.is_success:
                manga_details = result.value

            if result.is_error:
                error = result.error

            await client.close()
            ```
        """

        params = {"fields": self._fields}

        route = endpoints.GET_MANGA.generate_route(id).with_params(params)
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(self._serializer.deserialize_manga(result.data))

    async def get_manga_ranking(
        self,
        ranking_type: MangaRankingType,
        *,
        limit: int | None = 10,
        offset: int | None = 0,
    ) -> Result[list[MangaRanking]]:
        """Get different types of manga rankings.

        Args:
            ranking_type: The type of ranking. Check enum `MangaRankingType` for all the possible types.

        Keyword Args:
            limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
                Defaults to `10`

            offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
                Defaults to `0`

        Returns:
            [`Result`][aniwrap.Result] containing `list[MangaRanking]` model on success or error data on error.

        ??? example

            ```py
            import aniwrap

            client = aniwrap.Client(...)

            result = await client.manga.get_manga_ranking(MangaRankingType.ByPopularity, limit=5, offset=0)

            if result.is_success:
                manga_list = result.value

            if result.is_error:
                error = result.error

            await client.close()
            ```
        """

        params = {
            "fields": self._fields,
            "limit": 100 if limit > 100 else limit,
            "offset": 0 if offset < 0 else offset,
            "ranking_type": ranking_type.value,
        }

        route = endpoints.GET_MANGA_RANKING.generate_route().with_params(params)
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(self._serializer.deserialize_manga_ranking(result.data))

get_manga async

get_manga(id: int | str) -> ResultT[Manga]

Get details of manga by the manga id.

Parameters:

Name Type Description Default
id int | str

The Id of the manga.

required

Returns:

Type Description
ResultT[Manga]

Result containing Manga on success or error data on error.

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.manga.get_manga(23390)

if result.is_success:
    manga_details = result.value

if result.is_error:
    error = result.error

await client.close()
Source code in aniwrap/services/manga.py
async def get_manga(self, id: int | str) -> ResultT[Manga]:
    """Get details of manga by the manga id.

    Args:
        id: The Id of the manga.

    Returns:
        [`Result`][aniwrap.Result] containing `Manga` on success or error data on error.

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.manga.get_manga(23390)

        if result.is_success:
            manga_details = result.value

        if result.is_error:
            error = result.error

        await client.close()
        ```
    """

    params = {"fields": self._fields}

    route = endpoints.GET_MANGA.generate_route(id).with_params(params)
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(self._serializer.deserialize_manga(result.data))

get_manga_ranking async

get_manga_ranking(
    ranking_type: MangaRankingType,
    *,
    limit: int | None = 10,
    offset: int | None = 0
) -> Result[list[MangaRanking]]

Get different types of manga rankings.

Parameters:

Name Type Description Default
ranking_type MangaRankingType

The type of ranking. Check enum MangaRankingType for all the possible types.

required

Other Parameters:

Name Type Description
limit int | None

The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100. Defaults to 10

offset int | None

The optional offset to use with requests, which specifies the offset from the start when fetching response. Defaults to 0

Returns:

Type Description
Result[list[MangaRanking]]

Result containing list[MangaRanking] model on success or error data on error.

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.manga.get_manga_ranking(MangaRankingType.ByPopularity, limit=5, offset=0)

if result.is_success:
    manga_list = result.value

if result.is_error:
    error = result.error

await client.close()
Source code in aniwrap/services/manga.py
async def get_manga_ranking(
    self,
    ranking_type: MangaRankingType,
    *,
    limit: int | None = 10,
    offset: int | None = 0,
) -> Result[list[MangaRanking]]:
    """Get different types of manga rankings.

    Args:
        ranking_type: The type of ranking. Check enum `MangaRankingType` for all the possible types.

    Keyword Args:
        limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
            Defaults to `10`

        offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
            Defaults to `0`

    Returns:
        [`Result`][aniwrap.Result] containing `list[MangaRanking]` model on success or error data on error.

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.manga.get_manga_ranking(MangaRankingType.ByPopularity, limit=5, offset=0)

        if result.is_success:
            manga_list = result.value

        if result.is_error:
            error = result.error

        await client.close()
        ```
    """

    params = {
        "fields": self._fields,
        "limit": 100 if limit > 100 else limit,
        "offset": 0 if offset < 0 else offset,
        "ranking_type": ranking_type.value,
    }

    route = endpoints.GET_MANGA_RANKING.generate_route().with_params(params)
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(self._serializer.deserialize_manga_ranking(result.data))

search_manga async

search_manga(
    manga_name: str,
    *,
    limit: int | None = 10,
    offset: int | None = 0
) -> ResultT[list[Manga]]

Search for Manga by title.

Parameters:

Name Type Description Default
manga_name str

Title or name of the manga.

required

Other Parameters:

Name Type Description
limit int | None

The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100. Defaults to 10

offset int | None

The optional offset to use with requests, which specifies the offset from the start when fetching response. Defaults to 0

Returns:

Type Description
ResultT[list[Manga]]

Result containing list[Manga] on success or error data on error.

Example
import aniwrap

client = aniwrap.Client(...)

result = await client.manga.search_manga("shingeki no kyojin", limit=5, offset=0)

if result.is_success:
    manga_list = result.value

if result.is_error:
    error = result.error

await client.close()
Source code in aniwrap/services/manga.py
async def search_manga(
    self, manga_name: str, *, limit: int | None = 10, offset: int | None = 0
) -> ResultT[list[Manga]]:
    """Search for Manga by title.

    Args:
        manga_name: Title or name of the manga.

    Keyword Args:
        limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
            Defaults to `10`

        offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
            Defaults to `0`

    Returns:
        [`Result`][aniwrap.Result] containing `list[Manga]` on success or error data on error.

    ??? example

        ```py
        import aniwrap

        client = aniwrap.Client(...)

        result = await client.manga.search_manga("shingeki no kyojin", limit=5, offset=0)

        if result.is_success:
            manga_list = result.value

        if result.is_error:
            error = result.error

        await client.close()
        ```
    """

    params = {
        "fields": self._fields,
        "limit": 100 if limit > 100 else limit,
        "offset": 0 if offset < 0 else offset,
        "q": manga_name,
    }

    route = endpoints.SEARCH_MANGA.generate_route().with_params(params)
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(self._serializer.deserialize_manga_results(result.data))

UserService

Bases: BaseService

Hanldes all the methods related to user Anime and Manga.

Source code in aniwrap/services/user.py
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
class UserService(BaseService):
    """Hanldes all the methods related to user Anime and Manga."""

    __slots__ = ()
    _anime_fields = "id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,created_at,updated_at,media_type,status,genres,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,related_manga,recommendations,studios,statistics,list_status"
    _manga_fields = "id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,created_at,updated_at,media_type,status,genres,my_list_status,num_volumes,num_chapters,authors{first_name,last_name},pictures,background,related_anime,related_manga,recommendations,serialization,list_status"

    # region anime
    async def get_anime_list(
        self,
        username: str,
        *,
        status: AnimeWatchStatus | None = None,
        sort: AnimeListSortType | None = None,
        limit: int | None = 10,
        offset: int | None = 0,
    ) -> ResultT[list[AnimeList]]:
        """Get anime list of a user by username.

        Args:
            username: The username of the user.

        KeywordArgs:
            status: The optional status based on which the results will be filtered. If not specified, the result will contain anime with all the status.
                Defaults to `None`.

            sort: The optional sort filter based on which the results will be sorted. If not specified, by default the results will be sorted in the descending order of last updated date.
                Defaults to `None`.

            limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
                Defaults to `10`

            offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
                Defaults to `0`

        Returns:
            [`Result`][aniwrap.Result] containing `list[AnimeList]` model on success or [`HttpErrorResponse`] data on error.


        ??? example

            ```py
            import aniwrap

            user_client = aniwrap.UserClient(...)

            result = await user_client.user.get_anime_list("your-user-name")

            if result.is_success:
                anime_list = result.value

            if result.is_error:
                error = result.error

            await user_client.close()
            ```
        """

        params = {
            "fields": self._anime_fields,
            "status": status.value if status else "",
            "sort": sort.value if sort else "",
            "limit": 100 if limit > 100 else limit,
            "offset": 0 if offset < 0 else offset,
        }

        route = endpoints.GET_USER_ANIME_LIST.generate_route(username).with_params(
            params
        )
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(
            [
                self._serializer.deserialize_user_anime_list(a)
                for a in result.data.get("data", [])
            ]
        )

    async def update_anime_list(
        self,
        anime_id: int,
        *,
        status: AnimeWatchStatus | None = None,
        is_rewatching: bool | None = None,
        score: int | None = None,
        num_watched_episodes: int | None = None,
        priority: ListPriority | None = None,
        num_times_rewatched: int | None = None,
        rewatch_value: AnimeRewatchValue | None = None,
        tags: str | None = None,
        comments: str | None = None,
    ) -> ResultT[AnimeListUpdate]:
        """Update anime details in user anime list. If the anime doesn't already exists, this will add the anime to the list.
            Only specify the params that need to be updated.

        Args:
            anime_id: The id of the anime.

        Keyword Args:
            status: The watch status of the anime.

            is_rewatching: The rewatching status of the anime.

            score: The score of the anime. Score should be in between 0 and 10.

            num_watched_episodes: The number of episodes watched.

            priority: The priority of the anime in the user list.

            num_times_rewatched: The number of times rewatched.

            rewatch_value: The rewatch value of the anime.

            tags: The tags that need to be added.

            comments: The user comments.

        Returns:
            [`Result`][aniwrap.Result] containing [`AnimeListUpdate`][aniwrap.AnimeListUpdate] model on success or `HttpErrorResponse` data on error.
            `HttpErrorResponse.status` will be `404` if no anime with the provided anime_id contains on MAL.

        ??? example

            ```py
            import aniwrap

            user_client = aniwrap.UserClient(...)

            result = await user_client.user.update_anime_list(5114, score=8)

            if result.is_success:
                anime_list_update = result.value

            if result.is_error:
                error = result.error

            await user_client.close()
            ```
        """

        data = self._generate_data_map(
            status=status,
            is_rewatching=is_rewatching,
            score=None
            if not score
            else 10
            if score > 10
            else 0
            if score < 0
            else score,
            num_watched_episodes=num_watched_episodes,
            priority=priority.value if priority else None,
            num_times_rewatched=num_times_rewatched,
            rewatch_value=rewatch_value.value if rewatch_value else None,
            tags=tags,
            comments=comments,
        )

        route = endpoints.UPDATE_USER_ANIME_LIST.generate_route(anime_id).with_data(
            data
        )
        result = await self._http.fetch(route, HttpMethod.Patch)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(self._serializer.deserialize_anime_list_update(result.data))

    async def delete_anime_from_list(self, anime_id: str) -> ResultT[str]:
        """Delete an anime from user's list.

        Args:
            anime_id: The Id of the anime to be deleted from the list.

        Returns:
            [`Result`][aniwrap.Result] containing `str` with success message on success or [`HttpErrorResponse`] data on error. [`HttpErrorResponse.status`] will be `404` if no anime with the provided anime_id contains on MAL.

        ??? example

            ```py
            import aniwrap

            user_client = aniwrap.UserClient(...)

            result = await user_client.user.delete_anime_from_list(52034)

            if result.is_success:
                print("deleted")

            if result.is_error:
                error = result.error

            await user_client.close()
            ```
        """

        route = endpoints.DELETE_ANIME_FROM_LIST.generate_route(anime_id)
        result = await self._http.fetch(route, HttpMethod.Delete)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success("Succesfully deleted anime from the list.")

    # endregion

    # region manga
    async def get_manga_list(
        self,
        username: str,
        *,
        status: MangaReadStatus | None = None,
        sort: MangaListSortType | None = None,
        limit: int | None = 10,
        offset: int | None = 0,
    ) -> ResultT[list[MangaList]]:
        """Get the manga list of a user by user name.

        Args:
            username: The user name of the user.

        KeywordArgs:
            status: The status with which the list need to be filtered. If specified, the result will only contain manga of this status.
                Defaults to `None` and result contains manga of all status'.

            sort: The type of sorting on the manga list.
                Defaults to `None`.

            limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
                Defaults to `10`

            offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
                Defaults to `0`

        Returns:
            [`Result`][aniwrap.Result] containing `list[MangaList]` model on success or [`HttpErrorResponse`] data on error.

        ??? example

            ```py
            import aniwrap

            user_client = aniwrap.UserClient(...)

            result = await await user_client.user.get_manga_list("your-user-name")

            if result.is_success:
                manga_list = result.value

            if result.is_error:
                error = result.error

            await user_client.close()
            ```
        """

        params = {
            "fields": self._manga_fields,
            "status": status.value if status else "",
            "sort": sort.value if sort else "",
            "limit": 100 if limit > 100 else limit,
            "offset": 0 if offset < 0 else offset,
        }

        route = endpoints.GET_USER_MANGA_LIST.generate_route(username).with_params(
            params
        )
        result = await self._http.fetch(route, HttpMethod.Get)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(
            [
                self._serializer.deserialize_user_manga_list(m)
                for m in result.data.get("data", [])
            ]
        )

    async def update_manga_list(
        self,
        manga_id: str,
        *,
        status: MangaReadStatus | None = None,
        is_rereading: bool | None = None,
        score: int | None = None,
        num_volumes_read: int | None = None,
        num_chapters_read: int | None = None,
        priority: ListPriority | None = None,
        num_times_reread: int | None = None,
        reread_value: MangaRereadValue | None = None,
        tags: str | None = None,
        comments: str | None = None,
    ) -> ResultT[MangaListUpdate]:
        """Update manga details in user manga list. If the manga doesn't already exists, this will add the manga to the list.
            Only specify the params that need to be updated/added.

        Args:
            manga_id: The id of the manga.

        Keyword Args:
            status: The Read status of the manga.

            is_rereading: The rereading status of the manga.

            score: The score of the manga. Score should be in between 0 and 10.

            num_volumes_read: The number of volumes read.

            num_chapters_read: The number of chapters read.

            priority: The priority of the manga in the user list.

            num_times_reread: The number of times reread.

            reread_value: The reread value of the manga.

            tags: The tags that need to be added.

            comments: The user comments.

        Returns:
            [`Result`][aniwrap.Result] containing [`MangaListUpdate`][aniwrap.MangaListUpdate] model on success or `HttpErrorResponse` data on error.
            `HttpErrorResponse.status` will be `404` if no manga with the provided manga_id contains on MAL.

        ??? example

            ```py
            import aniwrap

            user_client = aniwrap.UserClient(...)

            result = await user_client.user.update_manga_list(13759, status=MangaReadStatus.PlanToRead)

            if result.is_success:
                manga_list_update = result.value

            if result.is_error:
                error = result.error

            await user_client.close()
            ```
        """

        data = self._generate_data_map(
            status=status,
            is_rereading=is_rereading,
            score=None
            if not score
            else 10
            if score > 10
            else 0
            if score < 0
            else score,
            num_volumes_read=num_volumes_read,
            num_chapters_read=num_chapters_read,
            priority=priority.value if priority else None,
            num_times_reread=num_times_reread,
            reread_value=reread_value.value if reread_value else None,
            tags=tags,
            comments=comments,
        )

        route = endpoints.UPATE_USER_MANGA_LIST.generate_route(manga_id).with_data(data)
        result = await self._http.fetch(route, HttpMethod.Patch)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success(self._serializer.deserialize_manga_list_update(result.data))

    async def delete_manga_from_list(self, manga_id: str) -> ResultT[str]:
        """Delete an manga from user's list.

        Args:
            manga_id: The Id of the manga to be deleted from the list.

        Returns:
            [`Result`][aniwrap.Result] containing `str` with success message on success or [`HttpErrorResponse`] data on error. [`HttpErrorResponse.status`] will be `404` if no anime with the provided manga_id contains on MAL.

        ??? example

            ```py
            import aniwrap

            user_client = aniwrap.UserClient(...)

            result = await user_client.user.delete_manga_from_list(13759)

            if result.is_success:
                print("deleted")

            if result.is_error:
                error = result.error

            await user_client.close()
            ```
        """

        route = endpoints.DELETE_MANGA_FROM_LIST.generate_route(manga_id)
        result = await self._http.fetch(route, HttpMethod.Delete)

        if isinstance(result, HttpErrorResponse):
            return Error(result)

        return Success("Succesfully deleted manga from the list.")

    # endregion

    def _generate_data_map(self, **kwargs) -> dict[str, Any]:
        """Generarates a dict from input keyword args."""

        data = {}
        for key, value in kwargs.items():
            if value:
                data[key] = value

        return data

delete_anime_from_list async

delete_anime_from_list(anime_id: str) -> ResultT[str]

Delete an anime from user's list.

Parameters:

Name Type Description Default
anime_id str

The Id of the anime to be deleted from the list.

required

Returns:

Type Description
ResultT[str]

Result containing str with success message on success or [HttpErrorResponse] data on error. [HttpErrorResponse.status] will be 404 if no anime with the provided anime_id contains on MAL.

Example
import aniwrap

user_client = aniwrap.UserClient(...)

result = await user_client.user.delete_anime_from_list(52034)

if result.is_success:
    print("deleted")

if result.is_error:
    error = result.error

await user_client.close()
Source code in aniwrap/services/user.py
async def delete_anime_from_list(self, anime_id: str) -> ResultT[str]:
    """Delete an anime from user's list.

    Args:
        anime_id: The Id of the anime to be deleted from the list.

    Returns:
        [`Result`][aniwrap.Result] containing `str` with success message on success or [`HttpErrorResponse`] data on error. [`HttpErrorResponse.status`] will be `404` if no anime with the provided anime_id contains on MAL.

    ??? example

        ```py
        import aniwrap

        user_client = aniwrap.UserClient(...)

        result = await user_client.user.delete_anime_from_list(52034)

        if result.is_success:
            print("deleted")

        if result.is_error:
            error = result.error

        await user_client.close()
        ```
    """

    route = endpoints.DELETE_ANIME_FROM_LIST.generate_route(anime_id)
    result = await self._http.fetch(route, HttpMethod.Delete)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success("Succesfully deleted anime from the list.")

delete_manga_from_list async

delete_manga_from_list(manga_id: str) -> ResultT[str]

Delete an manga from user's list.

Parameters:

Name Type Description Default
manga_id str

The Id of the manga to be deleted from the list.

required

Returns:

Type Description
ResultT[str]

Result containing str with success message on success or [HttpErrorResponse] data on error. [HttpErrorResponse.status] will be 404 if no anime with the provided manga_id contains on MAL.

Example
import aniwrap

user_client = aniwrap.UserClient(...)

result = await user_client.user.delete_manga_from_list(13759)

if result.is_success:
    print("deleted")

if result.is_error:
    error = result.error

await user_client.close()
Source code in aniwrap/services/user.py
async def delete_manga_from_list(self, manga_id: str) -> ResultT[str]:
    """Delete an manga from user's list.

    Args:
        manga_id: The Id of the manga to be deleted from the list.

    Returns:
        [`Result`][aniwrap.Result] containing `str` with success message on success or [`HttpErrorResponse`] data on error. [`HttpErrorResponse.status`] will be `404` if no anime with the provided manga_id contains on MAL.

    ??? example

        ```py
        import aniwrap

        user_client = aniwrap.UserClient(...)

        result = await user_client.user.delete_manga_from_list(13759)

        if result.is_success:
            print("deleted")

        if result.is_error:
            error = result.error

        await user_client.close()
        ```
    """

    route = endpoints.DELETE_MANGA_FROM_LIST.generate_route(manga_id)
    result = await self._http.fetch(route, HttpMethod.Delete)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success("Succesfully deleted manga from the list.")

get_anime_list async

get_anime_list(
    username: str,
    *,
    status: AnimeWatchStatus | None = None,
    sort: AnimeListSortType | None = None,
    limit: int | None = 10,
    offset: int | None = 0
) -> ResultT[list[AnimeList]]

Get anime list of a user by username.

Parameters:

Name Type Description Default
username str

The username of the user.

required
KeywordArgs

status: The optional status based on which the results will be filtered. If not specified, the result will contain anime with all the status. Defaults to None.

sort: The optional sort filter based on which the results will be sorted. If not specified, by default the results will be sorted in the descending order of last updated date. Defaults to None.

limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100. Defaults to 10

offset: The optional offset to use with requests, which specifies the offset from the start when fetching response. Defaults to 0

Returns:

Type Description
ResultT[list[AnimeList]]

Result containing list[AnimeList] model on success or [HttpErrorResponse] data on error.

Example
import aniwrap

user_client = aniwrap.UserClient(...)

result = await user_client.user.get_anime_list("your-user-name")

if result.is_success:
    anime_list = result.value

if result.is_error:
    error = result.error

await user_client.close()
Source code in aniwrap/services/user.py
async def get_anime_list(
    self,
    username: str,
    *,
    status: AnimeWatchStatus | None = None,
    sort: AnimeListSortType | None = None,
    limit: int | None = 10,
    offset: int | None = 0,
) -> ResultT[list[AnimeList]]:
    """Get anime list of a user by username.

    Args:
        username: The username of the user.

    KeywordArgs:
        status: The optional status based on which the results will be filtered. If not specified, the result will contain anime with all the status.
            Defaults to `None`.

        sort: The optional sort filter based on which the results will be sorted. If not specified, by default the results will be sorted in the descending order of last updated date.
            Defaults to `None`.

        limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
            Defaults to `10`

        offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
            Defaults to `0`

    Returns:
        [`Result`][aniwrap.Result] containing `list[AnimeList]` model on success or [`HttpErrorResponse`] data on error.


    ??? example

        ```py
        import aniwrap

        user_client = aniwrap.UserClient(...)

        result = await user_client.user.get_anime_list("your-user-name")

        if result.is_success:
            anime_list = result.value

        if result.is_error:
            error = result.error

        await user_client.close()
        ```
    """

    params = {
        "fields": self._anime_fields,
        "status": status.value if status else "",
        "sort": sort.value if sort else "",
        "limit": 100 if limit > 100 else limit,
        "offset": 0 if offset < 0 else offset,
    }

    route = endpoints.GET_USER_ANIME_LIST.generate_route(username).with_params(
        params
    )
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(
        [
            self._serializer.deserialize_user_anime_list(a)
            for a in result.data.get("data", [])
        ]
    )

get_manga_list async

get_manga_list(
    username: str,
    *,
    status: MangaReadStatus | None = None,
    sort: MangaListSortType | None = None,
    limit: int | None = 10,
    offset: int | None = 0
) -> ResultT[list[MangaList]]

Get the manga list of a user by user name.

Parameters:

Name Type Description Default
username str

The user name of the user.

required
KeywordArgs

status: The status with which the list need to be filtered. If specified, the result will only contain manga of this status. Defaults to None and result contains manga of all status'.

sort: The type of sorting on the manga list. Defaults to None.

limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100. Defaults to 10

offset: The optional offset to use with requests, which specifies the offset from the start when fetching response. Defaults to 0

Returns:

Type Description
ResultT[list[MangaList]]

Result containing list[MangaList] model on success or [HttpErrorResponse] data on error.

Example
import aniwrap

user_client = aniwrap.UserClient(...)

result = await await user_client.user.get_manga_list("your-user-name")

if result.is_success:
    manga_list = result.value

if result.is_error:
    error = result.error

await user_client.close()
Source code in aniwrap/services/user.py
async def get_manga_list(
    self,
    username: str,
    *,
    status: MangaReadStatus | None = None,
    sort: MangaListSortType | None = None,
    limit: int | None = 10,
    offset: int | None = 0,
) -> ResultT[list[MangaList]]:
    """Get the manga list of a user by user name.

    Args:
        username: The user name of the user.

    KeywordArgs:
        status: The status with which the list need to be filtered. If specified, the result will only contain manga of this status.
            Defaults to `None` and result contains manga of all status'.

        sort: The type of sorting on the manga list.
            Defaults to `None`.

        limit: The optional limit to use with requests, which specifies the number of results in the response. Should be between 1 and 100.
            Defaults to `10`

        offset: The optional offset to use with requests, which specifies the offset from the start when fetching response.
            Defaults to `0`

    Returns:
        [`Result`][aniwrap.Result] containing `list[MangaList]` model on success or [`HttpErrorResponse`] data on error.

    ??? example

        ```py
        import aniwrap

        user_client = aniwrap.UserClient(...)

        result = await await user_client.user.get_manga_list("your-user-name")

        if result.is_success:
            manga_list = result.value

        if result.is_error:
            error = result.error

        await user_client.close()
        ```
    """

    params = {
        "fields": self._manga_fields,
        "status": status.value if status else "",
        "sort": sort.value if sort else "",
        "limit": 100 if limit > 100 else limit,
        "offset": 0 if offset < 0 else offset,
    }

    route = endpoints.GET_USER_MANGA_LIST.generate_route(username).with_params(
        params
    )
    result = await self._http.fetch(route, HttpMethod.Get)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(
        [
            self._serializer.deserialize_user_manga_list(m)
            for m in result.data.get("data", [])
        ]
    )

update_anime_list async

update_anime_list(
    anime_id: int,
    *,
    status: AnimeWatchStatus | None = None,
    is_rewatching: bool | None = None,
    score: int | None = None,
    num_watched_episodes: int | None = None,
    priority: ListPriority | None = None,
    num_times_rewatched: int | None = None,
    rewatch_value: AnimeRewatchValue | None = None,
    tags: str | None = None,
    comments: str | None = None
) -> ResultT[AnimeListUpdate]

Update anime details in user anime list. If the anime doesn't already exists, this will add the anime to the list. Only specify the params that need to be updated.

Parameters:

Name Type Description Default
anime_id int

The id of the anime.

required

Other Parameters:

Name Type Description
status AnimeWatchStatus | None

The watch status of the anime.

is_rewatching bool | None

The rewatching status of the anime.

score int | None

The score of the anime. Score should be in between 0 and 10.

num_watched_episodes int | None

The number of episodes watched.

priority ListPriority | None

The priority of the anime in the user list.

num_times_rewatched int | None

The number of times rewatched.

rewatch_value AnimeRewatchValue | None

The rewatch value of the anime.

tags str | None

The tags that need to be added.

comments str | None

The user comments.

Returns:

Type Description
ResultT[AnimeListUpdate]

Result containing AnimeListUpdate model on success or HttpErrorResponse data on error.

ResultT[AnimeListUpdate]

HttpErrorResponse.status will be 404 if no anime with the provided anime_id contains on MAL.

Example
import aniwrap

user_client = aniwrap.UserClient(...)

result = await user_client.user.update_anime_list(5114, score=8)

if result.is_success:
    anime_list_update = result.value

if result.is_error:
    error = result.error

await user_client.close()
Source code in aniwrap/services/user.py
async def update_anime_list(
    self,
    anime_id: int,
    *,
    status: AnimeWatchStatus | None = None,
    is_rewatching: bool | None = None,
    score: int | None = None,
    num_watched_episodes: int | None = None,
    priority: ListPriority | None = None,
    num_times_rewatched: int | None = None,
    rewatch_value: AnimeRewatchValue | None = None,
    tags: str | None = None,
    comments: str | None = None,
) -> ResultT[AnimeListUpdate]:
    """Update anime details in user anime list. If the anime doesn't already exists, this will add the anime to the list.
        Only specify the params that need to be updated.

    Args:
        anime_id: The id of the anime.

    Keyword Args:
        status: The watch status of the anime.

        is_rewatching: The rewatching status of the anime.

        score: The score of the anime. Score should be in between 0 and 10.

        num_watched_episodes: The number of episodes watched.

        priority: The priority of the anime in the user list.

        num_times_rewatched: The number of times rewatched.

        rewatch_value: The rewatch value of the anime.

        tags: The tags that need to be added.

        comments: The user comments.

    Returns:
        [`Result`][aniwrap.Result] containing [`AnimeListUpdate`][aniwrap.AnimeListUpdate] model on success or `HttpErrorResponse` data on error.
        `HttpErrorResponse.status` will be `404` if no anime with the provided anime_id contains on MAL.

    ??? example

        ```py
        import aniwrap

        user_client = aniwrap.UserClient(...)

        result = await user_client.user.update_anime_list(5114, score=8)

        if result.is_success:
            anime_list_update = result.value

        if result.is_error:
            error = result.error

        await user_client.close()
        ```
    """

    data = self._generate_data_map(
        status=status,
        is_rewatching=is_rewatching,
        score=None
        if not score
        else 10
        if score > 10
        else 0
        if score < 0
        else score,
        num_watched_episodes=num_watched_episodes,
        priority=priority.value if priority else None,
        num_times_rewatched=num_times_rewatched,
        rewatch_value=rewatch_value.value if rewatch_value else None,
        tags=tags,
        comments=comments,
    )

    route = endpoints.UPDATE_USER_ANIME_LIST.generate_route(anime_id).with_data(
        data
    )
    result = await self._http.fetch(route, HttpMethod.Patch)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(self._serializer.deserialize_anime_list_update(result.data))

update_manga_list async

update_manga_list(
    manga_id: str,
    *,
    status: MangaReadStatus | None = None,
    is_rereading: bool | None = None,
    score: int | None = None,
    num_volumes_read: int | None = None,
    num_chapters_read: int | None = None,
    priority: ListPriority | None = None,
    num_times_reread: int | None = None,
    reread_value: MangaRereadValue | None = None,
    tags: str | None = None,
    comments: str | None = None
) -> ResultT[MangaListUpdate]

Update manga details in user manga list. If the manga doesn't already exists, this will add the manga to the list. Only specify the params that need to be updated/added.

Parameters:

Name Type Description Default
manga_id str

The id of the manga.

required

Other Parameters:

Name Type Description
status MangaReadStatus | None

The Read status of the manga.

is_rereading bool | None

The rereading status of the manga.

score int | None

The score of the manga. Score should be in between 0 and 10.

num_volumes_read int | None

The number of volumes read.

num_chapters_read int | None

The number of chapters read.

priority ListPriority | None

The priority of the manga in the user list.

num_times_reread int | None

The number of times reread.

reread_value MangaRereadValue | None

The reread value of the manga.

tags str | None

The tags that need to be added.

comments str | None

The user comments.

Returns:

Type Description
ResultT[MangaListUpdate]

Result containing MangaListUpdate model on success or HttpErrorResponse data on error.

ResultT[MangaListUpdate]

HttpErrorResponse.status will be 404 if no manga with the provided manga_id contains on MAL.

Example
import aniwrap

user_client = aniwrap.UserClient(...)

result = await user_client.user.update_manga_list(13759, status=MangaReadStatus.PlanToRead)

if result.is_success:
    manga_list_update = result.value

if result.is_error:
    error = result.error

await user_client.close()
Source code in aniwrap/services/user.py
async def update_manga_list(
    self,
    manga_id: str,
    *,
    status: MangaReadStatus | None = None,
    is_rereading: bool | None = None,
    score: int | None = None,
    num_volumes_read: int | None = None,
    num_chapters_read: int | None = None,
    priority: ListPriority | None = None,
    num_times_reread: int | None = None,
    reread_value: MangaRereadValue | None = None,
    tags: str | None = None,
    comments: str | None = None,
) -> ResultT[MangaListUpdate]:
    """Update manga details in user manga list. If the manga doesn't already exists, this will add the manga to the list.
        Only specify the params that need to be updated/added.

    Args:
        manga_id: The id of the manga.

    Keyword Args:
        status: The Read status of the manga.

        is_rereading: The rereading status of the manga.

        score: The score of the manga. Score should be in between 0 and 10.

        num_volumes_read: The number of volumes read.

        num_chapters_read: The number of chapters read.

        priority: The priority of the manga in the user list.

        num_times_reread: The number of times reread.

        reread_value: The reread value of the manga.

        tags: The tags that need to be added.

        comments: The user comments.

    Returns:
        [`Result`][aniwrap.Result] containing [`MangaListUpdate`][aniwrap.MangaListUpdate] model on success or `HttpErrorResponse` data on error.
        `HttpErrorResponse.status` will be `404` if no manga with the provided manga_id contains on MAL.

    ??? example

        ```py
        import aniwrap

        user_client = aniwrap.UserClient(...)

        result = await user_client.user.update_manga_list(13759, status=MangaReadStatus.PlanToRead)

        if result.is_success:
            manga_list_update = result.value

        if result.is_error:
            error = result.error

        await user_client.close()
        ```
    """

    data = self._generate_data_map(
        status=status,
        is_rereading=is_rereading,
        score=None
        if not score
        else 10
        if score > 10
        else 0
        if score < 0
        else score,
        num_volumes_read=num_volumes_read,
        num_chapters_read=num_chapters_read,
        priority=priority.value if priority else None,
        num_times_reread=num_times_reread,
        reread_value=reread_value.value if reread_value else None,
        tags=tags,
        comments=comments,
    )

    route = endpoints.UPATE_USER_MANGA_LIST.generate_route(manga_id).with_data(data)
    result = await self._http.fetch(route, HttpMethod.Patch)

    if isinstance(result, HttpErrorResponse):
        return Error(result)

    return Success(self._serializer.deserialize_manga_list_update(result.data))