From 76a7f7259f3558f3c22536adab040e955b841218 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Sun, 15 May 2022 22:13:00 +0300 Subject: [PATCH 01/22] add method for /api/genres --- shikithon/api.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/shikithon/api.py b/shikithon/api.py index b79c9be6..c7a8f35a 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -46,6 +46,7 @@ from shikithon.models.favourites import Favourites from shikithon.models.forum import Forum from shikithon.models.franchise_tree import FranchiseTree +from shikithon.models.genre import Genre from shikithon.models.history import History from shikithon.models.link import Link from shikithon.models.manga import Manga @@ -1638,6 +1639,17 @@ def destroy_friend(self, friend_id: int): f'Detailed information about destroying a friend {response=}') return 'notice' in response + def genres(self) -> List[Genre]: + """ + Returns list of genres. + + :return: List of genres + :rtype: List[Genre] + """ + logger.debug('Executing API method') + response: List[Dict[str, Any]] = self._request(self._endpoints.genres) + return [Genre(**genre) for genre in response] + def users(self, page: Optional[int] = None, limit: Optional[int] = None) -> Optional[List[User]]: From 0ba459331afbabb65e3337db3eced7f524dbd504 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Sun, 15 May 2022 23:35:21 +0300 Subject: [PATCH 02/22] add support for list on enums in query/data dict generation --- shikithon/utils.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/shikithon/utils.py b/shikithon/utils.py index cb550eff..b6ffd989 100644 --- a/shikithon/utils.py +++ b/shikithon/utils.py @@ -100,9 +100,12 @@ def generate_query_dict( elif isinstance(data, Enum): query_dict[key] = data.value elif isinstance(data, list): - data = [ - str(value) if value.isdigit() else value for value in data - ] + formatted_data: List[str] = [] + for item in data: + if item.isdigit(): + formatted_data.append(str(item)) + if isinstance(item, Enum): + formatted_data.append(item.value) query_dict[key] = ','.join(data) else: query_dict[key] = data @@ -148,9 +151,12 @@ def generate_data_dict( elif isinstance(data, Enum): new_data_dict[data_dict_name][key] = data.value elif isinstance(data, list): - data = [ - str(value) if value.isdigit() else value for value in data - ] + formatted_data: List[str] = [] + for item in data: + if item.isdigit(): + formatted_data.append(str(item)) + if isinstance(item, Enum): + formatted_data.append(item.value) new_data_dict[data_dict_name][key] = ','.join(data) else: new_data_dict[data_dict_name][key] = data From 93f810db9d19d03f10bb0e5e9c4a4e75f8b33aeb Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Sun, 15 May 2022 23:41:16 +0300 Subject: [PATCH 03/22] fix if's and joining in utils generators --- shikithon/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shikithon/utils.py b/shikithon/utils.py index b6ffd989..7dce3815 100644 --- a/shikithon/utils.py +++ b/shikithon/utils.py @@ -102,11 +102,11 @@ def generate_query_dict( elif isinstance(data, list): formatted_data: List[str] = [] for item in data: - if item.isdigit(): - formatted_data.append(str(item)) if isinstance(item, Enum): formatted_data.append(item.value) - query_dict[key] = ','.join(data) + elif item.isdigit(): + formatted_data.append(str(item)) + query_dict[key] = ','.join(formatted_data) else: query_dict[key] = data logger.debug(f'Generated query dictionary: {query_dict=}') @@ -153,11 +153,11 @@ def generate_data_dict( elif isinstance(data, list): formatted_data: List[str] = [] for item in data: - if item.isdigit(): - formatted_data.append(str(item)) if isinstance(item, Enum): formatted_data.append(item.value) - new_data_dict[data_dict_name][key] = ','.join(data) + elif item.isdigit(): + formatted_data.append(str(item)) + new_data_dict[data_dict_name][key] = ','.join(formatted_data) else: new_data_dict[data_dict_name][key] = data logger.debug(f'Generated data dictionary: {new_data_dict=}') From 67991d9a80a2ea583f07a6c52c7017c89fceace6 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Sun, 15 May 2022 23:55:26 +0300 Subject: [PATCH 04/22] update anime.py rename enums added opposite variants added some missing fields --- shikithon/enums/anime.py | 46 +++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/shikithon/enums/anime.py b/shikithon/enums/anime.py index 9efc82f6..bd04651b 100644 --- a/shikithon/enums/anime.py +++ b/shikithon/enums/anime.py @@ -2,9 +2,10 @@ from enum import Enum -class Order(Enum): +class AnimeOrder(Enum): """Contains constants related for list ordering query.""" ID = 'id' + ID_DESC = 'id_desc' RANKED = 'ranked' KIND = 'kind' POPULARITY = 'popularity' @@ -12,49 +13,72 @@ class Order(Enum): AIRED_ON = 'aired_on' EPISODES = 'episodes' STATUS = 'status' + CREATED_AT = 'created_at' + CREATED_AT_DESC = 'created_at_desc' RANDOM = 'random' -class Kind(Enum): +class AnimeKind(Enum): """Contains constants related for getting certain kind of anime.""" TV = 'tv' + NOT_TV = '!tv' TV_13 = 'tv_13' + NOT_TV_13 = '!tv_13' TV_24 = 'tv_24' + NOT_TV_24 = '!tv_24' TV_48 = 'tv_48' + NOT_TV_48 = '!tv_48' MOVIE = 'movie' + NOT_MOVIE = '!movie' OVA = 'ova' + NOT_OVA = '!ova' ONA = 'ona' + NOT_ONA = '!ona' SPECIAL = 'special' + NOT_SPECIAL = '!special' MUSIC = 'music' + NOT_MUSIC = '!music' -class Status(Enum): +class AnimeStatus(Enum): """Contains constants related for getting certain status of anime.""" ANONS = 'anons' + NOT_ANONS = '!anons' ONGOING = 'ongoing' + NOT_ONGOING = '!ongoing' RELEASED = 'released' - EPISODE = 'episode' + NOT_RELEASED = '!released' -class Duration(Enum): +class AnimeDuration(Enum): """Contains constants related for getting certain duration of anime.""" SHORT = 'S' + NOT_SHORT = '!S' MEDIUM = 'D' + NOT_MEDIUM = '!D' LONG = 'F' + NOT_LONG = '!F' -class Rating(Enum): +class AnimeRating(Enum): """Contains constants related for getting certain rating of anime.""" NO_RATING = 'none' + NOT_NO_RATING = '!none' ALL_AGES = 'g' + NOT_ALL_AGES = '!g' CHILDREN = 'pg' + NOT_CHILDREN = '!pg' TEENS = 'pg_13' + NOT_TEENS = '!pg_13' VIOLENCE = 'r' + NOT_VIOLENCE = '!r' MILD_NUDITY = 'r_plus' + NOT_MILD_NUDITY = '!r_plus' HENTAI = 'rx' + NOT_HENTAI = '!rx' -class Censorship(Enum): +class AnimeCensorship(Enum): """Contains constants related for getting certain censorship status of anime. """ @@ -62,13 +86,19 @@ class Censorship(Enum): UNCENSORED = 'false' -class MyList(Enum): +class AnimeList(Enum): """Contains constants related for getting certain user list status of anime. """ PLANNED = 'planned' + NOT_PLANNED = '!planned' WATCHING = 'watching' + NOT_WATCHING = '!watching' REWATCHING = 'rewatching' + NOT_REWATCHING = '!rewatching' COMPLETED = 'completed' + NOT_COMPLETED = '!completed' ON_HOLD = 'on_hold' + NOT_ON_HOLD = '!on_hold' DROPPED = 'dropped' + NOT_DROPPED = '!dropped' From d793edb6e1adb5324d79b28b54cb15e803d84bde Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Mon, 16 May 2022 00:55:03 +0300 Subject: [PATCH 05/22] update api.py and utils.py api.py: update /api/animes methods: - yay, add support for grouping, subtracting and combining parameters (See https://shikimori.one/api/doc/1.0/animes/index) - add support for unrestricted mode (for proper use of my_list) - update many parameters to union of single parameter or list - rename my_list to mylist in query add methods for /api/manga update executing api method message update query numbers validating update enums names fix docstrings of some methods manga.py: add enums for manga utils.py: add query_numbers_validator for checking numbers all at once validate_query_number now returns upper limit, if number is higher than that --- shikithon/api.py | 483 +++++++++++++++++++++++++++++---------- shikithon/enums/manga.py | 76 ++++++ shikithon/utils.py | 55 ++++- 3 files changed, 486 insertions(+), 128 deletions(-) create mode 100644 shikithon/enums/manga.py diff --git a/shikithon/api.py b/shikithon/api.py index c7a8f35a..f46e2725 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -15,13 +15,16 @@ from shikithon.config_cache import ConfigCache from shikithon.decorators import protected_method from shikithon.endpoints import Endpoints -from shikithon.enums.anime import (Censorship, Duration, Kind, MyList, Order, - Rating, Status) +from shikithon.enums.anime import (AnimeCensorship, AnimeDuration, AnimeKind, + AnimeList, AnimeOrder, AnimeRating, + AnimeStatus) from shikithon.enums.club import (CommentPolicy, ImageUploadPolicy, JoinPolicy, PagePolicy, TopicPolicy) from shikithon.enums.comment import CommentableType from shikithon.enums.favorite import LinkedType from shikithon.enums.history import TargetType +from shikithon.enums.manga import (MangaCensorship, MangaKind, MangaList, + MangaOrder, MangaStatus) from shikithon.enums.message import MessageType from shikithon.enums.person import PersonKind from shikithon.enums.request import RequestType @@ -584,20 +587,21 @@ def achievements(self, user_id: int) -> List[Achievement]: def animes(self, page: Optional[int] = None, limit: Optional[int] = None, - order: Optional[Order] = None, - kind: Optional[Kind] = None, - status: Optional[Status] = None, - season: Optional[str] = None, + order: Optional[AnimeOrder] = None, + kind: Optional[Union[AnimeKind, List[AnimeKind]]] = None, + status: Optional[Union[AnimeStatus, List[AnimeStatus]]] = None, + season: Optional[Union[str, List[str]]] = None, score: Optional[int] = None, - duration: Optional[Duration] = None, - rating: Optional[Rating] = None, - genre: Optional[List[int]] = None, - studio: Optional[List[int]] = None, - franchise: Optional[List[int]] = None, - censored: Optional[Censorship] = None, - my_list: Optional[MyList] = None, - ids: Optional[List[int]] = None, - exclude_ids: Optional[List[int]] = None, + duration: Optional[Union[AnimeDuration, + List[AnimeDuration]]] = None, + rating: Optional[Union[AnimeRating, List[AnimeRating]]] = None, + genre: Optional[Union[int, List[int]]] = None, + studio: Optional[Union[int, List[int]]] = None, + franchise: Optional[Union[int, List[int]]] = None, + censored: Optional[AnimeCensorship] = None, + my_list: Optional[Union[AnimeList, List[AnimeList]]] = None, + ids: Optional[Union[int, List[int]]] = None, + exclude_ids: Optional[Union[int, List[int]]] = None, search: Optional[str] = None) -> Optional[List[Anime]]: """ Returns animes list. @@ -609,77 +613,82 @@ def animes(self, :type limit: Optional[int] :param order: Type of order in list - :type order: Optional[Order] + :type order: Optional[AnimeOrder] - :param kind: Type of anime topic - :type kind: Optional[Kind] + :param kind: Type(s) of anime topics + :type kind: Optional[Union[AnimeKind, List[AnimeKind]]] - :param status: Type of anime status - :type status: Optional[Status + :param status: Type(s) of anime status + :type status: Optional[Union[AnimeStatus, List[AnimeStatus]]] - :param season: Name of anime season - :type season: Optional[str + :param season: Name(s) of anime seasons + :type season: Optional[Union[str, List[str]]] :param score: Minimal anime score :type score: Optional[int] - :param duration: Duration size of anime - :type duration: Optional[Duration] + :param duration: Duration size(s) of anime + :type duration: Optional[Union[AnimeDuration, List[AnimeDuration]]] - :param rating: Type of anime rating - :type rating: Optional[Rating] + :param rating: Type of anime rating(s) + :type rating: Optional[Union[AnimeRating, List[AnimeRating]]] - :param genre: Genres ID - :type genre: Optional[List[int]] + :param genre: Genre(s) ID + :type genre: Optional[Union[int, List[int]]] - :param studio: Studios ID - :type studio: Optional[List[int]] + :param studio: Studio(s) ID + :type studio: Optional[Union[int, List[int]]] - :param franchise: Franchises ID - :type franchise: Optional[List[int]] + :param franchise: Franchise(s) ID + :type franchise: Optional[Union[int, List[int]]] :param censored: Type of anime censorship - :type censored: Optional[Censorship] + :type censored: Optional[AnimeCensorship] - :param my_list: Status of anime in current user list - :type my_list: Optional[MyList] + :param my_list: Status(-es) of anime in current user list + Note: If app is in restricted mode, + this parameter won't affect on response. + :type my_list: Optional[Union[AnimeList, List[AnimeList]]] - :param ids: Animes ID to include - :type ids: Optional[List[int]] + :param ids: Anime(s) ID to include + :type ids: Optional[Union[int, List[int]]] - :param exclude_ids: Animes ID to exclude - :type exclude_ids: Optional[List[int]] + :param exclude_ids: Anime(s) ID to exclude + :type exclude_ids: Optional[Union[int, List[int]]] :param search: Search phrase to filter animes by name - :type search: Optional[str + :type search: Optional[str] :return: Animes list or None, if page is empty :rtype: Optional[List[Anime]] """ - logger.debug('Executing API method') - logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 50) - logger.debug('Checking score parameter') - score = Utils.validate_query_number(score, 9) + logger.debug('Executing "/api/animes" method') + validated_numbers = Utils.query_numbers_validator(page=[page, 100000], + limit=[limit, 50], + score=[score, 9]) + + headers: Optional[Dict[str, str]] = None + + if not self.restricted_mode: + headers = self._authorization_header response: List[Dict[str, Any]] = self._request( self._endpoints.animes, - query=Utils.generate_query_dict(page=page, - limit=limit, + headers=headers, + query=Utils.generate_query_dict(page=validated_numbers['name'], + limit=validated_numbers['limit'], order=order, kind=kind, status=status, season=season, - score=score, + score=validated_numbers['score'], duration=duration, rating=rating, genre=genre, studio=studio, franchise=franchise, censored=censored, - my_list=my_list, + mylist=my_list, ids=ids, exclude_ids=exclude_ids, search=search)) @@ -697,7 +706,7 @@ def anime(self, anime_id: int) -> Anime: :return: Anime info :rtype: Anime """ - logger.debug('Executing API method') + logger.debug('Executing "/api/animes/:id" method') response: Dict[str, Any] = self._request(self._endpoints.anime(anime_id)) return Anime(**response) @@ -712,7 +721,7 @@ def anime_creators(self, anime_id: int) -> List[Creator]: :return: List of anime creators :rtype: List[Creator] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/animes/:id/roles" method') response: List[Dict[str, Any]] = self._request( self._endpoints.anime_roles(anime_id)) return [Creator(**creator) for creator in response] @@ -727,7 +736,7 @@ def similar_animes(self, anime_id: int) -> List[Anime]: :return: List of similar animes :rtype: List[Anime] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/animes/:id/similar" method') response: List[Dict[str, Any]] = self._request( self._endpoints.similar_animes(anime_id)) return [Anime(**anime) for anime in response] @@ -742,7 +751,7 @@ def anime_related_content(self, anime_id: int) -> List[Relation]: :return: List of relations :rtype: List[Relation] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/animes/:id/related" method') response: List[Dict[str, Any]] = self._request( self._endpoints.anime_related_content(anime_id)) return [Relation(**relation) for relation in response] @@ -757,7 +766,7 @@ def anime_screenshots(self, anime_id: int) -> List[Screenshot]: :return: List of screenshot links :rtype: List[Screenshot] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/animes/:id/screenshots" method') response: List[Dict[str, Any]] = self._request( self._endpoints.anime_screenshots(anime_id)) return [Screenshot(**screenshot) for screenshot in response] @@ -772,7 +781,7 @@ def anime_franchise_tree(self, anime_id: int) -> FranchiseTree: :return: Franchise tree of certain anime :rtype: FranchiseTree """ - logger.debug('Executing API method') + logger.debug('Executing "/api/animes/:id/franchise" method') response: Dict[str, Any] = self._request( self._endpoints.anime_franchise_tree(anime_id)) return FranchiseTree(**response) @@ -787,7 +796,7 @@ def anime_external_links(self, anime_id: int) -> List[Link]: :return: List of external links :rtype: List[Link] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/animes/:id/external_links" method') response: List[Dict[str, Any]] = self._request( self._endpoints.anime_external_links(anime_id)) return [Link(**link) for link in response] @@ -796,7 +805,7 @@ def anime_topics(self, anime_id: int, page: Optional[int] = None, limit: Optional[int] = None, - kind: Optional[Status] = None, + kind: Optional[AnimeStatus] = None, episode: Optional[int] = None) -> Optional[List[Topic]]: """ Returns list of topics of certain anime. @@ -813,24 +822,22 @@ def anime_topics(self, :type limit: Optional[int] :param kind: Kind of anime (Uses status enum values) - :type kind: Optional[Status] + :type kind: Optional[AnimeStatus] :param episode: Number of anime episode :type episode: Optional[int] :return: List of topics or None, if page is empty - :rtype: Optional[List[Topic + :rtype: Optional[List[Topic]] """ - logger.debug('Executing API method') - logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 30) + logger.debug('Executing "/api/animes/:id/topics" method') + validated_numbers = Utils.query_numbers_validator(page=[page, 100000], + limit=[limit, 30]) response: List[Dict[str, Any]] = self._request( self._endpoints.anime_topics(anime_id), - query=Utils.generate_query_dict(page=page, - limit=limit, + query=Utils.generate_query_dict(page=validated_numbers['page'], + limit=validated_numbers['limit'], kind=kind, episode=episode)) if response: @@ -874,24 +881,27 @@ def bans(self, """ logger.debug('Executing API method') logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 30) + validated_numbers = Utils.query_numbers_validator( + page=[page, 100000], + limit=[limit, 30], + ) response: List[Dict[str, Any]] = self._request( self._endpoints.bans_list, - query=Utils.generate_query_dict(page=page, limit=limit)) + query=Utils.generate_query_dict(page=validated_numbers['page'], + limit=validated_numbers['limit'])) if response: return [Ban(**ban) for ban in response] return None - def calendar(self, - censored: Optional[Censorship] = None) -> List[CalendarEvent]: + def calendar( + self, + censored: Optional[AnimeCensorship] = None) -> List[CalendarEvent]: """ Returns current calendar events. :param censored: Status of censorship for events - :type censored: Optional[Censorship] + :type censored: Optional[AnimeCensorship] :return: List of calendar events :rtype: List[CalendarEvent] @@ -953,15 +963,15 @@ def clubs(self, :rtype: Optional[List[Club]] """ logger.debug('Executing API method') - logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 30) + validated_numbers = Utils.query_numbers_validator( + page=[page, 100000], + limit=[limit, 30], + ) response: List[Dict[str, Any]] = self._request( self._endpoints.clubs, - query=Utils.generate_query_dict(page=page, - limit=limit, + query=Utils.generate_query_dict(page=validated_numbers['page'], + limit=validated_numbers['limit'], search=search)) if response: return [Club(**club) for club in response] @@ -1246,15 +1256,15 @@ def comments(self, :rtype: Optional[List[Comment]] """ logger.debug('Executing API method') - logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 30) + validated_numbers = Utils.query_numbers_validator( + page=[page, 100000], + limit=[limit, 30], + ) response: List[Dict[str, Any]] = self._request( self._endpoints.comments, - query=Utils.generate_query_dict(page=page, - limit=limit, + query=Utils.generate_query_dict(page=validated_numbers['page'], + limit=validated_numbers['limit'], commentable_id=commentable_id, commentable_type=commentable_type, desc=desc)) @@ -1650,6 +1660,232 @@ def genres(self) -> List[Genre]: response: List[Dict[str, Any]] = self._request(self._endpoints.genres) return [Genre(**genre) for genre in response] + def mangas(self, + page: Optional[int] = None, + limit: Optional[int] = None, + order: Optional[MangaOrder] = None, + kind: Optional[Union[MangaKind, List[MangaKind]]] = None, + status: Optional[Union[MangaStatus, List[MangaStatus]]] = None, + season: Optional[Union[str, List[str]]] = None, + score: Optional[int] = None, + genre: Optional[Union[int, List[int]]] = None, + publisher: Optional[Union[int, List[int]]] = None, + franchise: Optional[Union[int, List[int]]] = None, + censored: Optional[MangaCensorship] = None, + my_list: Optional[Union[MangaList, List[MangaList]]] = None, + ids: Optional[Union[int, List[int]]] = None, + exclude_ids: Optional[Union[int, List[int]]] = None, + search: Optional[str] = None) -> Optional[List[Manga]]: + """ + Returns mangas list. + + :param page: Number of page + :type page: Optional[int] + + :param limit: Number of results limit + :type limit: Optional[int] + + :param order: Type of order in list + :type order: Optional[MangaOrder] + + :param kind: Type(s) of manga topic + :type kind: Optional[Union[MangaKind, List[MangaKind]] + + :param status: Type(s) of manga status + :type status: Optional[Union[MangaStatus, List[MangaStatus]]] + + :param season: Name(s) of manga seasons + :type season: Optional[Union[str, List[str]]] + + :param score: Minimal manga score + :type score: Optional[int] + + :param publisher: Publisher(s) ID + :type publisher: Optional[Union[int, List[int]] + + :param genre: Genre(s) ID + :type genre: Optional[Union[int, List[int]] + + :param franchise: Franchise(s) ID + :type franchise: Optional[Union[int, List[int]] + + :param censored: Type of manga censorship + :type censored: Optional[MangaCensorship] + + :param my_list: Status(-es) of manga in current user list + Note: If app in restricted mode, + this won't affect on response. + :type my_list: Optional[Union[MangaList, List[MangaList]]] + + :param ids: Manga(s) ID to include + :type ids: Optional[Union[int, List[int]] + + :param exclude_ids: Manga(s) ID to exclude + :type exclude_ids: Optional[Union[int, List[int]] + + :param search: Search phrase to filter mangas by name + :type search: Optional[str] + + :return: List of Mangas or None, if page is empty + :rtype: Optional[List[Manga]] + """ + logger.debug('Executing "/api/mangas" method') + validated_numbers = Utils.query_numbers_validator(page=[page, 100000], + limit=[limit, 50], + score=[score, 9]) + + headers: Optional[Dict[str, str]] = None + + if not self.restricted_mode: + headers = self._authorization_header + + response: List[Dict[str, Any]] = self._request( + self._endpoints.mangas, + headers=headers, + query=Utils.generate_query_dict(page=validated_numbers['page'], + limit=validated_numbers['limit'], + order=order, + kind=kind, + status=status, + season=season, + score=validated_numbers['score'], + genre=genre, + publisher=publisher, + franchise=franchise, + censored=censored, + mylist=my_list, + ids=ids, + exclude_ids=exclude_ids, + search=search)) + if response: + return [Manga(**manga) for manga in response] + return None + + def manga(self, manga_id: int) -> Manga: + """ + Returns info about certain manga. + + :param manga_id: Manga ID to get info + :type manga_id: int + + :return: Manga info + :rtype: Manga + """ + logger.debug('Executing "/api/mangas/:id" method') + response: Dict[str, + Any] = self._request(self._endpoints.manga(manga_id)) + return Manga(**response) + + def manga_creators(self, manga_id: int) -> List[Creator]: + """ + Returns creators info of certain manga. + + :param manga_id: Manga ID to get creators + :type manga_id: int + + :return: List of manga creators + :rtype: List[Creator] + """ + logger.debug('Executing "/api/mangas/:id/roles" method') + response: List[Dict[str, Any]] = self._request( + self._endpoints.manga_roles(manga_id)) + return [Creator(**creator) for creator in response] + + def similar_mangas(self, manga_id: int) -> List[Manga]: + """ + Returns list of similar mangas for certain manga. + + :param manga_id: Manga ID to get similar mangas + :type manga_id: int + + :return: List of similar mangas + :rtype: List[Manga] + """ + logger.debug('Executing "/api/mangas/:id/similar" method') + response: List[Dict[str, Any]] = self._request( + self._endpoints.similar_mangas(manga_id)) + return [Manga(**manga) for manga in response] + + def manga_related_content(self, manga_id: int) -> List[Relation]: + """ + Returns list of related content of certain manga. + + :param manga_id: Manga ID to get related content + :type manga_id: int + + :return: List of relations + :rtype: List[Relation] + """ + logger.debug('Executing "/api/mangas/:id/related" method') + response: List[Dict[str, Any]] = self._request( + self._endpoints.manga_related_content(manga_id)) + return [Relation(**relation) for relation in response] + + def manga_franchise_tree(self, manga_id: int) -> FranchiseTree: + """ + Returns franchise tree of certain manga. + + :param manga_id: Manga ID to get franchise tree + :type manga_id: int + + :return: Franchise tree of certain manga + :rtype: FranchiseTree + """ + logger.debug('Executing "/api/mangas/:id/franchise" method') + response: Dict[str, Any] = self._request( + self._endpoints.manga_franchise_tree(manga_id)) + return FranchiseTree(**response) + + def manga_external_links(self, manga_id: int) -> List[Link]: + """ + Returns list of external links of certain manga. + + :param manga_id: Manga ID to get external links + :type manga_id: int + + :return: List of external links + :rtype: List[Link] + """ + logger.debug('Executing "/api/mangas/:id/external_links" method') + response: List[Dict[str, Any]] = self._request( + self._endpoints.manga_external_links(manga_id)) + return [Link(**link) for link in response] + + def manga_topics(self, + manga_id: int, + page: Optional[int] = None, + limit: Optional[int] = None) -> Optional[List[Topic]]: + """ + Returns list of topics of certain manga. + + If some data are not provided, using default values. + + :param manga_id: Manga ID to get topics + :type manga_id: int + + :param page: Number of page + :type page: Optional[int] + + :param limit: Number of results limit + :type limit: Optional[int] + + :return: List of topics or None, if page is empty + :rtype: Optional[List[Topic]] + """ + logger.debug('Executing "/api/mangas/:id/topics" method') + validated_numbers = Utils.query_numbers_validator( + page=[page, 100000], + limit=[limit, 30], + ) + + response: List[Dict[str, Any]] = self._request( + self._endpoints.manga_topics(manga_id), + query=Utils.generate_query_dict(page=validated_numbers['page'], + limit=validated_numbers['limit'])) + if response: + return [Topic(**topic) for topic in response] + return None + def users(self, page: Optional[int] = None, limit: Optional[int] = None) -> Optional[List[User]]: @@ -1666,14 +1902,15 @@ def users(self, :rtype: Optional[List[User]] """ logger.debug('Executing API method') - logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 100) + validated_numbers = Utils.query_numbers_validator( + page=[page, 100000], + limit=[limit, 100], + ) response: List[Dict[str, Any]] = self._request( self._endpoints.users, - query=Utils.generate_query_dict(page=page, limit=limit)) + query=Utils.generate_query_dict(page=validated_numbers['page'], + limit=validated_numbers['limit'])) if response: return [User(**user) for user in response] return None @@ -1791,8 +2028,9 @@ def user_anime_rates( is_nickname: Optional[bool] = None, page: Optional[int] = None, limit: Optional[int] = None, - status: Optional[MyList] = None, - censored: Optional[Censorship] = None) -> Optional[List[UserList]]: + status: Optional[AnimeList] = None, + censored: Optional[AnimeCensorship] = None + ) -> Optional[List[UserList]]: """ Returns user's anime list. @@ -1809,25 +2047,25 @@ def user_anime_rates( :type limit: Optional[int] :param status: Status of status of anime in list - :type status: Optional[MyList] + :type status: Optional[AnimeList] :param censored: Type of anime censorship - :type censored: Optional[Censorship] + :type censored: Optional[AnimeCensorship] :return: User's anime list or None, if page is empty :rtype: Optional[List[UserList]] """ logger.debug('Executing API method') - logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 5000) + validated_numbers = Utils.query_numbers_validator( + page=[page, 100000], + limit=[limit, 5000], + ) response: List[Dict[str, Any]] = self._request( self._endpoints.user_anime_rates(user_id), query=Utils.generate_query_dict(is_nickname=is_nickname, - page=page, - limit=limit, + page=validated_numbers['page'], + limit=validated_numbers['limit'], status=status, censored=censored)) if response: @@ -1840,7 +2078,8 @@ def user_manga_rates( is_nickname: Optional[bool] = None, page: Optional[int] = None, limit: Optional[int] = None, - censored: Optional[Censorship] = None) -> Optional[List[UserList]]: + censored: Optional[AnimeCensorship] = None + ) -> Optional[List[UserList]]: """ Returns user's manga list. @@ -1857,22 +2096,22 @@ def user_manga_rates( :type limit: Optional[int] :param censored: Type of manga censorship - :type censored: Optional[Censorship] + :type censored: Optional[AnimeCensorship] :return: User's manga list or None, if page is empty :rtype: Optional[List[UserList]] """ logger.debug('Executing API method') - logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 5000) + validated_numbers = Utils.query_numbers_validator( + page=[page, 100000], + limit=[limit, 5000], + ) response: List[Dict[str, Any]] = self._request( self._endpoints.user_manga_rates(user_id), query=Utils.generate_query_dict(is_nickname=is_nickname, - page=page, - limit=limit, + page=validated_numbers['page'], + limit=validated_numbers['limit'], censored=censored)) if response: return [UserList(**user_list) for user_list in response] @@ -1930,17 +2169,17 @@ def current_user_messages( :rtype: Optional[List[Message]] """ logger.debug('Executing API method') - logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 100) + validated_numbers = Utils.query_numbers_validator( + page=[page, 100000], + limit=[limit, 100], + ) response: List[Dict[str, Any]] = self._request( self._endpoints.user_messages(user_id), headers=self._authorization_header, query=Utils.generate_query_dict(is_nickname=is_nickname, - page=page, - limit=limit, + page=validated_numbers['page'], + limit=validated_numbers['limit'], type=message_type)) if response: return [Message(**message) for message in response] @@ -2004,16 +2243,16 @@ def user_history( :rtype: Optional[List[History]] """ logger.debug('Executing API method') - logger.debug('Checking page parameter') - page = Utils.validate_query_number(page, 100000) - logger.debug('Checking limit parameter') - limit = Utils.validate_query_number(limit, 100) + validated_numbers = Utils.query_numbers_validator( + page=[page, 100000], + limit=[limit, 100], + ) response: List[Dict[str, Any]] = self._request( self._endpoints.user_history(user_id), query=Utils.generate_query_dict(is_nickname=is_nickname, - page=page, - limit=limit, + page=validated_numbers['page'], + limit=validated_numbers['limit'], target_id=target_id, target_type=target_type)) if response: diff --git a/shikithon/enums/manga.py b/shikithon/enums/manga.py new file mode 100644 index 00000000..b0b27f0a --- /dev/null +++ b/shikithon/enums/manga.py @@ -0,0 +1,76 @@ +"""Enums for /api/mangas.""" +from enum import Enum + + +class MangaOrder(Enum): + """Contains constants related for list ordering query.""" + ID = 'id' + ID_DESC = 'id_desc' + RANKED = 'ranked' + KIND = 'kind' + POPULARITY = 'popularity' + NAME = 'name' + AIRED_ON = 'aired_on' + VOLUMES = 'volumes' + CHAPTERS = 'chapters' + CREATED_AT = 'created_at' + CREATED_AT_DESC = 'created_at_desc' + RANDOM = 'random' + + +class MangaKind(Enum): + """Contains constants related for getting certain kind of manga.""" + MANGA = 'manga' + NOT_MANGA = '!manga' + MANHWA = 'manhwa' + NOT_MANHWA = '!manhwa' + MANHUA = 'manhua' + NOT_MANHUA = '!manhua' + LIGHT_NOVEL = 'light_novel' + NOT_LIGHT_NOVEL = '!light_novel' + NOVEL = 'novel' + NOT_NOVEL = '!novel' + ONE_SHOT = 'one_shot' + NOT_ONE_SHOT = '!one_shot' + DOUJIN = 'doujin' + NOT_DOUJIN = '!doujin' + + +class MangaStatus(Enum): + """Contains constants related for getting certain status of manga.""" + ANONS = 'anons' + NOT_ANONS = '!anons' + ONGOING = 'ongoing' + NOT_ONGOING = '!ongoing' + RELEASED = 'released' + NOT_RELEASED = '!released' + PAUSED = 'paused' + NOT_PAUSED = '!paused' + DISCONTINUED = 'discontinued' + NOT_DISCONTINUED = '!discontinued' + + +class MangaCensorship(Enum): + """Contains constants related for getting + certain censorship status of manga. + """ + CENSORED = 'true' + UNCENSORED = 'false' + + +class MangaList(Enum): + """Contains constants related for getting + certain user list status of manga. + """ + PLANNED = 'planned' + NOT_PLANNED = '!planned' + WATCHING = 'watching' + NOT_WATCHING = '!watching' + REWATCHING = 'rewatching' + NOT_REWATCHING = '!rewatching' + COMPLETED = 'completed' + NOT_COMPLETED = '!completed' + ON_HOLD = 'on_hold' + NOT_ON_HOLD = '!on_hold' + DROPPED = 'dropped' + NOT_DROPPED = '!dropped' diff --git a/shikithon/utils.py b/shikithon/utils.py index 7dce3815..c7ddfecd 100644 --- a/shikithon/utils.py +++ b/shikithon/utils.py @@ -169,8 +169,8 @@ def validate_query_number(query_number: Optional[int], """ Validates query number. - If number is not in range, returns lower limit number, - otherwise number or None. + If number is lower, returns lower limit, else upper limit. + If number is None, returns or None. :param query_number: Number to validate :type query_number: Optional[int] @@ -178,7 +178,7 @@ def validate_query_number(query_number: Optional[int], :param upper_limit: Upper limit for range check :type upper_limit: int - :return: Validated value + :return: Validated number :rtype: Optional[int] """ logger.debug(f'Validating query number ({query_number}) ' @@ -186,9 +186,52 @@ def validate_query_number(query_number: Optional[int], if query_number is None: logger.debug('Query number is None') return query_number - if query_number < LOWER_LIMIT_NUMBER or query_number > upper_limit: - logger.debug(f'Query number ({query_number}) is not in range. ' + + if query_number < LOWER_LIMIT_NUMBER: + logger.debug(f'Query number ({query_number}) is lower ' + f'than lower limit ({LOWER_LIMIT_NUMBER}). ' f'Returning {LOWER_LIMIT_NUMBER=}') return LOWER_LIMIT_NUMBER - logger.debug(f'Returning validated query number ({query_number})') + + if query_number > upper_limit: + logger.debug(f'Query number ({query_number}) is higher ' + f'than upper limit ({upper_limit}). ' + f'Returning {upper_limit=}') + return upper_limit + + logger.debug(f'Returning passed query number ({query_number})') return query_number + + @staticmethod + def query_numbers_validator(**query_numbers: List[Optional[int]] + ) -> Dict[str, Optional[int]]: + """ + Gets all query numbers to validate and returns validated numbers. + + This method uses validate_query_number method for validating. + + Query numbers are passed in such form: + { "page": [1, 100], ... } + + "page" <- Name of query number + + [1 (Passed value), 100 (Upper limit value)] + + This method outputs them like this: + { "page": 1 } + + "page" <- Name of query number + + 1 <- Validated number + + :param query_numbers: Passed query numbers to validate + :type query_numbers: List[Optional[int]] + :return: Dict of validated numbers + :rtype: Dict[str, Optional[int]] + """ + validated_numbers: Dict[str, Optional[int]] = {} + for name, data in query_numbers.items(): + logger.debug(f'Checking {name} parameter') + validated_numbers[name] = (Utils.validate_query_number( + data[0], data[1])) + return validated_numbers From 20a93f1fdb126f9a98f3cfaf0b80de12f6751946 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Mon, 16 May 2022 01:03:08 +0300 Subject: [PATCH 06/22] fix getting validated number from dict --- shikithon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shikithon/api.py b/shikithon/api.py index f46e2725..697fbf60 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -675,7 +675,7 @@ def animes(self, response: List[Dict[str, Any]] = self._request( self._endpoints.animes, headers=headers, - query=Utils.generate_query_dict(page=validated_numbers['name'], + query=Utils.generate_query_dict(page=validated_numbers['page'], limit=validated_numbers['limit'], order=order, kind=kind, From a177b517401012ea7f20e557f4d1ea1679617ebd Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Mon, 16 May 2022 01:03:44 +0300 Subject: [PATCH 07/22] updated types of params_data in generate_query_dict --- shikithon/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shikithon/utils.py b/shikithon/utils.py index c7ddfecd..3ed2d48b 100644 --- a/shikithon/utils.py +++ b/shikithon/utils.py @@ -71,7 +71,8 @@ def get_new_expire_time(time_expire_constant: int) -> int: @staticmethod def generate_query_dict( - **params_data: Optional[Union[str, bool, int, Enum, List[int]]] + **params_data: Optional[Union[str, bool, int, Enum, List[Union[int, + str]]]] ) -> Dict[str, str]: """ Returns valid query dict for API requests. @@ -79,7 +80,8 @@ def generate_query_dict( This methods checks for data type and converts to valid one. :param params_data: API methods parameters data - :type params_data: Optional[Union[str, bool, int, Enum, List[int]]] + :type params_data: + Optional[Union[str, bool, int, Enum, List[Union[int, str]]]] :return: Valid query dictionary :rtype: Dict[str, str] From 03d0fc8c8691450a43657078fc068109be2dd3da Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Mon, 16 May 2022 01:08:14 +0300 Subject: [PATCH 08/22] update executing API method messages --- shikithon/api.py | 120 +++++++++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/shikithon/api.py b/shikithon/api.py index 697fbf60..61eb06b3 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -578,7 +578,7 @@ def achievements(self, user_id: int) -> List[Achievement]: :return: List of achievements :rtype: List[Achievement] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/achievements/ method') response: List[Dict[str, Any]] = self._request( self._endpoints.achievements, query=Utils.generate_query_dict(user_id=user_id)) @@ -849,19 +849,21 @@ def appears(self, comment_ids: List[str]) -> bool: """ Marks comments or topics as read. + This method uses generate_query_dict for data dict, + because there is no need for nested dictionary + :param comment_ids: IDs of comments or topics to mark :type comment_ids: List[str] :return: Status of mark :rtype: bool """ - logger.debug('Executing API method') - logger.debug('Combining comment IDs into a single line') - data: Dict[str, str] = {'ids': ','.join(comment_ids)} - response_code: int = self._request(self._endpoints.appears, - headers=self._authorization_header, - data=data, - request_type=RequestType.POST) + logger.debug('Executing "/api/appears" method') + response_code: int = self._request( + self._endpoints.appears, + headers=self._authorization_header, + data=Utils.generate_query_dict(ids=comment_ids), + request_type=RequestType.POST) return response_code == ResponseCode.SUCCESS.value def bans(self, @@ -879,7 +881,7 @@ def bans(self, :return: List of recent bans or None, if page is empty :rtype: Optional[List[Ban """ - logger.debug('Executing API method') + logger.debug('Executing "/api/bans" method') logger.debug('Checking page parameter') validated_numbers = Utils.query_numbers_validator( page=[page, 100000], @@ -906,7 +908,7 @@ def calendar( :return: List of calendar events :rtype: List[CalendarEvent] """ - logger.debug('Executing API method') + logger.debug('Executing "api/calendar" method') response: List[Dict[str, Any]] = self._request( self._endpoints.calendar, query=Utils.generate_query_dict(censored=censored)) @@ -922,7 +924,7 @@ def character(self, character_id: int) -> Character: :return: Character info :rtype: Character """ - logger.debug('Executing API method') + logger.debug('Executing "/api/characters/:id" method') response: Dict[str, Any] = self._request( self._endpoints.character(character_id)) return Character(**response) @@ -937,7 +939,7 @@ def character_search(self, search: Optional[str] = None) -> List[Character]: :return: List of found characters :rtype: List[Character] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/characters/search" method') response: List[Dict[str, Any]] = self._request( self._endpoints.character_search, query=Utils.generate_query_dict(search=search)) @@ -962,7 +964,7 @@ def clubs(self, :return: Clubs list or None, if page is empty :rtype: Optional[List[Club]] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs" method') validated_numbers = Utils.query_numbers_validator( page=[page, 100000], limit=[limit, 30], @@ -987,7 +989,7 @@ def club(self, club_id: int) -> Club: :return: Info about club :rtype: Club """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id" method') response: Dict[str, Any] = self._request(self._endpoints.club(club_id)) return Club(**response) @@ -1072,7 +1074,7 @@ def club_update( :return: Updated club info or None if an error occurred :rtype: Optional[Club] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id" method') response: Dict[str, Any] = self._request( self._endpoints.club(club_id), headers=self._authorization_header, @@ -1110,7 +1112,7 @@ def club_animes(self, club_id: int) -> List[Anime]: :return: Club anime list :rtype: List[Anime] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id/animes" method') response: List[Dict[str, Any]] = self._request( self._endpoints.club_animes(club_id)) return [Anime(**anime) for anime in response] @@ -1125,7 +1127,7 @@ def club_mangas(self, club_id: int) -> List[Manga]: :return: Club manga list :rtype: List[Manga] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id/mangas" method') response: List[Dict[str, Any]] = self._request( self._endpoints.club_mangas(club_id)) return [Manga(**manga) for manga in response] @@ -1140,7 +1142,7 @@ def club_ranobe(self, club_id: int) -> List[Ranobe]: :return: Club ranobe list :rtype: List[Ranobe] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id/ranobe" method') response: List[Dict[str, Any]] = self._request( self._endpoints.club_ranobe(club_id)) return [Ranobe(**ranobe) for ranobe in response] @@ -1155,7 +1157,7 @@ def club_characters(self, club_id: int) -> List[Character]: :return: Club character list :rtype: List[Character] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id/characters" method') response: List[Dict[str, Any]] = self._request( self._endpoints.club_characters(club_id)) return [Character(**character) for character in response] @@ -1170,7 +1172,7 @@ def club_members(self, club_id: int) -> List[User]: :return: Club member list :rtype: List[User] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id/members" method') response: List[Dict[str, Any]] = self._request( self._endpoints.club_members(club_id)) return [User(**user) for user in response] @@ -1185,7 +1187,7 @@ def club_images(self, club_id: int) -> List[ClubImage]: :return: Club's images :rtype: List[ClubImage] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id/images" method') response: List[Dict[str, Any]] = self._request( self._endpoints.club_images(club_id)) return [ClubImage(**club_image) for club_image in response] @@ -1201,7 +1203,7 @@ def club_join(self, club_id: int): :return: Status of join :rtype: bool """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id/join" method') response: Union[Dict[str, Any], int] = self._request(self._endpoints.club_join(club_id), headers=self._authorization_header, @@ -1220,7 +1222,7 @@ def club_leave(self, club_id: int) -> bool: :return: Status of leave :rtype: bool """ - logger.debug('Executing API method') + logger.debug('Executing "/api/clubs/:id/leave" method') response: Union[Dict[str, Any], int] = self._request( self._endpoints.club_leave(club_id), headers=self._authorization_header, @@ -1255,7 +1257,7 @@ def comments(self, :return: List of comments or None, if page is empty :rtype: Optional[List[Comment]] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/comments" method') validated_numbers = Utils.query_numbers_validator( page=[page, 100000], limit=[limit, 30], @@ -1282,7 +1284,7 @@ def comment(self, comment_id: int) -> Comment: :return: Comment info :rtype: Comment """ - logger.debug('Executing API method') + logger.debug('Executing "/api/comments/:id" method') response: Dict[str, Any] = self._request(self._endpoints.comment(comment_id)) return Comment(**response) @@ -1318,7 +1320,7 @@ def create_comment(self, :return: Updated comment info or None if an error occurred :rtype: Optional[Comment] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/comments" method') data_dict: Dict[str, Any] = Utils.generate_data_dict( dict_name='comment', body=body, @@ -1353,7 +1355,7 @@ def update_comment(self, comment_id: int, body: str) -> Optional[Comment]: :return: Updated comment info or None if an error occurred :rtype: Optional[Comment] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/comments/:id" method') response: Dict[str, Any] = self._request( self._endpoints.comment(comment_id), headers=self._authorization_header, @@ -1374,7 +1376,7 @@ def delete_comment(self, comment_id: int) -> bool: :return: Status of comment deletion :rtype: bool """ - logger.debug('Executing API method') + logger.debug('Executing "/api/comments/:id" method') response: Dict[str, Any] = self._request(self._endpoints.comment(comment_id), headers=self._authorization_header, @@ -1390,7 +1392,7 @@ def anime_constants(self) -> AnimeConstants: :return: Anime constants values :rtype: AnimeConstants """ - logger.debug('Executing API method') + logger.debug('Executing "/api/constants/anime" method') response: Dict[str, Any] = self._request(self._endpoints.anime_constants) return AnimeConstants(**response) @@ -1402,7 +1404,7 @@ def manga_constants(self) -> MangaConstants: :return: Manga constants values :rtype: MangaConstants """ - logger.debug('Executing API method') + logger.debug('Executing "/api/constants/manga" method') response: Dict[str, Any] = self._request(self._endpoints.manga_constants) return MangaConstants(**response) @@ -1414,7 +1416,7 @@ def user_rate_constants(self) -> UserRateConstants: :return: User rate constants values :rtype: UserRateConstants """ - logger.debug('Executing API method') + logger.debug('Executing "/api/constants/user_rate" method') response: Dict[str, Any] = self._request(self._endpoints.user_rate_constants) return UserRateConstants(**response) @@ -1426,7 +1428,7 @@ def club_constants(self) -> ClubConstants: :return: Club constants values :rtype: ClubConstants """ - logger.debug('Executing API method') + logger.debug('Executing "/api/constants/club" method') response: Dict[str, Any] = self._request(self._endpoints.club_constants) return ClubConstants(**response) @@ -1437,7 +1439,7 @@ def smileys_constants(self) -> List[SmileyConstants]: :return: List of smileys constants values :rtype: List[SmileyConstants] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/constants/smileys" method') response: List[Dict[str, Any]] = self._request( self._endpoints.smileys_constants) return [SmileyConstants(**smiley) for smiley in response] @@ -1450,7 +1452,7 @@ def dialogs(self) -> Optional[List[Dialog]]: :return: List of dialogs or None, if there are no dialogs :rtype: Optional[List[Dialog]] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/dialogs" method') response: List[Dict[str, Any]] = self._request( self._endpoints.dialogs, headers=self._authorization_header) if response: @@ -1468,7 +1470,7 @@ def dialog(self, user_id: Union[int, str]) -> Optional[List[Message]]: :return: List of messages or None, if there are no messages :rtype: Optional[List[Message]] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/dialogs/:id" method') response: List[Dict[str, Any]] = self._request( self._endpoints.dialog(user_id), headers=self._authorization_header) if response: @@ -1486,7 +1488,7 @@ def delete_dialog(self, user_id: Union[int, str]) -> bool: :return: Status of message deletion :rtype: bool """ - logger.debug('Executing API method') + logger.debug('Executing "/api/dialogs/:id" method') response: List[Dict[str, Any]] = self._request( self._endpoints.dialog(user_id), headers=self._authorization_header, @@ -1516,7 +1518,9 @@ def create_favorite(self, :return: Status of favorite create :rtype: bool """ - logger.debug('Executing API method') + logger.debug('Executing ' + '"/api/favorites/:linked_type/:linked_id(/:kind)" ' + 'method') response: Dict[str, Any] = self._request(self._endpoints.favorites_create( linked_type, linked_id, kind), @@ -1540,7 +1544,9 @@ def destroy_favorite(self, linked_type: LinkedType, linked_id: int) -> bool: :return: Status of favorite destroy :rtype: bool """ - logger.debug('Executing API method') + logger.debug('Executing ' + '"/api/favorites/:linked_type/:linked_id" ' + 'method') response: Dict[str, Any] = self._request(self._endpoints.favorites_destroy( linked_type, linked_id), @@ -1566,7 +1572,7 @@ def reorder_favorite(self, :return: Status of reorder :rtype: bool """ - logger.debug('Executing API method') + logger.debug('Executing "/api/favorites/:id/reorder" method') response: Union[Dict[str, Any], int] = self._request( self._endpoints.favorites_reorder(favorite_id), headers=self._authorization_header, @@ -1585,7 +1591,7 @@ def forums(self) -> List[Forum]: :returns: List of forums :rtype: List[Forum] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/forums" method') response: List[Dict[str, Any]] = self._request(self._endpoints.forums) return [Forum(**forum) for forum in response] @@ -1604,7 +1610,7 @@ def create_friend(self, friend_id: int): :return: Status of create (addition) :rtype: bool """ - logger.debug('Executing API method') + logger.debug('Executing "/api/friends/:id" method') if isinstance(friend_id, str): logger.debug( @@ -1634,7 +1640,7 @@ def destroy_friend(self, friend_id: int): :return: Status of destroy (removal) :rtype: bool """ - logger.debug('Executing API method') + logger.debug('Executing "/api/friends/:id" method') if isinstance(friend_id, str): logger.debug( @@ -1656,7 +1662,7 @@ def genres(self) -> List[Genre]: :return: List of genres :rtype: List[Genre] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/genres" method') response: List[Dict[str, Any]] = self._request(self._endpoints.genres) return [Genre(**genre) for genre in response] @@ -1901,7 +1907,7 @@ def users(self, :return: List of users :rtype: Optional[List[User]] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users" method') validated_numbers = Utils.query_numbers_validator( page=[page, 100000], limit=[limit, 100], @@ -1930,7 +1936,7 @@ def user(self, :return: Info about user :rtype: User """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id" method') response: Dict[str, Any] = self._request( self._endpoints.user(user_id), query=Utils.generate_query_dict(is_nickname=is_nickname)) @@ -1951,7 +1957,7 @@ def user_info(self, :return: User's brief info :rtype: User """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users"/:id/info method') response: Dict[str, Any] = self._request( self._endpoints.user_info(user_id), query=Utils.generate_query_dict(is_nickname=is_nickname)) @@ -1967,7 +1973,7 @@ def current_user(self) -> User: :return: Current user brief info :rtype: User """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/whoami" method') response: Dict[str, Any] = self._request(self._endpoints.whoami, headers=self._authorization_header) @@ -1976,7 +1982,7 @@ def current_user(self) -> User: @protected_method() def sign_out(self): """Sends sign out request to API.""" - logger.debug('Executing API method') + logger.debug('Executing "/api/users/sign_out" method') self._request(self._endpoints.sign_out, headers=self._authorization_header) @@ -1995,7 +2001,7 @@ def user_friends(self, :return: List of user's friends :rtype: List[User] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id/friends" method') response: List[Dict[str, Any]] = self._request( self._endpoints.user_friends(user_id), query=Utils.generate_query_dict(is_nickname=is_nickname)) @@ -2016,7 +2022,7 @@ def user_clubs(self, :return: List of user's clubs :rtype: List[Club] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id/clubs" method') response: List[Dict[str, Any]] = self._request( self._endpoints.user_clubs(user_id), query=Utils.generate_query_dict(is_nickname=is_nickname)) @@ -2055,7 +2061,7 @@ def user_anime_rates( :return: User's anime list or None, if page is empty :rtype: Optional[List[UserList]] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id/anime_rates" method') validated_numbers = Utils.query_numbers_validator( page=[page, 100000], limit=[limit, 5000], @@ -2101,7 +2107,7 @@ def user_manga_rates( :return: User's manga list or None, if page is empty :rtype: Optional[List[UserList]] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id/manga_rates" method') validated_numbers = Utils.query_numbers_validator( page=[page, 100000], limit=[limit, 5000], @@ -2132,7 +2138,7 @@ def user_favourites(self, :return: User's favourites :rtype: Favourites """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id/favourites" method') response: Dict[str, Any] = self._request( self._endpoints.user_favourites(user_id), query=Utils.generate_query_dict(is_nickname=is_nickname)) @@ -2168,7 +2174,7 @@ def current_user_messages( :return: Current user's messages or None, if page is empty :rtype: Optional[List[Message]] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id/messages" method') validated_numbers = Utils.query_numbers_validator( page=[page, 100000], limit=[limit, 100], @@ -2202,7 +2208,7 @@ def current_user_unread_messages( :return: Current user's unread messages counters :rtype: UnreadMessages """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id/unread_messages" method') response: Dict[str, Any] = self._request( self._endpoints.user_unread_messages(user_id), headers=self._authorization_header, @@ -2242,7 +2248,7 @@ def user_history( :return: User's history or None, if page is empty :rtype: Optional[List[History]] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id/history" method') validated_numbers = Utils.query_numbers_validator( page=[page, 100000], limit=[limit, 100], @@ -2274,7 +2280,7 @@ def user_bans(self, :return: User's bans :rtype: List[Ban] """ - logger.debug('Executing API method') + logger.debug('Executing "/api/users/:id/bans" method') response: List[Dict[str, Any]] = self._request( self._endpoints.user_bans(user_id), query=Utils.generate_query_dict(is_nickname=is_nickname)) From 958c73d9bfe523f380d0dcd5439d659637285142 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Mon, 16 May 2022 01:09:23 +0300 Subject: [PATCH 09/22] remove function name from decorator debug print --- shikithon/decorators.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shikithon/decorators.py b/shikithon/decorators.py index 6bf79633..1b18dba3 100644 --- a/shikithon/decorators.py +++ b/shikithon/decorators.py @@ -37,8 +37,7 @@ def wrapper(api: API, *args, **kwargs): or if required scope is missing :rtype: None """ - logger.debug('Checking the possibility of using a protected ' - f'"{function.__name__}" method') + logger.debug('Checking the possibility of using a protected method') if api.restricted_mode: logger.debug('It is not possible to use the protected method ' 'due to the restricted mode') From 1716698d22a573db4d126fc2f27b7648372ecb7d Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Mon, 16 May 2022 01:13:33 +0300 Subject: [PATCH 10/22] add logging for anime/manga restricted mode check --- shikithon/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shikithon/api.py b/shikithon/api.py index 61eb06b3..ecfebb7f 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -670,6 +670,7 @@ def animes(self, headers: Optional[Dict[str, str]] = None if not self.restricted_mode: + logger.debug('Using /api/animes" as protected method') headers = self._authorization_header response: List[Dict[str, Any]] = self._request( @@ -1743,6 +1744,7 @@ def mangas(self, headers: Optional[Dict[str, str]] = None if not self.restricted_mode: + logger.debug('Using /api/mangas" as protected method') headers = self._authorization_header response: List[Dict[str, Any]] = self._request( From e56962e52fb2dc62967c525aa46ed35c0d90e231 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Mon, 16 May 2022 01:17:54 +0300 Subject: [PATCH 11/22] update some logging messages --- shikithon/api.py | 6 +++--- shikithon/utils.py | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/shikithon/api.py b/shikithon/api.py index ecfebb7f..472dfeeb 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -274,7 +274,7 @@ def _init_config(self, config: Union[str, Dict[str, str]]): logger.debug('Initializing API config') self._validate_config(config) self._validate_vars() - logger.debug('Setting User-Agent with current app_name') + logger.debug('Setting User-Agent with current app name') self._user_agent = self._app_name if isinstance(config, dict) and not self._access_token: @@ -670,7 +670,7 @@ def animes(self, headers: Optional[Dict[str, str]] = None if not self.restricted_mode: - logger.debug('Using /api/animes" as protected method') + logger.debug('Using "/api/animes" as protected method') headers = self._authorization_header response: List[Dict[str, Any]] = self._request( @@ -1744,7 +1744,7 @@ def mangas(self, headers: Optional[Dict[str, str]] = None if not self.restricted_mode: - logger.debug('Using /api/mangas" as protected method') + logger.debug('Using "/api/mangas" as protected method') headers = self._authorization_header response: List[Dict[str, Any]] = self._request( diff --git a/shikithon/utils.py b/shikithon/utils.py index 3ed2d48b..08d648c2 100644 --- a/shikithon/utils.py +++ b/shikithon/utils.py @@ -51,7 +51,7 @@ def convert_app_name(app_name: str) -> str: :return: Converted app name for filename :rtype: str """ - logger.debug(f'Converting "{app_name}" for cached config') + logger.debug(f'Converting {app_name=} for cached config') return '_'.join(app_name.lower().split(' ')) @staticmethod @@ -183,25 +183,25 @@ def validate_query_number(query_number: Optional[int], :return: Validated number :rtype: Optional[int] """ - logger.debug(f'Validating query number ({query_number}) ' - f'with upper limit ({upper_limit})') + logger.debug(f'Validating query number ("{query_number}") ' + f'with upper limit ("{upper_limit}")') if query_number is None: - logger.debug('Query number is None') + logger.debug('Query number is "None"') return query_number if query_number < LOWER_LIMIT_NUMBER: - logger.debug(f'Query number ({query_number}) is lower ' - f'than lower limit ({LOWER_LIMIT_NUMBER}). ' + logger.debug(f'Query number ("{query_number}") is lower ' + f'than lower limit ("{LOWER_LIMIT_NUMBER}"). ' f'Returning {LOWER_LIMIT_NUMBER=}') return LOWER_LIMIT_NUMBER if query_number > upper_limit: - logger.debug(f'Query number ({query_number}) is higher ' - f'than upper limit ({upper_limit}). ' + logger.debug(f'Query number ("{query_number}") is higher ' + f'than upper limit ("{upper_limit}"). ' f'Returning {upper_limit=}') return upper_limit - logger.debug(f'Returning passed query number ({query_number})') + logger.debug(f'Returning passed query number ("{query_number}")') return query_number @staticmethod @@ -233,7 +233,7 @@ def query_numbers_validator(**query_numbers: List[Optional[int]] """ validated_numbers: Dict[str, Optional[int]] = {} for name, data in query_numbers.items(): - logger.debug(f'Checking {name} parameter') + logger.debug(f'Checking "{name}" parameter') validated_numbers[name] = (Utils.validate_query_number( data[0], data[1])) return validated_numbers From 056d54801da3d3438ea0111fd52e4ee57cab61cd Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 02:34:24 +0300 Subject: [PATCH 12/22] fix support for int in generate_query/data_dict also fix check for correct digit string --- shikithon/utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shikithon/utils.py b/shikithon/utils.py index 08d648c2..e5488dcb 100644 --- a/shikithon/utils.py +++ b/shikithon/utils.py @@ -106,8 +106,10 @@ def generate_query_dict( for item in data: if isinstance(item, Enum): formatted_data.append(item.value) - elif item.isdigit(): + elif isinstance(item, int): formatted_data.append(str(item)) + elif isinstance(item, str) and item.isdigit(): + formatted_data.append(item) query_dict[key] = ','.join(formatted_data) else: query_dict[key] = data @@ -157,8 +159,10 @@ def generate_data_dict( for item in data: if isinstance(item, Enum): formatted_data.append(item.value) - elif item.isdigit(): + elif isinstance(item, int): formatted_data.append(str(item)) + elif isinstance(item, str) and item.isdigit(): + formatted_data.append(item) new_data_dict[data_dict_name][key] = ','.join(formatted_data) else: new_data_dict[data_dict_name][key] = data From a9d7b6be9a39227ba3a05bacc1e09aa6bd7b0a10 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 02:34:47 +0300 Subject: [PATCH 13/22] add NO_CONTENT enum --- shikithon/enums/response.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shikithon/enums/response.py b/shikithon/enums/response.py index 944b6cb3..22030d3c 100644 --- a/shikithon/enums/response.py +++ b/shikithon/enums/response.py @@ -5,4 +5,5 @@ class ResponseCode(Enum): """Contains response status codes.""" SUCCESS = 200 + NO_CONTENT = 204 RETRY_LATER = 429 From c453a8184d05b57e5701a02b1caeeec3d462205b Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 02:35:33 +0300 Subject: [PATCH 14/22] add indenting for cache config JSON --- shikithon/config_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shikithon/config_cache.py b/shikithon/config_cache.py index e7313a83..ba1228fa 100644 --- a/shikithon/config_cache.py +++ b/shikithon/config_cache.py @@ -123,7 +123,7 @@ def save_config(config: Dict[str, str]) -> bool: try: with open(config_name, 'w', encoding='utf-8') as config_file: - config_file.write(dumps(config)) + config_file.write(dumps(config, indent=4)) return True except IOError as err: logger.warning(f'Couldn\'t save config to file: {err}') From b040d2f7505421bcb5b4876e09cc18b2f6c0b87d Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 02:35:56 +0300 Subject: [PATCH 15/22] fix docstring typo of create_comment method --- shikithon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shikithon/api.py b/shikithon/api.py index 472dfeeb..71ef909a 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -1318,7 +1318,7 @@ def create_comment(self, :param broadcast: Broadcast comment in club’s topic status :type broadcast: Optional[bool] - :return: Updated comment info or None if an error occurred + :return: Created comment info or None if an error occurred :rtype: Optional[Comment] """ logger.debug('Executing "/api/comments" method') From 42cb3df8814212462321089035456a9b4f82de45 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 02:38:24 +0300 Subject: [PATCH 16/22] add /api/messages methods --- shikithon/api.py | 184 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/shikithon/api.py b/shikithon/api.py index 71ef909a..111c1bfa 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -1894,6 +1894,190 @@ def manga_topics(self, return [Topic(**topic) for topic in response] return None + @protected_method(scope='messages') + def message(self, message_id) -> Message: + """ + Returns message info. + + :param message_id: ID of message to get info + :type message_id: int + + :return: Message info + :rtype: Message + """ + logger.debug('Executing "/api/messages/:id" method') + response: Dict[str, + Any] = self._request(self._endpoints.message(message_id), + headers=self._authorization_header) + return Message(**response) + + @protected_method(scope='messages') + def create_message(self, body: str, from_id: int, + to_id: int) -> Optional[Message]: + """ + Updates message. + + :param body: Body of message + :type body: str + + :param from_id: Sender ID + :type from_id: int + + :param to_id: Reciver ID + :type to_id: int + + :return: Created message info or None if an error occurred + :rtype: Optional[Message] + """ + logger.debug('Executing "/api/messages" method') + response: Dict[str, Any] = self._request( + self._endpoints.messages, + headers=self._authorization_header, + data=Utils.generate_data_dict(dict_name='message', + body=body, + from_id=from_id, + kind='Private', + to_id=to_id), + request_type=RequestType.POST) + logger.debug( + f'Detailed information about creating the message {response=}') + return Message(**response) if 'errors' not in response else None + + @protected_method(scope='messages') + def update_message(self, message_id: int, body: str) -> Optional[Message]: + """ + Updates message. + + :param message_id: ID of message to update + :type message_id: int + + :param body: New body of message + :type body: str + + :return: Updated message info or None if an error occurred + :rtype: Optional[Message] + """ + logger.debug('Executing "/api/messages/:id" method') + response: Dict[str, Any] = self._request( + self._endpoints.message(message_id), + headers=self._authorization_header, + data=Utils.generate_data_dict(dict_name='message', body=body), + request_type=RequestType.PATCH) + logger.debug( + f'Detailed information about updating the message {response=}') + return Message(**response) if 'errors' not in response else None + + @protected_method(scope='messages') + def delete_message(self, message_id: int) -> bool: + """ + Deletes message. + + :param message_id: ID of message to delete + :type message_id: int + + :return: Status of message deletion + :rtype: bool + """ + logger.debug('Executing "/api/messages/:id" method') + response: Union[Dict[str, Any], int] = self._request( + self._endpoints.message(message_id), + headers=self._authorization_header, + request_type=RequestType.DELETE) + logger.debug( + f'Detailed information about deleting the message {response=}') + if isinstance(response, int): + return response == ResponseCode.NO_CONTENT.value + return False + + @protected_method(scope='messages') + def message_mark_read(self, + message_ids: Optional[Union[int, List[int]]] = None, + is_read: Optional[bool] = None) -> bool: + """ + Marks read/unread selected messages. + + This method uses generate_query_dict for data dict, + because there is no need for nested dictionary + + :param message_ids: ID(s) of messages to mark read/unread + :type message_ids: Optional[Union[int, List[int]]] + + :param is_read: Status of message (read/unread) + :type is_read: Optional[bool] + + :return: Status of messages read/unread + :rtype: bool + """ + logger.debug('Executing "/api/messages/mark_read" method') + response: Union[Dict[str, Any], int] = self._request( + self._endpoints.messages_mark_read, + headers=self._authorization_header, + data=Utils.generate_query_dict(ids=message_ids, is_read=is_read), + request_type=RequestType.POST) + logger.debug( + f'Detailed information about marking selected messages {response=}') + if isinstance(response, int): + return response == ResponseCode.SUCCESS.value + return False + + @protected_method(scope='messages') + def read_all_messages(self, message_type: MessageType) -> bool: + """ + Reads all messages on current user's account. + + This method uses generate_query_dict for data dict, + because there is no need for nested dictionary + + Note: This methods accepts as type only MessageType.NEWS and + MessageType.NOTIFICATIONS + + :param message_type: Type of messages to read + :type message_type: MessageType + + :return: Status of messages read + :rtype: bool + """ + logger.debug('Executing "/api/messages/read_all" method') + response: Union[Dict[str, Any], int] = self._request( + self._endpoints.messages_read_all, + headers=self._authorization_header, + data=Utils.generate_query_dict(type=message_type), + request_type=RequestType.POST) + logger.debug( + f'Detailed information about reading all messages {response=}') + if isinstance(response, int): + return response == ResponseCode.SUCCESS.value + return False + + @protected_method(scope='messages') + def delete_all_messages(self, message_type: MessageType) -> bool: + """ + Deletes all messages on current user's account. + + This method uses generate_query_dict for data dict, + because there is no need for nested dictionary + + Note: This methods accepts as type only MessageType.NEWS and + MessageType.NOTIFICATIONS + + :param message_type: Type of messages to delete + :type message_type: MessageType + + :return: Status of messages deletion + :rtype: bool + """ + logger.debug('Executing "/api/messages/delete_all" method') + response: Union[Dict[str, Any], int] = self._request( + self._endpoints.messages_delete_all, + headers=self._authorization_header, + data=Utils.generate_query_dict(type=message_type), + request_type=RequestType.POST) + logger.debug( + f'Detailed information about deleting all messages {response=}') + if isinstance(response, int): + return response == ResponseCode.SUCCESS.value + return False + def users(self, page: Optional[int] = None, limit: Optional[int] = None) -> Optional[List[User]]: From 0e867787ba537497df5c4a835a6cca7d8ee47bd3 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 03:04:56 +0300 Subject: [PATCH 17/22] update people.py rename from person.py to people.py update fields --- shikithon/models/people.py | 35 +++++++++++++++++++++++++++++++++++ shikithon/models/person.py | 13 ------------- 2 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 shikithon/models/people.py delete mode 100644 shikithon/models/person.py diff --git a/shikithon/models/people.py b/shikithon/models/people.py new file mode 100644 index 00000000..6bec68db --- /dev/null +++ b/shikithon/models/people.py @@ -0,0 +1,35 @@ +"""Model for /api/people and submodel for creator.py""" +from datetime import datetime +from typing import List, Optional, Tuple + +from pydantic import BaseModel + +from shikithon.models.image import Image +from shikithon.models.people_roles import PeopleRoles +from shikithon.models.people_works import PeopleWorks + + +class People(BaseModel): + """Represents person entity.""" + id: int + name: str + russian: str + image: Image + url: str + japanese: Optional[str] + job_title: Optional[str] + birthday: Optional[str] + website: Optional[str] + groupped_roles: Optional[List[Tuple[str, int]]] + roles: Optional[List[PeopleRoles]] + works: Optional[List[PeopleWorks]] + thread_id: Optional[int] + topic_id: Optional[int] + person_favoured: Optional[bool] + producer: Optional[bool] + producer_favoured: Optional[bool] + mangaka: Optional[bool] + mangaka_favoured: Optional[bool] + seyu: Optional[bool] + seyu_favoured: Optional[bool] + updated_at: Optional[datetime] diff --git a/shikithon/models/person.py b/shikithon/models/person.py deleted file mode 100644 index 80c2cd42..00000000 --- a/shikithon/models/person.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Submodel for creator.py""" -from pydantic import BaseModel - -from shikithon.models.image import Image - - -class Person(BaseModel): - """Represents person entity.""" - id: int - name: str - russian: str - image: Image - url: str From 6a6d9aa0e39f3c1cf4d142314178e7a867ed657f Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 03:05:16 +0300 Subject: [PATCH 18/22] update creator.py fix person model name --- shikithon/models/creator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shikithon/models/creator.py b/shikithon/models/creator.py index 957dee38..8fb114e0 100644 --- a/shikithon/models/creator.py +++ b/shikithon/models/creator.py @@ -4,7 +4,7 @@ from pydantic import BaseModel from shikithon.models.character import Character -from shikithon.models.person import Person +from shikithon.models.people import People class Creator(BaseModel): @@ -12,4 +12,4 @@ class Creator(BaseModel): roles: List[str] roles_russian: List[str] character: Optional[Character] - person: Optional[Person] + person: Optional[People] From 6e3000a767f8a01f5acaa8683f9ea4b6a9884be5 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 03:05:31 +0300 Subject: [PATCH 19/22] add new submodels for people.py --- shikithon/models/people_roles.py | 13 +++++++++++++ shikithon/models/people_works.py | 14 ++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 shikithon/models/people_roles.py create mode 100644 shikithon/models/people_works.py diff --git a/shikithon/models/people_roles.py b/shikithon/models/people_roles.py new file mode 100644 index 00000000..12fff8fb --- /dev/null +++ b/shikithon/models/people_roles.py @@ -0,0 +1,13 @@ +"""Submodel for people.py""" +from typing import List, Optional + +from pydantic import BaseModel + +from shikithon.models.anime import Anime +from shikithon.models.character import Character + + +class PeopleRoles(BaseModel): + """Represents roles entity of person.""" + characters: Optional[List[Character]] + anime: Optional[List[Anime]] diff --git a/shikithon/models/people_works.py b/shikithon/models/people_works.py new file mode 100644 index 00000000..ba26654f --- /dev/null +++ b/shikithon/models/people_works.py @@ -0,0 +1,14 @@ +"""Submodel for people.py""" +from typing import Optional + +from pydantic import BaseModel + +from shikithon.models.anime import Anime +from shikithon.models.manga import Manga + + +class PeopleWorks(BaseModel): + """Represents works entity of person.""" + anime: Optional[Anime] + manga: Optional[Manga] + role: str From 0528f0fa9493e877d56a52a0b6d0f20246912c3c Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 03:06:33 +0300 Subject: [PATCH 20/22] update api.py add methods for /api/people fix docstring and person model name --- shikithon/api.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/shikithon/api.py b/shikithon/api.py index 111c1bfa..50fb6423 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -54,6 +54,7 @@ from shikithon.models.link import Link from shikithon.models.manga import Manga from shikithon.models.message import Message +from shikithon.models.people import People from shikithon.models.ranobe import Ranobe from shikithon.models.relation import Relation from shikithon.models.screenshot import Screenshot @@ -935,7 +936,7 @@ def character_search(self, search: Optional[str] = None) -> List[Character]: Returns list of found characters. :param search: Search query for characters - :type search: Optional[str + :type search: Optional[str] :return: List of found characters :rtype: List[Character] @@ -2078,6 +2079,48 @@ def delete_all_messages(self, message_type: MessageType) -> bool: return response == ResponseCode.SUCCESS.value return False + def people(self, people_id: int) -> People: + """ + Returns info about a person. + + :param people_id: ID of person to get info + :type people_id: int + + :return: Info about a person + :rtype: People + """ + logger.debug('Executing "/api/people/:id" method') + response: Dict[str, + Any] = self._request(self._endpoints.people(people_id)) + return People(**response) + + def people_search( + self, + search: Optional[str] = None, + people_kind: Optional[PersonKind] = None) -> Optional[List[People]]: + """ + Returns list of found persons. + + Note: This API method only allows PersonKind.SEYU, + PersonKind.MANGAKA or PersonKind.PRODUCER as kind parameter + + :param search: Search query for persons + :type search: Optional[str] + + :param people_kind: Kind of person for searching + :type people_kind: Optional[PersonKind] + + :return: List of found persons or None, if list is empty + :rtype: Optional[List[People]] + """ + logger.debug('Executing "/api/people/search" method') + response: List[Dict[str, Any]] = self._request( + self._endpoints.people_search, + query=Utils.generate_query_dict(search=search, kind=people_kind)) + if not response: + return None + return [People(**people) for people in response] + def users(self, page: Optional[int] = None, limit: Optional[int] = None) -> Optional[List[User]]: From 4128d1dbbb4d52b34a22d5bf5e6c0e9d17a916a9 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 10:00:16 +0300 Subject: [PATCH 21/22] fix methods docs --- shikithon/api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shikithon/api.py b/shikithon/api.py index 50fb6423..dd0d396a 100644 --- a/shikithon/api.py +++ b/shikithon/api.py @@ -881,7 +881,7 @@ def bans(self, :type limit: Optional[int] :return: List of recent bans or None, if page is empty - :rtype: Optional[List[Ban + :rtype: Optional[List[Ban]] """ logger.debug('Executing "/api/bans" method') logger.debug('Checking page parameter') @@ -961,7 +961,7 @@ def clubs(self, :type limit: Optional[int] :param search: Search phrase to filter clubs by name - :type search: Optional[str + :type search: Optional[str] :return: Clubs list or None, if page is empty :rtype: Optional[List[Club]] @@ -1023,10 +1023,10 @@ def club_update( :type club_id: int :param name: New name of club - :type name: Optional[str + :type name: Optional[str] :param description: New description of club - :type description: Optional[str + :type description: Optional[str] :param display_images: New display images status of club :type display_images: Optional[bool] @@ -1916,7 +1916,7 @@ def message(self, message_id) -> Message: def create_message(self, body: str, from_id: int, to_id: int) -> Optional[Message]: """ - Updates message. + Creates message. :param body: Body of message :type body: str From fe7bb00db826d6d808cc7972dfdbef0bfbbb6930 Mon Sep 17 00:00:00 2001 From: SecondThundeR Date: Tue, 17 May 2022 10:00:59 +0300 Subject: [PATCH 22/22] bump version to 0.4.0 --- pyproject.toml | 2 +- shikithon/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4254ca78..ee1c4ea1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "shikithon" -version = "0.3.0" +version = "0.4.0" description = "Yet another Python wrapper for Shikimori API" authors = [ "SecondThundeR " diff --git a/shikithon/__init__.py b/shikithon/__init__.py index 87bfefca..3ea83a65 100644 --- a/shikithon/__init__.py +++ b/shikithon/__init__.py @@ -1,5 +1,5 @@ """Contains package version and some magic for importing API object.""" from shikithon.api import API -__version__ = '0.3.0' +__version__ = '0.4.0' __all__ = ['API']