diff --git a/docs/source/yandex_music.playlist.playlist_recommendations.rst b/docs/source/yandex_music.playlist.playlist_recommendations.rst new file mode 100644 index 0000000..60f499d --- /dev/null +++ b/docs/source/yandex_music.playlist.playlist_recommendations.rst @@ -0,0 +1,7 @@ +yandex_music.PlaylistRecommendations +==================================== + +.. autoclass:: yandex_music.PlaylistRecommendations + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/yandex_music.playlist.rst b/docs/source/yandex_music.playlist.rst index 10aa8aa..be53013 100644 --- a/docs/source/yandex_music.playlist.rst +++ b/docs/source/yandex_music.playlist.rst @@ -10,5 +10,6 @@ yandex_music.playlist.playlist yandex_music.playlist.case_forms yandex_music.playlist.playlist_id + yandex_music.playlist.playlist_recommendations yandex_music.playlist.tag_result yandex_music.playlist.tag diff --git a/tests/test_playlist_recommendations.py b/tests/test_playlist_recommendations.py new file mode 100644 index 0000000..292d98f --- /dev/null +++ b/tests/test_playlist_recommendations.py @@ -0,0 +1,43 @@ +import pytest + +from yandex_music import PlaylistRecommendations + + +@pytest.fixture(scope='class') +def playlist_recommendations(track): + return PlaylistRecommendations([track], TestPlaylistRecommendations.batch_id) + + +class TestPlaylistRecommendations: + batch_id = '1588835234913188-6341822935848536902' + + def test_expected_values(self, playlist_recommendations, track): + assert playlist_recommendations.batch_id == self.batch_id + assert playlist_recommendations.tracks == [track] + + def test_de_json_none(self, client): + assert PlaylistRecommendations.de_json({}, client) is None + + def test_de_json_required(self, client, track): + json_dict = {'tracks': [track.to_dict()]} + playlist_recommendations = PlaylistRecommendations.de_json(json_dict, client) + + assert playlist_recommendations.tracks == [track] + + def test_de_json_all(self, client, track): + json_dict = {'batch_id': self.batch_id, 'tracks': [track.to_dict()]} + playlist_recommendations = PlaylistRecommendations.de_json(json_dict, client) + + assert playlist_recommendations.batch_id == self.batch_id + assert playlist_recommendations.tracks == [track] + + def test_equality(self, track): + a = PlaylistRecommendations([track]) + b = PlaylistRecommendations([]) + c = PlaylistRecommendations([track]) + + assert a != b + assert hash(a) != hash(b) + assert a is not b + + assert a == c diff --git a/yandex_music/__init__.py b/yandex_music/__init__.py index 4fbba79..427d319 100644 --- a/yandex_music/__init__.py +++ b/yandex_music/__init__.py @@ -38,6 +38,7 @@ from .playlist.tag import Tag from .playlist.tag_result import TagResult from .playlist.playlist_absence import PlaylistAbsence from .playlist.playlist import Playlist +from .playlist.playlist_recommendation import PlaylistRecommendations from .shot.shot_type import ShotType from .shot.shot_data import ShotData @@ -125,4 +126,4 @@ __all__ = ['YandexMusicObject', 'Client', 'Account', 'PassportPhone', 'Invocatio 'Sequence', 'StationTracksResult', 'BriefInfo', 'Description', 'PlaylistId', 'Vinyl', 'Supplement', 'Lyrics', 'VideoSupplement', 'ArtistTracks', 'Pager', 'ArtistAlbums', 'PlaylistAbsence', 'Shot', 'ShotEvent', 'ShotType', 'ShotData', 'SimilarTracks', 'UserSettings', 'RenewableRemainder', 'ChartInfo', 'ChartInfoMenu', - 'ChartInfoMenuItem', 'Tag', 'TagResult'] + 'ChartInfoMenuItem', 'Tag', 'TagResult', 'PlaylistRecommendations'] diff --git a/yandex_music/client.py b/yandex_music/client.py index 5541eb9..e56fc83 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -6,7 +6,7 @@ from typing import Callable, Dict, List, Optional, Union from yandex_music import Album, Artist, ArtistAlbums, ArtistTracks, BriefInfo, Dashboard, DownloadInfo, Experiments, \ Feed, Genre, Landing, Like, PermissionAlerts, Playlist, PromoCodeStatus, Search, Settings, ShotEvent, Supplement, \ StationResult, StationTracksResult, Status, Suggestions, SimilarTracks, Track, TracksList, UserSettings, \ - YandexMusicObject, ChartInfo, TagResult + YandexMusicObject, ChartInfo, TagResult, PlaylistRecommendations from yandex_music.exceptions import Captcha, InvalidToken from yandex_music.utils.difference import Difference from yandex_music.utils.request import Request @@ -823,6 +823,33 @@ class Client(YandexMusicObject): return Playlist.de_json(result, self) + @log + def users_playlists_recommendations(self, kind: Union[str, int], user_id: Union[str, int] = None, + timeout: Union[int, float] = None, *args, **kwargs): + """Получение рекомендаций для плейлиста. + + Args: + kind (:obj:`str` | :obj:`int`): Уникальный идентификатор плейлиста. + user_id (:obj:`str` | :obj:`int`): Уникальный идентификатор пользователя владеющим плейлистом. + timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания + ответа от сервера вместо указанного при создании пула. + **kwargs (:obj:`dict`, optional): Произвольные аргументы (будут переданы в запрос). + + Returns: + :obj:`yandex_music.PlaylistRecommendations` | :obj:`None`: Рекомендации для плейлиста или :obj:`None`. + + Raises: + :class:`yandex_music.exceptions.YandexMusicError`: Базовое исключение библиотеки. + """ + if user_id is None and self.me is not None: + user_id = self.me.account.uid + + url = f'{self.base_url}/users/{user_id}/playlists/{kind}/recommendations' + + result = self._request.get(url, timeout=timeout, *args, **kwargs) + + return PlaylistRecommendations.de_json(result, self) + @log def users_playlists_create(self, title: str, visibility: str = 'public', user_id: Union[str, int] = None, timeout: Union[int, float] = None, *args, **kwargs) -> Optional[Playlist]: @@ -2134,6 +2161,10 @@ class Client(YandexMusicObject): generateTokenByUsernameAndPassword = generate_token_by_username_and_password #: Псевдоним для :attr:`account_status` accountStatus = account_status + #: Псевдоним для :attr:`account_settings` + accountSettings = account_settings + #: Псевдоним для :attr:`account_settings_set` + accountSettingsSet = account_settings_set #: Псевдоним для :attr:`permission_alerts` permissionAlerts = permission_alerts #: Псевдоним для :attr:`account_experiments` @@ -2154,8 +2185,12 @@ class Client(YandexMusicObject): albumsWithTracks = albums_with_tracks #: Псевдоним для :attr:`search_suggest` searchSuggest = search_suggest + #: Псевдоним для :attr:`users_settings` + usersSettings = users_settings #: Псевдоним для :attr:`users_playlists` usersPlaylists = users_playlists + #: Псевдоним для :attr:`users_playlists_recommendations` + usersPlaylistsRecommendations = users_playlists_recommendations #: Псевдоним для :attr:`users_playlists_create` usersPlaylistsCreate = users_playlists_create #: Псевдоним для :attr:`users_playlists_delete` diff --git a/yandex_music/playlist/playlist.py b/yandex_music/playlist/playlist.py index d55c0d4..fcf5401 100644 --- a/yandex_music/playlist/playlist.py +++ b/yandex_music/playlist/playlist.py @@ -3,7 +3,8 @@ from typing import TYPE_CHECKING, Optional, List from yandex_music import YandexMusicObject if TYPE_CHECKING: - from yandex_music import Client, User, Cover, MadeFor, TrackShort, PlaylistAbsence, PlayCounter + from yandex_music import Client, User, Cover, MadeFor, TrackShort, PlaylistAbsence, PlayCounter,\ + PlaylistRecommendations class Playlist(YandexMusicObject): @@ -168,6 +169,13 @@ class Playlist(YandexMusicObject): def playlist_id(self) -> str: return f'{self.owner.uid}:{self.kind}' + def get_recommendations(self, *args, **kwargs) -> Optional['PlaylistRecommendations']: + """Сокращение для:: + + client.users_playlists_recommendations(playlist.kind, playlist.owner.uid, *args, **kwargs) + """ + return self.client.users_playlists_recommendations(self.kind, self.owner.uid, *args, **kwargs) + def download_animated_cover(self, filename: str, size: str = '200x200') -> None: """Загрузка анимированной обложки. @@ -263,6 +271,8 @@ class Playlist(YandexMusicObject): isMine = is_mine #: Псевдоним для :attr:`playlist_id` playlistId = playlist_id + #: Псевдоним для :attr:`get_recommendations` + getRecommendations = get_recommendations #: Псевдоним для :attr:`download_animated_cover` downloadAnimatedCover = download_animated_cover #: Псевдоним для :attr:`download_og_image` diff --git a/yandex_music/playlist/playlist_recommendation.py b/yandex_music/playlist/playlist_recommendation.py new file mode 100644 index 0000000..e6dffc7 --- /dev/null +++ b/yandex_music/playlist/playlist_recommendation.py @@ -0,0 +1,55 @@ +from typing import TYPE_CHECKING, Optional, List + +from yandex_music import YandexMusicObject + +if TYPE_CHECKING: + from yandex_music import Client, Track + + +class PlaylistRecommendations(YandexMusicObject): + """Класс, представляющий рекомендации для плейлиста. + + Attributes: + tracks (:obj:`list` из :obj:`yandex_music.Track`): Список рекомендованных треков. + batch_id (:obj:`str`): Уникальный идентификатор партии треков. + client (:obj:`yandex_music.Client`): Клиент Yandex Music. + + Args: + tracks (:obj:`list` из :obj:`yandex_music.Track`): Список рекомендованных треков. + batch_id (:obj:`str`, optional): Уникальный идентификатор партии треков. + client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music. + **kwargs: Произвольные ключевые аргументы полученные от API. + """ + + def __init__(self, + tracks: List['Track'], + batch_id: Optional[str] = None, + client: Optional['Client'] = None, + **kwargs) -> None: + super().handle_unknown_kwargs(self, **kwargs) + + self.batch_id = batch_id + self.tracks = tracks + + self.client = client + self._id_attrs = (self.batch_id, self.tracks) + + @classmethod + def de_json(cls, data: dict, client: 'Client') -> Optional['PlaylistRecommendations']: + """Десериализация объекта. + + Args: + data (:obj:`dict`): Поля и значения десериализуемого объекта. + client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music. + + Returns: + :obj:`yandex_music.PlaylistRecommendations`: Рекомендации для плейлиста. + """ + if not data: + return None + + data = super(PlaylistRecommendations, cls).de_json(data, client) + from yandex_music import Track + data['tracks'] = Track.de_list(data.get('tracks'), client) + + return cls(client=client, **data)