From 716f4d6ffc24306719bcb486b13021d996aaa395 Mon Sep 17 00:00:00 2001 From: Snayt1k3 Date: Sun, 26 May 2024 12:24:12 +0300 Subject: [PATCH 1/7] run black, and start add graphql endpoints --- shikimori/endpoints/grapql.py | 122 ++++++++++++++++++++++++++++++++++ shikimori/endpoints/users.py | 6 +- shikimori/enums.py | 62 +++++++++++++++++ 3 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 shikimori/endpoints/grapql.py create mode 100644 shikimori/enums.py diff --git a/shikimori/endpoints/grapql.py b/shikimori/endpoints/grapql.py new file mode 100644 index 0000000..1c47b73 --- /dev/null +++ b/shikimori/endpoints/grapql.py @@ -0,0 +1,122 @@ +import logging + +from shikimori.enums import ( + OrderEnum, + GenreEntryTypeEnum, + UserRateTargetTypeEnum, + UserRateStatusEnum, + UserRateOrderInputType, +) +from .base import BaseEndpoint + +logger = logging.getLogger(__name__) + + +class GraphQlEndpoint(BaseEndpoint): + async def animes( + self, + page: int = 1, + limit: int = 1, + order: OrderEnum = None, + kind: str = None, + status: str = None, + season: str = None, + score: int = None, + duration: int = None, + rating: str = None, + genre: str = None, + studio: str = None, + franchise: str = None, + censored=True, + mylist: str = None, + ids: str = None, + excludeIds: str = None, + search: str = None, + ): + """ + :param kind: List of values separated by comma. Add ! before value to apply negative filter. values: tv, movie, ova, ona, special, tv_special, music, pv, cm, tv_13, tv_24, tv_48 + :param status: List of values separated by comma. Add ! before value to apply negative filter. + :param season: List of values separated by comma. Add ! before value to apply negative filter. Examples: summer_2017, 2016, 2014_2016, 199x + :param score: Minimal anime score + :param duration: List of values separated by comma. Add ! before value to apply negative filter. Examples: S - Less than 10 minutes, D - Less than 30 minutes, F - More than 30 minutes + :param rating: List of values separated by comma. Add ! before value to apply negative filter. Examples: none - No rating, g - G - All ages, pg - PG - Children, pg_13 - PG-13 - Teens 13 or older, r - R - 17+ recommended (violence & profanity), r_plus - R+ - Mild Nudity (may also contain violence & profanity), rx - Rx - Hentai (extreme sexual content/nudity) + :param genre: List of comma separated genre ids + :param studio: List of comma separated studio ids + :param franchise: List of comma separated franchises + :param censored: Set to false to allow hentai, yaoi and yuri + :param mylist: List of values separated by comma. Add ! before value to apply negative filter. Examples: planned - Planned to Watch, watching - Watching, rewatching - Rewatching, completed - Completed, on_hold - On Hold, dropped - Dropped + :param ids: List of comma separated ids + :param excludeIds: List of comma separated ids + :param search: must be a string + :param order: OrderEnum + :param page: must be a number + :param limit: Maximum 50 + """ + + pass + + async def characters( + self, page: int = 1, limit: int = 1, ids: str = None, search: str = None + ): + pass + + async def contests(self, page: int = 1, limit: int = 1, ids: str = None): + pass + + async def currentUser(self): + pass + + async def genres(self, entry: GenreEntryTypeEnum): + pass + + async def mangas( + self, + page: int = 1, + limit: int = 1, + order: OrderEnum = None, + kind: str = None, + status: str = None, + season: str = None, + score: int = None, + genre: str = None, + publisher: str = None, + franchise: str = None, + censored=True, + mylist: str = None, + ids: str = None, + excludeIds: str = None, + search: str = None, + ): + pass + + async def people( + self, + page: int = 1, + limit: int = 1, + ids: str = None, + search: str = None, + isSeyu: bool = None, + isProducer: bool = None, + isMangaka: bool = None, + ): + pass + + async def userRates( + self, + userId: int, + page: int = 1, + limit: int = 1, + targetType: UserRateTargetTypeEnum = None, + status: UserRateStatusEnum = None, + order: UserRateOrderInputType = None, + ): + pass + + async def users( + self, + page: int = 1, + limit: int = 1, + ids: str = None, + search: str = None + ): + pass diff --git a/shikimori/endpoints/users.py b/shikimori/endpoints/users.py index e145cd0..86f79dc 100644 --- a/shikimori/endpoints/users.py +++ b/shikimori/endpoints/users.py @@ -125,7 +125,9 @@ async def signOut(self) -> None | RequestError: return response - async def friends(self, page: int = None, limit: int = None) -> List[User] | RequestError: + async def friends( + self, page: int = None, limit: int = None + ) -> List[User] | RequestError: """ Show user's friends :param limit: 100 maximum @@ -135,7 +137,7 @@ async def friends(self, page: int = None, limit: int = None) -> List[User] | Req "GET", url=f"{self._base_url}/api/users/friends", headers=self.headers, - json=filter_none_parameters({"page": page, "limit": limit}) + json=filter_none_parameters({"page": page, "limit": limit}), ) if not isinstance(response, RequestError): return [User.from_dict(u) for u in response] diff --git a/shikimori/enums.py b/shikimori/enums.py new file mode 100644 index 0000000..40fa504 --- /dev/null +++ b/shikimori/enums.py @@ -0,0 +1,62 @@ +import dataclasses +from enum import Enum + + +class OrderEnum(Enum): + ID = "id" + ID_DESC = "id_desc" + RANKED = "ranked" + KIND = "kind" + POPULARITY = "popularity" + NAME = "name" + AIRED_ON = "aired_on" + EPISODES = "episodes" + STATUS = "status" + RANDOM = "random" + RANKED_RANDOM = "ranked_random" + RANKED_SHIKI = "ranked_shiki" + CREATED_AT = "created_at" + CREATED_AT_DESC = "created_at_desc" + + def __str__(self): + return self.value + +class GenreEntryTypeEnum(Enum): + ANIME = "Anime" + MANGA = "Manga" + + def __str__(self): + return self.value + +class UserRateTargetTypeEnum(Enum): + ANIME = "Anime" + MANGA = "Manga" + + def __str__(self): + return self.value + + +class UserRateStatusEnum(Enum): + PLANNED = "planned" + WATCHING = "watching" + REWATCHING = "rewatching" + COMPLETED = "completed" + ON_HOLD = "on_hold" + DROPPED = "dropped" + + def __str__(self): + return self.value + + +class UserRateOrderFieldEnum(Enum): + ID = "id" + UPDATED_AT = "updated_at" + +class SortOrderEnum(Enum): + ASC = "asc" + DESC = "desc" + +@dataclasses.dataclass +class UserRateOrderInputType: + field: UserRateOrderFieldEnum + order: SortOrderEnum From 83a1943655a3f2e120128cec345ac40996ddf6d9 Mon Sep 17 00:00:00 2001 From: Snayt1k3 Date: Sun, 26 May 2024 14:20:27 +0300 Subject: [PATCH 2/7] run black, add description to graphql methods --- shikimori/endpoints/episode_notifications.py | 2 +- shikimori/endpoints/grapql.py | 63 ++++++++++++++++++-- shikimori/enums.py | 4 ++ 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/shikimori/endpoints/episode_notifications.py b/shikimori/endpoints/episode_notifications.py index 4a24c33..aee567f 100644 --- a/shikimori/endpoints/episode_notifications.py +++ b/shikimori/endpoints/episode_notifications.py @@ -22,7 +22,7 @@ async def notify( ) -> EpisodeNotification | RequestError: """ Notify shikimori about anime episode release - + :param token: Private token required to access this api :param anime_id: Must be a number. :param episode: Must be a number. diff --git a/shikimori/endpoints/grapql.py b/shikimori/endpoints/grapql.py index 1c47b73..a9d28c0 100644 --- a/shikimori/endpoints/grapql.py +++ b/shikimori/endpoints/grapql.py @@ -58,9 +58,22 @@ async def animes( async def characters( self, page: int = 1, limit: int = 1, ids: str = None, search: str = None ): + """ + + :param page: int + :param limit: Maximum 50 + :param ids: list of separated ids + :param search: str + """ pass async def contests(self, page: int = 1, limit: int = 1, ids: str = None): + """ + + :param page: int + :param limit: Maximum 10 + :param ids: list of separated ids + """ pass async def currentUser(self): @@ -87,6 +100,22 @@ async def mangas( excludeIds: str = None, search: str = None, ): + """ + :param kind: List of values separated by comma. Add ! before value to apply negative filter. values: tv, movie, ova, ona, special, tv_special, music, pv, cm, tv_13, tv_24, tv_48 + :param status: List of values separated by comma. Add ! before value to apply negative filter. + :param season: List of values separated by comma. Add ! before value to apply negative filter. Examples: summer_2017, 2016, 2014_2016, 199x + :param score: Minimal anime score + :param genre: List of comma separated genre ids + :param franchise: List of comma separated franchises + :param censored: Set to false to allow hentai, yaoi and yuri + :param mylist: List of values separated by comma. Add ! before value to apply negative filter. Examples: planned - Planned to Watch, watching - Watching, rewatching - Rewatching, completed - Completed, on_hold - On Hold, dropped - Dropped + :param ids: List of comma separated ids + :param excludeIds: List of comma separated ids + :param search: must be a string + :param order: OrderEnum + :param page: must be a number + :param limit: Maximum 50 + """ pass async def people( @@ -99,24 +128,46 @@ async def people( isProducer: bool = None, isMangaka: bool = None, ): + """ + + :param page: int + :param limit: Maximum 10 + :param ids: List of values separated by comma. + :param search: str + :param isSeyu: bool + :param isProducer: bool + :param isMangaka: bool + """ pass async def userRates( self, - userId: int, + userId: int = None, page: int = 1, limit: int = 1, targetType: UserRateTargetTypeEnum = None, status: UserRateStatusEnum = None, order: UserRateOrderInputType = None, ): + """ + + :param userId: ID of current user is used by default + :param page: int + :param limit: maximum - 50 + :param targetType: UserRateTargetTypeEnum + :param status: UserRateStatusEnum + :param order: UserRateOrderInputType + """ pass async def users( - self, - page: int = 1, - limit: int = 1, - ids: str = None, - search: str = None + self, page: int = 1, limit: int = 1, ids: str = None, search: str = None ): + """ + + :param page: int + :param limit: maximum - 50 + :param ids: List of values separated by comma. + :param search: str + """ pass diff --git a/shikimori/enums.py b/shikimori/enums.py index 40fa504..c7f458d 100644 --- a/shikimori/enums.py +++ b/shikimori/enums.py @@ -21,6 +21,7 @@ class OrderEnum(Enum): def __str__(self): return self.value + class GenreEntryTypeEnum(Enum): ANIME = "Anime" MANGA = "Manga" @@ -28,6 +29,7 @@ class GenreEntryTypeEnum(Enum): def __str__(self): return self.value + class UserRateTargetTypeEnum(Enum): ANIME = "Anime" MANGA = "Manga" @@ -52,10 +54,12 @@ class UserRateOrderFieldEnum(Enum): ID = "id" UPDATED_AT = "updated_at" + class SortOrderEnum(Enum): ASC = "asc" DESC = "desc" + @dataclasses.dataclass class UserRateOrderInputType: field: UserRateOrderFieldEnum From 669697333950f7369405e2a16c39291c5c1ee6b1 Mon Sep 17 00:00:00 2001 From: Snayt1k3 Date: Sun, 26 May 2024 14:22:45 +0300 Subject: [PATCH 3/7] add missing parameter --- shikimori/endpoints/grapql.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shikimori/endpoints/grapql.py b/shikimori/endpoints/grapql.py index a9d28c0..3ab7c7a 100644 --- a/shikimori/endpoints/grapql.py +++ b/shikimori/endpoints/grapql.py @@ -101,6 +101,7 @@ async def mangas( search: str = None, ): """ + :param publisher: List of comma separated publisher ids :param kind: List of values separated by comma. Add ! before value to apply negative filter. values: tv, movie, ova, ona, special, tv_special, music, pv, cm, tv_13, tv_24, tv_48 :param status: List of values separated by comma. Add ! before value to apply negative filter. :param season: List of values separated by comma. Add ! before value to apply negative filter. Examples: summer_2017, 2016, 2014_2016, 199x From 0df686ddd4bd5f85b006271bab16bdf89545c658 Mon Sep 17 00:00:00 2001 From: Snayt1k3 Date: Tue, 28 May 2024 10:50:33 +0300 Subject: [PATCH 4/7] add methods(BETA) --- shikimori/endpoints/grapql.py | 299 +++++++++++++++++++++++++++++++--- 1 file changed, 276 insertions(+), 23 deletions(-) diff --git a/shikimori/endpoints/grapql.py b/shikimori/endpoints/grapql.py index 3ab7c7a..c40b5f4 100644 --- a/shikimori/endpoints/grapql.py +++ b/shikimori/endpoints/grapql.py @@ -7,6 +7,7 @@ UserRateStatusEnum, UserRateOrderInputType, ) +from shikimori.exceptions import RequestError from .base import BaseEndpoint logger = logging.getLogger(__name__) @@ -15,6 +16,7 @@ class GraphQlEndpoint(BaseEndpoint): async def animes( self, + query: str, page: int = 1, limit: int = 1, order: OrderEnum = None, @@ -32,8 +34,10 @@ async def animes( ids: str = None, excludeIds: str = None, search: str = None, - ): + ) -> dict | RequestError: """ + + :param query: graphql query :param kind: List of values separated by comma. Add ! before value to apply negative filter. values: tv, movie, ova, ona, special, tv_special, music, pv, cm, tv_13, tv_24, tv_48 :param status: List of values separated by comma. Add ! before value to apply negative filter. :param season: List of values separated by comma. Add ! before value to apply negative filter. Examples: summer_2017, 2016, 2014_2016, 199x @@ -53,37 +57,172 @@ async def animes( :param limit: Maximum 50 """ - pass + response = await self._request.make_request( + "POST", + url=f"{self._base_url}/api/graphql", + json={ + "query": query, + "variables": { + "page": page, + "limit": limit, + "order": order.value if order else None, + "kind": kind, + "status": status, + "season": season, + "score": score, + "duration": duration, + "rating": rating, + "genre": genre, + "studio": studio, + "franchise": franchise, + "censored": censored, + "mylist": mylist, + "ids": ids, + "excludeIds": excludeIds, + "search": search, + }, + }, + headers=self.headers, + ) + + if not isinstance(response, RequestError): + return response + + logger.debug( + f"Bad Request(animes graphql): status - {response.status_code}: info - {str(response)}" + ) + + return response async def characters( - self, page: int = 1, limit: int = 1, ids: str = None, search: str = None - ): + self, + query: str, + page: int = 1, + limit: int = 1, + ids: str = None, + search: str = None, + ) -> dict | RequestError: """ + :param query: graphql query :param page: int :param limit: Maximum 50 :param ids: list of separated ids :param search: str """ - pass + response = await self._request.make_request( + "POST", + url=f"{self._base_url}/api/graphql", + json={ + "query": query, + "variables": { + "page": page, + "limit": limit, + "ids": ids, + "search": search, + }, + }, + headers=self.headers, + ) + + if not isinstance(response, RequestError): + return response + + logger.debug( + f"Bad Request(characters graphql): status - {response.status_code}: info - {str(response)}" + ) + + return response - async def contests(self, page: int = 1, limit: int = 1, ids: str = None): + async def contests( + self, query: str, page: int = 1, limit: int = 1, ids: str = None + ) -> dict | RequestError: """ + :param query: graphql query :param page: int :param limit: Maximum 10 :param ids: list of separated ids """ - pass + response = await self._request.make_request( + "POST", + url=f"{self._base_url}/api/graphql", + json={ + "query": query, + "variables": { + "page": page, + "limit": limit, + "ids": ids, + }, + }, + headers=self.headers, + ) + + if not isinstance(response, RequestError): + return response + + logger.debug( + f"Bad Request(contests graphql): status - {response.status_code}: info - {str(response)}" + ) + + return response + + async def currentUser( + self, + query: str, + ) -> dict | RequestError: + """ + :param query: graphql query + """ + response = await self._request.make_request( + "POST", + url=f"{self._base_url}/api/graphql", + json={ + "query": query, + }, + headers=self.headers, + ) + + if not isinstance(response, RequestError): + return response + + logger.debug( + f"Bad Request(currentUser graphql): status - {response.status_code}: info - {str(response)}" + ) + + return response + + async def genres( + self, query: str, entry: GenreEntryTypeEnum + ) -> dict | RequestError: + """ + :param query: graphql query + :param entry: + """ + response = await self._request.make_request( + "POST", + url=f"{self._base_url}/api/graphql", + json={ + "query": query, + "variables": { + "entryType": str(entry), + }, + }, + headers=self.headers, + ) + + if not isinstance(response, RequestError): + return response - async def currentUser(self): - pass + logger.debug( + f"Bad Request(genres graphql): status - {response.status_code}: info - {str(response)}" + ) - async def genres(self, entry: GenreEntryTypeEnum): - pass + return response async def mangas( self, + query: str, page: int = 1, limit: int = 1, order: OrderEnum = None, @@ -99,8 +238,9 @@ async def mangas( ids: str = None, excludeIds: str = None, search: str = None, - ): + ) -> dict | RequestError: """ + :param query: graphql query :param publisher: List of comma separated publisher ids :param kind: List of values separated by comma. Add ! before value to apply negative filter. values: tv, movie, ova, ona, special, tv_special, music, pv, cm, tv_13, tv_24, tv_48 :param status: List of values separated by comma. Add ! before value to apply negative filter. @@ -117,10 +257,44 @@ async def mangas( :param page: must be a number :param limit: Maximum 50 """ - pass + response = await self._request.make_request( + "POST", + url=f"{self._base_url}/api/graphql", + json={ + "query": query, + "variables": { + "page": page, + "limit": limit, + "order": order.value if order else None, + "kind": kind, + "status": status, + "season": season, + "score": score, + "genre": genre, + "franchise": franchise, + "censored": censored, + "mylist": mylist, + "ids": ids, + "excludeIds": excludeIds, + "search": search, + "publisher": publisher, + }, + }, + headers=self.headers, + ) + + if not isinstance(response, RequestError): + return response + + logger.debug( + f"Bad Request(mangas graphql): status - {response.status_code}: info - {str(response)}" + ) + + return response async def people( self, + query: str, page: int = 1, limit: int = 1, ids: str = None, @@ -128,9 +302,9 @@ async def people( isSeyu: bool = None, isProducer: bool = None, isMangaka: bool = None, - ): + ) -> dict | RequestError: """ - + :param query: graphql query :param page: int :param limit: Maximum 10 :param ids: List of values separated by comma. @@ -139,19 +313,45 @@ async def people( :param isProducer: bool :param isMangaka: bool """ - pass + response = await self._request.make_request( + "POST", + url=f"{self._base_url}/api/graphql", + json={ + "query": query, + "variables": { + "page": page, + "limit": limit, + "search": search, + "isSeyu": isSeyu, + "isProducer": isProducer, + "isMangaka": isMangaka, + "ids": ids, + }, + }, + headers=self.headers, + ) + + if not isinstance(response, RequestError): + return response + + logger.debug( + f"Bad Request(people graphql): status - {response.status_code}: info - {str(response)}" + ) + + return response async def userRates( self, + query: str, userId: int = None, page: int = 1, limit: int = 1, targetType: UserRateTargetTypeEnum = None, status: UserRateStatusEnum = None, order: UserRateOrderInputType = None, - ): + ) -> dict | RequestError: """ - + :param query: graphql query :param userId: ID of current user is used by default :param page: int :param limit: maximum - 50 @@ -159,16 +359,69 @@ async def userRates( :param status: UserRateStatusEnum :param order: UserRateOrderInputType """ - pass + response = await self._request.make_request( + "POST", + url=f"{self._base_url}/api/graphql", + json={ + "query": query, + "variables": { + "page": page, + "limit": limit, + "status": status.value if status else None, + "userId": userId, + "targetType": targetType.value if targetType else None, + "order": ( + {"field": order.field, "order": order.order} if order else None + ), + }, + }, + headers=self.headers, + ) + + if not isinstance(response, RequestError): + return response + + logger.debug( + f"Bad Request(userRates graphql): status - {response.status_code}: info - {str(response)}" + ) + + return response async def users( - self, page: int = 1, limit: int = 1, ids: str = None, search: str = None - ): + self, + query: str, + page: int = 1, + limit: int = 1, + ids: str = None, + search: str = None, + ) -> dict | RequestError: """ - + :param query: graphql query :param page: int :param limit: maximum - 50 :param ids: List of values separated by comma. :param search: str """ - pass + response = await self._request.make_request( + "POST", + url=f"{self._base_url}/api/graphql", + json={ + "query": query, + "variables": { + "page": page, + "limit": limit, + "search": search, + "ids": ids, + }, + }, + headers=self.headers, + ) + + if not isinstance(response, RequestError): + return response + + logger.debug( + f"Bad Request(users graphql): status - {response.status_code}: info - {str(response)}" + ) + + return response From 2e5d18cbf3419b05a549f1e41596bc5a1dd65c71 Mon Sep 17 00:00:00 2001 From: Snayt1k3 Date: Tue, 28 May 2024 13:02:07 +0300 Subject: [PATCH 5/7] add tests --- tests/conftest.py | 1 + tests/fixtures/unit/endpoints/graphql.py | 9 ++ tests/unit/endpoints/test_graphql.py | 123 +++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 tests/fixtures/unit/endpoints/graphql.py create mode 100644 tests/unit/endpoints/test_graphql.py diff --git a/tests/conftest.py b/tests/conftest.py index 40a01ba..a0f6dd7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,4 +37,5 @@ from tests.fixtures.unit.endpoints.topic import * from tests.fixtures.unit.endpoints.users import * from tests.fixtures.unit.endpoints.videos import * +from tests.fixtures.unit.endpoints.graphql import * from tests.fixtures.integration.client import * diff --git a/tests/fixtures/unit/endpoints/graphql.py b/tests/fixtures/unit/endpoints/graphql.py new file mode 100644 index 0000000..e3c89ae --- /dev/null +++ b/tests/fixtures/unit/endpoints/graphql.py @@ -0,0 +1,9 @@ +import pytest + +from shikimori.endpoints.grapql import GraphQlEndpoint + + +@pytest.fixture +def graphql_client(): + return GraphQlEndpoint("", "", "") + diff --git a/tests/unit/endpoints/test_graphql.py b/tests/unit/endpoints/test_graphql.py new file mode 100644 index 0000000..0f90983 --- /dev/null +++ b/tests/unit/endpoints/test_graphql.py @@ -0,0 +1,123 @@ +import pytest + +from tests.fixtures.unit.api_client import FakeRequest +from shikimori.exceptions import RequestError + + +@pytest.mark.asyncio +async def test_animes_success(graphql_client): + graphql_client._request = FakeRequest({}) + response = await graphql_client.animes(123) + assert response == {} + + +@pytest.mark.asyncio +async def test_animes_error(graphql_client): + graphql_client._request = FakeRequest(RequestError("Test Message", 404)) + response = await graphql_client.animes(123) + assert isinstance(response, RequestError) + +@pytest.mark.asyncio +async def test_characters_success(graphql_client): + graphql_client._request = FakeRequest({}) + response = await graphql_client.characters(123) + assert response == {} + + +@pytest.mark.asyncio +async def test_characters_error(graphql_client): + graphql_client._request = FakeRequest(RequestError("Test Message", 404)) + response = await graphql_client.characters(123) + assert isinstance(response, RequestError) + +@pytest.mark.asyncio +async def test_contests_success(graphql_client): + graphql_client._request = FakeRequest({}) + response = await graphql_client.contests(123) + assert response == {} + + +@pytest.mark.asyncio +async def test_contests_error(graphql_client): + graphql_client._request = FakeRequest(RequestError("Test Message", 404)) + response = await graphql_client.contests(123) + assert isinstance(response, RequestError) + + +@pytest.mark.asyncio +async def test_currentUser_success(graphql_client): + graphql_client._request = FakeRequest({}) + response = await graphql_client.currentUser(123) + assert response == {} + + +@pytest.mark.asyncio +async def test_currentUser_error(graphql_client): + graphql_client._request = FakeRequest(RequestError("Test Message", 404)) + response = await graphql_client.currentUser(123) + assert isinstance(response, RequestError) + +@pytest.mark.asyncio +async def test_genres_success(graphql_client): + graphql_client._request = FakeRequest({}) + response = await graphql_client.genres(123, "1231") + assert response == {} + + +@pytest.mark.asyncio +async def test_genres_error(graphql_client): + graphql_client._request = FakeRequest(RequestError("Test Message", 404)) + response = await graphql_client.genres(123, "1234") + assert isinstance(response, RequestError) + +@pytest.mark.asyncio +async def test_mangas_success(graphql_client): + graphql_client._request = FakeRequest({}) + response = await graphql_client.mangas(123) + assert response == {} + +@pytest.mark.asyncio +async def test_mangas_error(graphql_client): + graphql_client._request = FakeRequest(RequestError("Test Message", 404)) + response = await graphql_client.mangas(123) + assert isinstance(response, RequestError) + +@pytest.mark.asyncio +async def test_people_success(graphql_client): + graphql_client._request = FakeRequest({}) + response = await graphql_client.people(123) + assert response == {} + + +@pytest.mark.asyncio +async def test_people_error(graphql_client): + graphql_client._request = FakeRequest(RequestError("Test Message", 404)) + response = await graphql_client.people(123) + assert isinstance(response, RequestError) + +@pytest.mark.asyncio +async def test_userRates_success(graphql_client): + graphql_client._request = FakeRequest({}) + response = await graphql_client.userRates(123) + assert response == {} + + +@pytest.mark.asyncio +async def test_userRates_error(graphql_client): + graphql_client._request = FakeRequest(RequestError("Test Message", 404)) + response = await graphql_client.userRates(123) + assert isinstance(response, RequestError) + + +@pytest.mark.asyncio +async def test_users_success(graphql_client): + graphql_client._request = FakeRequest({}) + response = await graphql_client.users(123) + assert response == {} + + +@pytest.mark.asyncio +async def test_users_error(graphql_client): + graphql_client._request = FakeRequest(RequestError("Test Message", 404)) + response = await graphql_client.users(123) + assert isinstance(response, RequestError) \ No newline at end of file From 67c3c8a8bff7bbaeefa2a2b344e52843b5ddbf2f Mon Sep 17 00:00:00 2001 From: Snayt1k3 Date: Tue, 28 May 2024 14:14:53 +0300 Subject: [PATCH 6/7] query -> fields, add endpoint to client, fix tests --- shikimori/client.py | 3 + shikimori/endpoints/__init__.py | 3 +- shikimori/endpoints/grapql.py | 147 +++++++++++++++++++++++---- tests/unit/endpoints/test_graphql.py | 36 +++---- 4 files changed, 150 insertions(+), 39 deletions(-) diff --git a/shikimori/client.py b/shikimori/client.py index 9d267ac..b878790 100644 --- a/shikimori/client.py +++ b/shikimori/client.py @@ -142,6 +142,9 @@ def __init__( self.video = endpoints.VideosEndpoint(**self._deps) """Endpoint for making requests to the URL 'https://shikimori.one/api/animes/:anime_id/videos'""" self.auth = Auth(self._limiter, self._user_agent, self._options, self._base_url) + """Endpoint for OAUTH""" + self.graphql = endpoints.GraphQlEndpoint(**self._deps) + """Endpoint for making requests to the URL 'https://shikimori.one/api/grapql with your fields'""" def set_token(self, token: str) -> None: """ diff --git a/shikimori/endpoints/__init__.py b/shikimori/endpoints/__init__.py index a365dbe..64367ab 100644 --- a/shikimori/endpoints/__init__.py +++ b/shikimori/endpoints/__init__.py @@ -31,9 +31,10 @@ from .user_rates import UserRatesEndpoint from .users import UserEndpoint from .videos import VideosEndpoint - +from .grapql import GraphQlEndpoint __all__ = [ "AbuseRequestEndpoint", + "GraphQlEndpoint", "AchievementsEndpoint", "AnimeEndpoint", "AppearsEndpoint", diff --git a/shikimori/endpoints/grapql.py b/shikimori/endpoints/grapql.py index c40b5f4..a48f0aa 100644 --- a/shikimori/endpoints/grapql.py +++ b/shikimori/endpoints/grapql.py @@ -16,7 +16,7 @@ class GraphQlEndpoint(BaseEndpoint): async def animes( self, - query: str, + fields: str, page: int = 1, limit: int = 1, order: OrderEnum = None, @@ -37,7 +37,7 @@ async def animes( ) -> dict | RequestError: """ - :param query: graphql query + :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) :param kind: List of values separated by comma. Add ! before value to apply negative filter. values: tv, movie, ova, ona, special, tv_special, music, pv, cm, tv_13, tv_24, tv_48 :param status: List of values separated by comma. Add ! before value to apply negative filter. :param season: List of values separated by comma. Add ! before value to apply negative filter. Examples: summer_2017, 2016, 2014_2016, 199x @@ -57,6 +57,27 @@ async def animes( :param limit: Maximum 50 """ + query = """query($page: Int, $limit: Int, $order: OrderEnum, $kind: AnimeKindString, $status: AnimeStatusString, $season: SeasonString, $score: Int, $duration: DurationString, $rating: RatingString, $genre: String, $studio: String, $franchise: String, $censored: Boolean, $mylist: MylistString, $ids: String, $excludeIds: String, $search: String){ + animes( + page: $page, + limit: $limit, + order: $order, + kind: $kind, + status: $status, + season: $season, + score: $score, + duration: $duration, + rating: $rating, + genre: $genre, + studio: $studio, + franchise: $franchise, + censored: $censored, + mylist: $mylist, + ids: $ids, + excludeIds: $excludeIds, + search: $search + )""" + fields + "}" + response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -96,7 +117,7 @@ async def animes( async def characters( self, - query: str, + fields: str, page: int = 1, limit: int = 1, ids: str = None, @@ -104,12 +125,22 @@ async def characters( ) -> dict | RequestError: """ - :param query: graphql query + :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) :param page: int :param limit: Maximum 50 :param ids: list of separated ids :param search: str """ + + query = """ + query($page: Int, $limit: Int, $ids: [ID!], $search: String) { + characters( + page: $page, + limit: $limit, + ids: $ids, + search: $search + ) + """ + fields + "}" response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -135,15 +166,24 @@ async def characters( return response async def contests( - self, query: str, page: int = 1, limit: int = 1, ids: str = None + self, fields: str, page: int = 1, limit: int = 1, ids: str = None ) -> dict | RequestError: """ - - :param query: graphql query + :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) :param page: int :param limit: Maximum 10 :param ids: list of separated ids """ + + query = """ + query($page: Int, $limit: Int, $ids: [ID!]) { + contests( + page: $page, + limit: $limit, + ids: $ids + ) + """ + fields + "}" + response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -169,11 +209,14 @@ async def contests( async def currentUser( self, - query: str, + fields: str, ) -> dict | RequestError: """ - :param query: graphql query + :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) """ + query = """ + query {} + """ + fields response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -193,12 +236,18 @@ async def currentUser( return response async def genres( - self, query: str, entry: GenreEntryTypeEnum + self, fields: str, entry: GenreEntryTypeEnum ) -> dict | RequestError: """ - :param query: graphql query - :param entry: + :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) + :param entry: GenreEntryTypeEnum """ + + query = """ + query ($entryType: GenreEntryTypeEnum!) { + genres(entryType: $entryType) + """ + fields + "}" + response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -222,7 +271,7 @@ async def genres( async def mangas( self, - query: str, + fields: str, page: int = 1, limit: int = 1, order: OrderEnum = None, @@ -240,7 +289,7 @@ async def mangas( search: str = None, ) -> dict | RequestError: """ - :param query: graphql query + :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) :param publisher: List of comma separated publisher ids :param kind: List of values separated by comma. Add ! before value to apply negative filter. values: tv, movie, ova, ona, special, tv_special, music, pv, cm, tv_13, tv_24, tv_48 :param status: List of values separated by comma. Add ! before value to apply negative filter. @@ -257,6 +306,28 @@ async def mangas( :param page: must be a number :param limit: Maximum 50 """ + + query = """ + query ($page: Int, $limit: Int, $order: OrderEnum, $kind: MangaKindString, $status: MangaStatusString, $season: SeasonString, $score: Int, $genre: String, $publisher: String, $franchise: String, $censored: Boolean, $mylist: MylistString, $ids: String, $excludeIds: String, $search: String) { + mangas( + page: $page, + limit: $limit, + order: $order, + kind: $kind, + status: $status, + season: $season, + score: $score, + genre: $genre, + publisher: $publisher, + franchise: $franchise, + censored: $censored, + mylist: $mylist, + ids: $ids, + excludeIds: $excludeIds, + search: $search + ) + """ + fields + "}" + response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -294,7 +365,7 @@ async def mangas( async def people( self, - query: str, + fields: str, page: int = 1, limit: int = 1, ids: str = None, @@ -304,7 +375,7 @@ async def people( isMangaka: bool = None, ) -> dict | RequestError: """ - :param query: graphql query + :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) :param page: int :param limit: Maximum 10 :param ids: List of values separated by comma. @@ -313,6 +384,19 @@ async def people( :param isProducer: bool :param isMangaka: bool """ + + query = """ + query ($page: Int, $limit: Int, $ids: [ID!], $search: String, $isSeyu: Boolean, $isProducer: Boolean, $isMangaka: Boolean) { + people( + page: $page, + limit: $limit, + ids: $ids, + search: $search, + isSeyu: $isSeyu, + isProducer: $isProducer, + isMangaka: $isMangaka + ) + """ + fields + "}" response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -342,7 +426,7 @@ async def people( async def userRates( self, - query: str, + fields: str, userId: int = None, page: int = 1, limit: int = 1, @@ -351,7 +435,7 @@ async def userRates( order: UserRateOrderInputType = None, ) -> dict | RequestError: """ - :param query: graphql query + :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) :param userId: ID of current user is used by default :param page: int :param limit: maximum - 50 @@ -359,6 +443,19 @@ async def userRates( :param status: UserRateStatusEnum :param order: UserRateOrderInputType """ + + query = """ + query ($page: Int, $limit: Int, $userId: ID, $targetType: UserRateTargetTypeEnum!, $status: UserRateStatusEnum, $order: UserRateOrderInputType) { + userRates( + page: $page, + limit: $limit, + userId: $userId, + targetType: $targetType, + status: $status, + order: $order + ) + """ + fields + "}" + response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -389,19 +486,29 @@ async def userRates( async def users( self, - query: str, + fields: str, page: int = 1, limit: int = 1, ids: str = None, search: str = None, ) -> dict | RequestError: """ - :param query: graphql query + :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) :param page: int :param limit: maximum - 50 :param ids: List of values separated by comma. :param search: str """ + + query = """ + query ($page: Int, $limit: Int, $ids: [ID!], $search: String) { + users( + page: $page, + limit: $limit, + ids: $ids, + search: $search + ) + """ + fields + "}" response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", diff --git a/tests/unit/endpoints/test_graphql.py b/tests/unit/endpoints/test_graphql.py index 0f90983..7c023fa 100644 --- a/tests/unit/endpoints/test_graphql.py +++ b/tests/unit/endpoints/test_graphql.py @@ -7,117 +7,117 @@ @pytest.mark.asyncio async def test_animes_success(graphql_client): graphql_client._request = FakeRequest({}) - response = await graphql_client.animes(123) + response = await graphql_client.animes("123") assert response == {} @pytest.mark.asyncio async def test_animes_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) - response = await graphql_client.animes(123) + response = await graphql_client.animes("123") assert isinstance(response, RequestError) @pytest.mark.asyncio async def test_characters_success(graphql_client): graphql_client._request = FakeRequest({}) - response = await graphql_client.characters(123) + response = await graphql_client.characters("123") assert response == {} @pytest.mark.asyncio async def test_characters_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) - response = await graphql_client.characters(123) + response = await graphql_client.characters("123") assert isinstance(response, RequestError) @pytest.mark.asyncio async def test_contests_success(graphql_client): graphql_client._request = FakeRequest({}) - response = await graphql_client.contests(123) + response = await graphql_client.contests("123") assert response == {} @pytest.mark.asyncio async def test_contests_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) - response = await graphql_client.contests(123) + response = await graphql_client.contests("123") assert isinstance(response, RequestError) @pytest.mark.asyncio async def test_currentUser_success(graphql_client): graphql_client._request = FakeRequest({}) - response = await graphql_client.currentUser(123) + response = await graphql_client.currentUser("123") assert response == {} @pytest.mark.asyncio async def test_currentUser_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) - response = await graphql_client.currentUser(123) + response = await graphql_client.currentUser("123") assert isinstance(response, RequestError) @pytest.mark.asyncio async def test_genres_success(graphql_client): graphql_client._request = FakeRequest({}) - response = await graphql_client.genres(123, "1231") + response = await graphql_client.genres("123", "1231") assert response == {} @pytest.mark.asyncio async def test_genres_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) - response = await graphql_client.genres(123, "1234") + response = await graphql_client.genres("123", "1234") assert isinstance(response, RequestError) @pytest.mark.asyncio async def test_mangas_success(graphql_client): graphql_client._request = FakeRequest({}) - response = await graphql_client.mangas(123) + response = await graphql_client.mangas("123") assert response == {} @pytest.mark.asyncio async def test_mangas_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) - response = await graphql_client.mangas(123) + response = await graphql_client.mangas("123") assert isinstance(response, RequestError) @pytest.mark.asyncio async def test_people_success(graphql_client): graphql_client._request = FakeRequest({}) - response = await graphql_client.people(123) + response = await graphql_client.people("123") assert response == {} @pytest.mark.asyncio async def test_people_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) - response = await graphql_client.people(123) + response = await graphql_client.people("123") assert isinstance(response, RequestError) @pytest.mark.asyncio async def test_userRates_success(graphql_client): graphql_client._request = FakeRequest({}) - response = await graphql_client.userRates(123) + response = await graphql_client.userRates("123") assert response == {} @pytest.mark.asyncio async def test_userRates_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) - response = await graphql_client.userRates(123) + response = await graphql_client.userRates("123") assert isinstance(response, RequestError) @pytest.mark.asyncio async def test_users_success(graphql_client): graphql_client._request = FakeRequest({}) - response = await graphql_client.users(123) + response = await graphql_client.users("123") assert response == {} @pytest.mark.asyncio async def test_users_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) - response = await graphql_client.users(123) + response = await graphql_client.users("123") assert isinstance(response, RequestError) \ No newline at end of file From c9d512d382b0fb31033e3a440661ebb66675c12f Mon Sep 17 00:00:00 2001 From: Snayt1k3 Date: Tue, 28 May 2024 14:16:03 +0300 Subject: [PATCH 7/7] run black --- docs/conf.py | 13 +++-- setup.py | 1 + shikimori/endpoints/__init__.py | 1 + shikimori/endpoints/grapql.py | 71 ++++++++++++++++++------ shikimori/types/animes.py | 1 - shikimori/types/character.py | 5 +- shikimori/types/comment.py | 1 + shikimori/types/externalLink.py | 2 +- shikimori/types/topics.py | 1 + tests/fixtures/unit/endpoints/graphql.py | 1 - tests/unit/endpoints/test_graphql.py | 9 ++- 11 files changed, 77 insertions(+), 29 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 515e38d..aa750e9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,16 +5,17 @@ import tomli import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information with open("../pyproject.toml", "rb") as f: pyproject = tomli.load(f) _version: str = pyproject["tool"]["poetry"]["version"] -project = 'shiki.py' -copyright = '2024, Snayt1k3' -author = 'Snayt1k3' +project = "shiki.py" +copyright = "2024, Snayt1k3" +author = "Snayt1k3" release = _version version = ".".join(_version.split(".", 2)[:2]) @@ -37,13 +38,13 @@ "sphinx_search.extension", ] -templates_path = ['_templates'] +templates_path = ["_templates"] exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "furo" -html_static_path = ['_static'] +html_static_path = ["_static"] autodoc_default_options = {"member-order": "bysource"} intersphinx_mapping = { "py": ("https://docs.python.org/3", None), diff --git a/setup.py b/setup.py index 67bc20a..a3d7563 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ def get_long_description() -> str: return (Path(__file__).parent / "README.md").read_text() + extra_requirements = { "readthedocs": [ "furo", diff --git a/shikimori/endpoints/__init__.py b/shikimori/endpoints/__init__.py index 64367ab..11aa86b 100644 --- a/shikimori/endpoints/__init__.py +++ b/shikimori/endpoints/__init__.py @@ -32,6 +32,7 @@ from .users import UserEndpoint from .videos import VideosEndpoint from .grapql import GraphQlEndpoint + __all__ = [ "AbuseRequestEndpoint", "GraphQlEndpoint", diff --git a/shikimori/endpoints/grapql.py b/shikimori/endpoints/grapql.py index a48f0aa..1729e25 100644 --- a/shikimori/endpoints/grapql.py +++ b/shikimori/endpoints/grapql.py @@ -57,7 +57,8 @@ async def animes( :param limit: Maximum 50 """ - query = """query($page: Int, $limit: Int, $order: OrderEnum, $kind: AnimeKindString, $status: AnimeStatusString, $season: SeasonString, $score: Int, $duration: DurationString, $rating: RatingString, $genre: String, $studio: String, $franchise: String, $censored: Boolean, $mylist: MylistString, $ids: String, $excludeIds: String, $search: String){ + query = ( + """query($page: Int, $limit: Int, $order: OrderEnum, $kind: AnimeKindString, $status: AnimeStatusString, $season: SeasonString, $score: Int, $duration: DurationString, $rating: RatingString, $genre: String, $studio: String, $franchise: String, $censored: Boolean, $mylist: MylistString, $ids: String, $excludeIds: String, $search: String){ animes( page: $page, limit: $limit, @@ -76,7 +77,10 @@ async def animes( ids: $ids, excludeIds: $excludeIds, search: $search - )""" + fields + "}" + )""" + + fields + + "}" + ) response = await self._request.make_request( "POST", @@ -132,7 +136,8 @@ async def characters( :param search: str """ - query = """ + query = ( + """ query($page: Int, $limit: Int, $ids: [ID!], $search: String) { characters( page: $page, @@ -140,7 +145,10 @@ async def characters( ids: $ids, search: $search ) - """ + fields + "}" + """ + + fields + + "}" + ) response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -175,14 +183,18 @@ async def contests( :param ids: list of separated ids """ - query = """ + query = ( + """ query($page: Int, $limit: Int, $ids: [ID!]) { contests( page: $page, limit: $limit, ids: $ids ) - """ + fields + "}" + """ + + fields + + "}" + ) response = await self._request.make_request( "POST", @@ -214,9 +226,12 @@ async def currentUser( """ :param fields: e.g. '{ anime { id name } }' (check more in doc https://shikimori.one/api/doc/graphql ) """ - query = """ + query = ( + """ query {} - """ + fields + """ + + fields + ) response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -243,10 +258,14 @@ async def genres( :param entry: GenreEntryTypeEnum """ - query = """ + query = ( + """ query ($entryType: GenreEntryTypeEnum!) { genres(entryType: $entryType) - """ + fields + "}" + """ + + fields + + "}" + ) response = await self._request.make_request( "POST", @@ -307,7 +326,8 @@ async def mangas( :param limit: Maximum 50 """ - query = """ + query = ( + """ query ($page: Int, $limit: Int, $order: OrderEnum, $kind: MangaKindString, $status: MangaStatusString, $season: SeasonString, $score: Int, $genre: String, $publisher: String, $franchise: String, $censored: Boolean, $mylist: MylistString, $ids: String, $excludeIds: String, $search: String) { mangas( page: $page, @@ -326,7 +346,10 @@ async def mangas( excludeIds: $excludeIds, search: $search ) - """ + fields + "}" + """ + + fields + + "}" + ) response = await self._request.make_request( "POST", @@ -385,7 +408,8 @@ async def people( :param isMangaka: bool """ - query = """ + query = ( + """ query ($page: Int, $limit: Int, $ids: [ID!], $search: String, $isSeyu: Boolean, $isProducer: Boolean, $isMangaka: Boolean) { people( page: $page, @@ -396,7 +420,10 @@ async def people( isProducer: $isProducer, isMangaka: $isMangaka ) - """ + fields + "}" + """ + + fields + + "}" + ) response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", @@ -444,7 +471,8 @@ async def userRates( :param order: UserRateOrderInputType """ - query = """ + query = ( + """ query ($page: Int, $limit: Int, $userId: ID, $targetType: UserRateTargetTypeEnum!, $status: UserRateStatusEnum, $order: UserRateOrderInputType) { userRates( page: $page, @@ -454,7 +482,10 @@ async def userRates( status: $status, order: $order ) - """ + fields + "}" + """ + + fields + + "}" + ) response = await self._request.make_request( "POST", @@ -500,7 +531,8 @@ async def users( :param search: str """ - query = """ + query = ( + """ query ($page: Int, $limit: Int, $ids: [ID!], $search: String) { users( page: $page, @@ -508,7 +540,10 @@ async def users( ids: $ids, search: $search ) - """ + fields + "}" + """ + + fields + + "}" + ) response = await self._request.make_request( "POST", url=f"{self._base_url}/api/graphql", diff --git a/shikimori/types/animes.py b/shikimori/types/animes.py index 4b96a2e..c22e5d5 100644 --- a/shikimori/types/animes.py +++ b/shikimori/types/animes.py @@ -142,4 +142,3 @@ def from_dict(cls, data: dict): anime=Anime.from_dict(anime) if (anime := data.get("anime")) else None, manga=Manga.from_dict(manga) if (manga := data.get("manga")) else None, ) - diff --git a/shikimori/types/character.py b/shikimori/types/character.py index e52c06d..2afa251 100644 --- a/shikimori/types/character.py +++ b/shikimori/types/character.py @@ -132,7 +132,10 @@ def from_dict(cls, data: dict): thread_id=data.get("thread_id"), topic_id=data.get("topic_id"), updated_at=data.get("updated_at"), - seyu=[CharacterBrief.from_dict(seyu_data) for seyu_data in data.get("seyu", [])], + seyu=[ + CharacterBrief.from_dict(seyu_data) + for seyu_data in data.get("seyu", []) + ], animes=[ AnimeRole.from_dict(anime_data) for anime_data in data.get("animes", []) ], diff --git a/shikimori/types/comment.py b/shikimori/types/comment.py index a4bc264..8262d36 100644 --- a/shikimori/types/comment.py +++ b/shikimori/types/comment.py @@ -36,6 +36,7 @@ def from_dict(cls, data: dict): user=User.from_dict(data.get("user")), ) + @dataclass class CommentBrief: id: int diff --git a/shikimori/types/externalLink.py b/shikimori/types/externalLink.py index 9f20873..d6fed05 100644 --- a/shikimori/types/externalLink.py +++ b/shikimori/types/externalLink.py @@ -28,4 +28,4 @@ def from_dict(cls, data: dict): created_at=data.get("created_at"), updated_at=data.get("updated_at"), imported_at=data.get("imported_at"), - ) \ No newline at end of file + ) diff --git a/shikimori/types/topics.py b/shikimori/types/topics.py index f27bfc8..c7686b2 100644 --- a/shikimori/types/topics.py +++ b/shikimori/types/topics.py @@ -201,6 +201,7 @@ def from_dict(cls, data: dict): created_at=data.get("created_at"), ) + @dataclass class TopicReview: id: int diff --git a/tests/fixtures/unit/endpoints/graphql.py b/tests/fixtures/unit/endpoints/graphql.py index e3c89ae..146113e 100644 --- a/tests/fixtures/unit/endpoints/graphql.py +++ b/tests/fixtures/unit/endpoints/graphql.py @@ -6,4 +6,3 @@ @pytest.fixture def graphql_client(): return GraphQlEndpoint("", "", "") - diff --git a/tests/unit/endpoints/test_graphql.py b/tests/unit/endpoints/test_graphql.py index 7c023fa..37859e1 100644 --- a/tests/unit/endpoints/test_graphql.py +++ b/tests/unit/endpoints/test_graphql.py @@ -17,6 +17,7 @@ async def test_animes_error(graphql_client): response = await graphql_client.animes("123") assert isinstance(response, RequestError) + @pytest.mark.asyncio async def test_characters_success(graphql_client): graphql_client._request = FakeRequest({}) @@ -30,6 +31,7 @@ async def test_characters_error(graphql_client): response = await graphql_client.characters("123") assert isinstance(response, RequestError) + @pytest.mark.asyncio async def test_contests_success(graphql_client): graphql_client._request = FakeRequest({}) @@ -57,6 +59,7 @@ async def test_currentUser_error(graphql_client): response = await graphql_client.currentUser("123") assert isinstance(response, RequestError) + @pytest.mark.asyncio async def test_genres_success(graphql_client): graphql_client._request = FakeRequest({}) @@ -70,18 +73,21 @@ async def test_genres_error(graphql_client): response = await graphql_client.genres("123", "1234") assert isinstance(response, RequestError) + @pytest.mark.asyncio async def test_mangas_success(graphql_client): graphql_client._request = FakeRequest({}) response = await graphql_client.mangas("123") assert response == {} + @pytest.mark.asyncio async def test_mangas_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) response = await graphql_client.mangas("123") assert isinstance(response, RequestError) + @pytest.mark.asyncio async def test_people_success(graphql_client): graphql_client._request = FakeRequest({}) @@ -95,6 +101,7 @@ async def test_people_error(graphql_client): response = await graphql_client.people("123") assert isinstance(response, RequestError) + @pytest.mark.asyncio async def test_userRates_success(graphql_client): graphql_client._request = FakeRequest({}) @@ -120,4 +127,4 @@ async def test_users_success(graphql_client): async def test_users_error(graphql_client): graphql_client._request = FakeRequest(RequestError("Test Message", 404)) response = await graphql_client.users("123") - assert isinstance(response, RequestError) \ No newline at end of file + assert isinstance(response, RequestError)