Merge pull request #196 from MarshalX/issue-185

Плейлист с Алисой и поддержка её шотов
このコミットが含まれているのは:
Il'ya 2020-01-24 19:15:19 +03:00 committed by GitHub
コミット 6b1c12844d
この署名に対応する既知のキーがデータベースに存在しません
GPGキーID: 4AEE18F83AFDEB23
18個のファイルの変更579行の追加12行の削除

ファイルの表示

@ -20,7 +20,8 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py
pip install pipenv
pipenv install --dev --deploy --system
shell: bash

ファイルの表示

@ -20,7 +20,8 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py
pip install pipenv
pipenv install --dev --deploy --system
shell: bash

ファイルの表示

@ -19,7 +19,8 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py
pip install pipenv twine
pipenv install --dev --deploy --system
shell: bash

ファイルの表示

@ -23,7 +23,8 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py
pip install pipenv
pipenv install --dev --deploy --system
shell: bash

ファイルの表示

@ -70,3 +70,6 @@ from .test_value import TestValue
from .test_video import TestVideo
from .test_video_supplement import TestVideoSupplement
from .test_vinyl import TestVinyl
from .test_shot_type import TestShotType
from .test_shot_data import TestShotData
from .test_shot import TestShot

ファイルの表示

@ -6,7 +6,7 @@ from yandex_music import Counts, TrackId, CaseForms, Ratings, Icon, Album, Lyric
Account, Client, TrackShort, Value, DiscreteScale, PlaylistId, MixLink, Link, PassportPhone, User, Promotion, \
PersonalPlaylistsData, RotorSettings, TrackShortOld, PlayContextsData, Status, Settings, StationResult, Enum, \
TrackWithAds, VideoSupplement, ArtistEvent, ChartItem, Event, AlbumEvent, Day, PlayContext, Plus, Title, Label, \
GeneratedPlaylist, Video, Vinyl, SearchResult, BlockEntity, Block, PlaylistAbsence
GeneratedPlaylist, Video, Vinyl, SearchResult, BlockEntity, Block, PlaylistAbsence, ShotType, ShotData, Shot
from . import TestCounts, TestTrackId, TestCaseForms, TestRatings, TestIcon, TestAlbum, TestLyrics, \
TestTrack, TestInvocationInfo, TestPlaylist, TestAutoRenewable, TestStation, TestNormalization, TestMajor, \
TestTrackPosition, TestBest, TestChart, TestPermissions, TestPlus, TestProduct, TestCover, TestPlayCounter, \
@ -15,7 +15,7 @@ from . import TestCounts, TestTrackId, TestCaseForms, TestRatings, TestIcon, Tes
TestUser, TestPassportPhone, TestPromotion, TestTitle, TestPersonalPlaylistsData, TestRotorSettings, \
TestTrackShortOld, TestPager, TestStatus, TestSettings, TestStationResult, TestLabel, TestTrackWithAds, \
TestVideoSupplement, TestEvent, TestDay, TestPlayContext, TestGeneratedPlaylist, TestVideo, TestVinyl, \
TestSearchResult, TestBlockEntity, TestBlock, TestPlaylistAbsence
TestSearchResult, TestBlockEntity, TestBlock, TestPlaylistAbsence, TestShot, TestShotData, TestShotType
@pytest.fixture(scope='session')
@ -438,6 +438,21 @@ def station(id_, icon, restrictions):
return Station(id_, TestStation.name, icon, icon, icon, TestStation.id_for_from, restrictions, restrictions, id_)
@pytest.fixture(scope='session')
def shot_type():
return ShotType(TestShotType.id, TestShotType.title)
@pytest.fixture(scope='session')
def shot_data(shot_type):
return ShotData(TestShotData.cover_uri, TestShotData.mds_url, TestShotData.shot_text, shot_type)
@pytest.fixture(scope='session')
def shot(shot_data):
return Shot(TestShot.order, TestShot.played, shot_data, TestShot.shot_id, TestShot.status)
@pytest.fixture(scope='session')
def chart_item(track, chart):
return ChartItem(track, chart)

53
tests/test_shot.py ノーマルファイル
ファイルの表示

@ -0,0 +1,53 @@
from yandex_music import Shot
class TestShot:
order = 0
played = False
shot_id = '1036797'
status = 'ready'
def test_expected_values(self, shot, shot_data):
assert shot.order == self.order
assert shot.played == self.played
assert shot.shot_id == self.shot_id
assert shot.status == self.status
assert shot.shot_data == shot_data
def test_de_json_none(self, client):
assert Shot.de_json({}, client) is None
def test_de_json_required(self, client, shot_data):
json_dict = {'order': self.order, 'played': self.played, 'shot_id': self.shot_id,
'status': self.status, 'shot_data': shot_data.to_dict()}
shot = Shot.de_json(json_dict, client)
assert shot.order == self.order
assert shot.played == self.played
assert shot.shot_id == self.shot_id
assert shot.status == self.status
assert shot.shot_data == shot_data
def test_de_json_all(self, client, shot_data):
json_dict = {'order': self.order, 'played': self.played, 'shot_id': self.shot_id,
'status': self.status, 'shot_data': shot_data.to_dict()}
shot = Shot.de_json(json_dict, client)
assert shot.order == self.order
assert shot.played == self.played
assert shot.shot_id == self.shot_id
assert shot.status == self.status
assert shot.shot_data == shot_data
def test_equality(self, shot_data):
a = Shot(self.order, self.played, shot_data, self.shot_id, self.status)
b = Shot(self.order, True, shot_data, self.shot_id, self.status)
c = Shot(self.order, self.played, shot_data, '10', self.status)
d = Shot(self.order, self.played, shot_data, self.shot_id, self.status)
assert a != b != c != d
assert hash(a) != hash(b) != hash(c) != hash(d)
assert a is not b is not c is not d
assert a == d
assert hash(a) == hash(d)

49
tests/test_shot_data.py ノーマルファイル
ファイルの表示

@ -0,0 +1,49 @@
from yandex_music import ShotData
class TestShotData:
cover_uri = 'avatars.mds.yandex.net/get-music-misc/49997/img.5da435f1da39b871a74270e2/%%'
mds_url = 'https://storage.mds.yandex.net/get-music/1634376/public/shots/1036797_1574621686'
shot_text = 'Бард - это не просто певец, это поющий поэт.'
def test_expected_values(self, shot_data, shot_type):
assert shot_data.cover_uri == self.cover_uri
assert shot_data.mds_url == self.mds_url
assert shot_data.shot_text == self.shot_text
assert shot_data.shot_type == shot_type
def test_de_json_none(self, client):
assert ShotData.de_json({}, client) is None
def test_de_json_required(self, client, shot_type):
json_dict = {'cover_uri': self.cover_uri, 'mds_url': self.mds_url, 'shot_text': self.shot_text,
'shot_type': shot_type.to_dict()}
shot_data = ShotData.de_json(json_dict, client)
assert shot_data.cover_uri == self.cover_uri
assert shot_data.mds_url == self.mds_url
assert shot_data.shot_text == self.shot_text
assert shot_data.shot_type == shot_type
def test_de_json_all(self, client, shot_type):
json_dict = {'cover_uri': self.cover_uri, 'mds_url': self.mds_url, 'shot_text': self.shot_text,
'shot_type': shot_type.to_dict()}
shot_data = ShotData.de_json(json_dict, client)
assert shot_data.cover_uri == self.cover_uri
assert shot_data.mds_url == self.mds_url
assert shot_data.shot_text == self.shot_text
assert shot_data.shot_type == shot_type
def test_equality(self, shot_type):
a = ShotData(self.cover_uri, self.mds_url, self.shot_text, shot_type)
b = ShotData('', self.mds_url, self.shot_text, shot_type)
c = ShotData(self.cover_uri, '', self.shot_text, shot_type)
d = ShotData(self.cover_uri, self.mds_url, self.shot_text, shot_type)
assert a != b != c != d
assert hash(a) != hash(b) != hash(c) != hash(d)
assert a is not b is not c is not d
assert a == d
assert hash(a) == hash(d)

46
tests/test_shot_event.py ノーマルファイル
ファイルの表示

@ -0,0 +1,46 @@
import pytest
from yandex_music import ShotEvent
@pytest.fixture(scope='class')
def shot_event(shot):
return ShotEvent(TestShotType.event_id, [shot])
class TestShotType:
event_id = '5e25fb2c0cf28e741cb996eb'
def test_expected_values(self, shot_event, shot):
assert shot_event.event_id == self.event_id
assert shot_event.shots == [shot]
def test_de_json_none(self, client):
assert ShotEvent.de_json({}, client) is None
def test_de_json_required(self, client, shot):
json_dict = {'event_id': self.event_id, 'shots': [shot.to_dict()]}
shot_event = ShotEvent.de_json(json_dict, client)
assert shot_event.event_id == self.event_id
assert shot_event.shots == [shot]
def test_de_json_all(self, client, shot):
json_dict = {'event_id': self.event_id, 'shots': [shot.to_dict()]}
shot_event = ShotEvent.de_json(json_dict, client)
assert shot_event.event_id == self.event_id
assert shot_event.shots == [shot]
def test_equality(self, shot):
a = ShotEvent(self.event_id, [shot])
b = ShotEvent('', [shot])
c = ShotEvent(self.event_id, [])
d = ShotEvent(self.event_id, [shot])
assert a != b != c != d
assert hash(a) != hash(b) != hash(c) != hash(d)
assert a is not b is not c is not d
assert a == d
assert hash(a) == hash(d)

41
tests/test_shot_type.py ノーマルファイル
ファイルの表示

@ -0,0 +1,41 @@
from yandex_music import ShotType
class TestShotType:
id = 'alice'
title = 'Шот от Алисы'
def test_expected_values(self, shot_type):
assert shot_type.id_ == self.id
assert shot_type.title == self.title
def test_de_json_none(self, client):
assert ShotType.de_json({}, client) is None
def test_de_json_required(self, client):
json_dict = {'id_': self.id, 'title': self.title}
shot_type = ShotType.de_json(json_dict, client)
assert shot_type.id_ == self.id
assert shot_type.title == self.title
def test_de_json_all(self, client):
json_dict = {'id_': self.id, 'title': self.title}
shot_type = ShotType.de_json(json_dict, client)
assert shot_type.id_ == self.id
assert shot_type.title == self.title
def test_equality(self):
a = ShotType(self.id, self.title)
b = ShotType('', self.title)
c = ShotType(self.id, '')
d = ShotType('', '')
e = ShotType(self.id, self.title)
assert a != b != c != d != e
assert hash(a) != hash(b) != hash(c) != hash(d) != hash(e)
assert a is not b is not c is not d is not e
assert a == e
assert hash(a) == hash(e)

ファイルの表示

@ -35,6 +35,11 @@ from .playlist.playlist_id import PlaylistId
from .playlist.playlist_absence import PlaylistAbsence
from .playlist.playlist import Playlist
from .shot.shot_type import ShotType
from .shot.shot_data import ShotData
from .shot.shot import Shot
from .shot.shot_event import ShotEvent
from .tracks_list import TracksList
from .track.major import Major
from .track.normalization import Normalization
@ -112,6 +117,6 @@ __all__ = ['YandexMusicObject', 'Client', 'Account', 'PassportPhone', 'Invocatio
'Suggestions', 'MixLink', 'BlockEntity', 'Block', 'PlayContextsData', 'TrackId', 'TrackShortOld',
'PersonalPlaylistsData', 'Promotion', 'Landing', 'Chart', 'ChartItem', 'PlayContext', 'Title', 'Genre',
'Icon', 'Images', 'Id', 'Station', 'Dashboard', 'RotorSettings', 'AdParams', 'Restrictions', 'Value', 'Enum',
'DiscreteScale', 'StationResult', 'Sequence', 'StationTracksResult', 'BriefInfo', 'Description', 'PlaylistId',
'Vinyl', 'Supplement', 'Lyrics', 'VideoSupplement', 'ArtistTracks', 'Pager', 'ArtistAlbums',
'PlaylistAbsence']
'DiscreteScale', 'StationResult', 'Sequence', 'StationTracksResult', 'BriefInfo', 'Description',
'PlaylistId', 'Vinyl', 'Supplement', 'Lyrics', 'VideoSupplement', 'ArtistTracks', 'Pager', 'ArtistAlbums',
'PlaylistAbsence', 'Shot', 'ShotEvent', 'ShotType', 'ShotData']

ファイルの表示

@ -6,7 +6,7 @@ from typing import Callable, Union, List, Optional
from yandex_music import YandexMusicObject, Status, Settings, PermissionAlerts, Experiments, Artist, Album, Playlist, \
TracksList, Track, AlbumsLikes, ArtistsLikes, PlaylistsLikes, Feed, PromoCodeStatus, DownloadInfo, Search, \
Suggestions, Landing, Genre, Dashboard, StationResult, StationTracksResult, BriefInfo, Supplement, ArtistTracks, \
ArtistAlbums
ArtistAlbums, ShotEvent
from yandex_music.utils.request import Request
from yandex_music.utils.difference import Difference
from yandex_music.exceptions import InvalidToken, Captcha
@ -1443,6 +1443,57 @@ class Client(YandexMusicObject):
timeout: Union[int, float] = None, *args, **kwargs) -> bool:
return self._dislike_action(track_ids, True, user_id, timeout, *args, **kwargs)
@log
def after_track(self, next_track_id: Union[str, int], context_item: str, prev_track_id: Union[str, int] = None,
context: str = 'playlist', types: str = 'shot', from_: str = 'mobile-landing-origin-default',
timeout: Union[int, float] = None, *args, **kwargs) -> Optional[ShotEvent]:
"""Получение рекламы или шота от Алисы после трека.
При получения шота от Алисы `prev_track_id` можно не указывать.
Если `context = 'playlist'`, то в `context_item` необходимо передать `{OWNER_PLAYLIST}:{ID_PLAYLIST}`.
Плейлист с Алисой имеет владельца с `id = 940441070`.
ID плейлиста можно получить из блоков landing'a. Получить шот чужого плейлиста нельзя.
Известные значения `context`: `playlist`.
Известные значения `types`: `shot`, `ad`.
Args:
prev_track_id (:obj:`str` | :obj:`int`): Уникальный идентификатор предыдущего трека.
next_track_id (:obj:`str` | :obj:`int`): Уникальный идентификатор следующего трека.
context_item (:obj:`str`): Уникальный идентификатор контекста.
context (:obj:`str`, optional): Место, откуда было вызвано получение.
types (:obj:`str`, optional): Тип того, что вернуть после трека.
from_ (:obj:`str`, optional): Место, с которого попали в контекст.
timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания
ответа от сервера вместо указанного при создании пула.
**kwargs (:obj:`dict`, optional): Произвольные аргументы (будут переданы в запрос).
Returns:
:obj:`yandex_music.ShotEvent`: Объекта класса :class:`yandex_music.ShotEvent`
представляющий шоты от Алисы, иначе :obj:`None`.
Raises:
:class:`yandex_music.YandexMusicError`
"""
url = f'{self.base_url}/after-track'
params = {
'from': from_,
'prevTrackId': prev_track_id,
'nextTrackId': next_track_id,
'context': context,
'contextItem': context_item,
'types': types,
}
result = self._request.get(url, params=params, timeout=timeout, *args, **kwargs)
# TODO судя по всему эндпоинт ещё возвращает рекламу после треков для пользователей без подписки.
return ShotEvent.de_json(result.get('shot_event'), self)
# camelCase псевдонимы
#: Псевдоним для :attr:`from_credentials`
@ -1549,3 +1600,5 @@ class Client(YandexMusicObject):
usersDislikesTracksAdd = users_dislikes_tracks_add
#: Псевдоним для :attr:`users_dislikes_tracks_remove`
usersDislikesTracksRemove = users_dislikes_tracks_remove
#: Псевдоним для :attr:`after_track`
afterTrack = after_track

0
yandex_music/shot/__init__.py ノーマルファイル
ファイルの表示

93
yandex_music/shot/shot.py ノーマルファイル
ファイルの表示

@ -0,0 +1,93 @@
from typing import TYPE_CHECKING, Optional, List
if TYPE_CHECKING:
from yandex_music import Client, ShotData
from yandex_music import YandexMusicObject
class Shot(YandexMusicObject):
"""Класс, представляющий шот от Алисы.
Известные значения поля `status`: `ready`.
Attributes:
order (:obj:`int`): Порядковый номер при воспроизведении.
played (:obj:`bool`): Был ли проигран шот.
shot_data (:obj:`yandex_music.ShotData`): Объект класса :class:`yandex_music.ShotData` представляющий
основную информацию о шоте.
shot_id (:obj:`str`): Уникальный идентификатор шота.
status (:obj:`str`): Статус шота.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Args:
order (:obj:`int`): Порядковый номер при воспроизведении.
played (:obj:`bool`): Был ли проигран шот.
shot_data (:obj:`yandex_music.ShotData`): Объект класса :class:`yandex_music.ShotData` представляющий
основную информацию о шоте.
shot_id (:obj:`str`): Уникальный идентификатор шота.
status (:obj:`str`): Статус шота.
client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент
Yandex Music.
**kwargs: Произвольные ключевые аргументы полученные от API.
"""
def __init__(self,
order: int,
played: bool,
shot_data: 'ShotData',
shot_id: str,
status: str,
client: Optional['Client'] = None,
**kwargs):
self.order = order
self.played = played
self.shot_data = shot_data
self.shot_id = shot_id
self.status = status
self.client = client
self._id_attrs = (self.order, self.played, self.shot_data, self.shot_id, self.status)
@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['Shot']:
"""Десериализация объекта.
Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Returns:
:obj:`yandex_music.Shot`: Объект класса :class:`yandex_music.Shot`.
"""
if not data:
return None
data = super(Shot, cls).de_json(data, client)
from yandex_music import ShotData
data['shot_data'] = ShotData.de_json(data.get('shot_data'), client)
return cls(client=client, **data)
@classmethod
def de_list(cls, data: dict, client: 'Client') -> List['Shot']:
"""Десериализация списка объектов.
Args:
data (:obj:`list`): Список словарей с полями и значениями десериализуемого объекта.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Returns:
:obj:`list` из :obj:`yandex_music.Shot`: Список объектов класса :class:`yandex_music.Shot`.
"""
if not data:
return []
shots = list()
for shot in data:
shots.append(cls.de_json(shot, client))
return shots

92
yandex_music/shot/shot_data.py ノーマルファイル
ファイルの表示

@ -0,0 +1,92 @@
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from yandex_music import Client, ShotType
from yandex_music import YandexMusicObject
class ShotData(YandexMusicObject):
"""Класс, представляющий основную информацию о шоте.
Attributes:
cover_uri (:obj:`str`): Ссылка на обложку шота (иконка Алисы).
mds_url (:obj:`str`): Ссылка на аудиоверсию шота в озвучке от Алисы.
shot_text (:obj:`str`): Текстовая версия шота.
shot_type (:obj:`yandex_music.ShotType`): Объект класса :class:`yandex_music.ShotType` представляющий тип
шота.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Args:
cover_uri (:obj:`str`): Ссылка на обложку шота (иконка Алисы).
mds_url (:obj:`str`): Ссылка на аудиоверсию шота в озвучке от Алисы.
shot_text (:obj:`str`): Текстовая версия шота.
shot_type (:obj:`yandex_music.ShotType`): Объект класса :class:`yandex_music.ShotType` представляющий тип
шота.
client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент
Yandex Music.
**kwargs: Произвольные ключевые аргументы полученные от API.
"""
def __init__(self,
cover_uri: str,
mds_url: str,
shot_text: str,
shot_type: 'ShotType',
client: Optional['Client'] = None,
**kwargs):
self.cover_uri = cover_uri
self.mds_url = mds_url
self.shot_text = shot_text
self.shot_type = shot_type
self.client = client
self._id_attrs = (self.cover_uri, self.mds_url, self.shot_text, self.shot_type)
def download_cover(self, filename: str, size: str = '200x200') -> None:
"""Загрузка обложки.
Args:
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
size (:obj:`str`, optional): Размер обложки.
"""
self.client.request.download(f'https://{self.cover_uri.replace("%%", size)}', filename)
def download_mds(self, filename: str) -> None:
"""Загрузка аудиоверсии шота.
Args:
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
"""
self.client.request.download(self.mds_url, filename)
@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['ShotData']:
"""Десериализация объекта.
Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Returns:
:obj:`yandex_music.ShotData`: Объект класса :class:`yandex_music.ShotData`.
"""
if not data:
return None
data = super(ShotData, cls).de_json(data, client)
from yandex_music import ShotType
data['shot_type'] = ShotType.de_json(data.get('shot_type'), client)
return cls(client=client, **data)
# camelCase псевдонимы
#: Псевдоним для :attr:`download_cover`
downloadCover = download_cover
#: Псевдоним для :attr:`download_mds`
downloadMds = download_mds

58
yandex_music/shot/shot_event.py ノーマルファイル
ファイルの表示

@ -0,0 +1,58 @@
from typing import TYPE_CHECKING, Optional, List
if TYPE_CHECKING:
from yandex_music import Client, Shot
from yandex_music import YandexMusicObject
class ShotEvent(YandexMusicObject):
"""Класс, представляющий событие-шот перед началом следующего трека.
Attributes:
event_id (:obj:`str`): Уникальный идентификатор события.
shots (:obj:`list` из :obj:`yandex_music.Shot`): Список объектов класса :class:`yandex_music.Shot`
представляющие шоты от Алисы.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Args:
event_id (:obj:`str`): Уникальный идентификатор события.
shots (:obj:`list` из :obj:`yandex_music.Shot`): Список объектов класса :class:`yandex_music.Shot`
представляющие шоты от Алисы.
client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент
Yandex Music.
**kwargs: Произвольные ключевые аргументы полученные от API.
"""
def __init__(self,
event_id: str,
shots: List['Shot'],
client: Optional['Client'] = None,
**kwargs):
self.event_id = event_id
self.shots = shots
self.client = client
self._id_attrs = (self.event_id, self.shots)
@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['ShotEvent']:
"""Десериализация объекта.
Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Returns:
:obj:`yandex_music.ShotEvent`: Объект класса :class:`yandex_music.ShotEvent`.
"""
if not data:
return None
data = super(ShotEvent, cls).de_json(data, client)
from yandex_music import Shot
data['shots'] = Shot.de_list(data.get('shots'), client)
return cls(client=client, **data)

54
yandex_music/shot/shot_type.py ノーマルファイル
ファイルの表示

@ -0,0 +1,54 @@
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from yandex_music import Client
from yandex_music import YandexMusicObject
class ShotType(YandexMusicObject):
"""Класс, представляющий тип шота от Алисы.
Attributes:
id_ (:obj:`str`): Уникальный идентификатор типа.
title (:obj:`str`): Заголовок шота.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Args:
id_ (:obj:`str`): Уникальный идентификатор типа.
title (:obj:`str`): Заголовок шота.
client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент
Yandex Music.
**kwargs: Произвольные ключевые аргументы полученные от API.
"""
def __init__(self,
id_: str,
title: str,
client: Optional['Client'] = None,
**kwargs):
self.id_ = id_
self.title = title
self.client = client
self._id_attrs = (self.id_, self.title)
@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['ShotType']:
"""Десериализация объекта.
Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Returns:
:obj:`yandex_music.ShotType`: Объект класса :class:`yandex_music.ShotType`.
"""
if not data:
return None
data = super(ShotType, cls).de_json(data, client)
return cls(client=client, **data)

ファイルの表示

@ -6,6 +6,7 @@ import builtins
# Отправка вообще application/x-www-form-urlencoded, а не JSON'a
# https://github.com/psf/requests/blob/master/requests/models.py#L508
import json
from typing import Optional
import requests
@ -16,7 +17,7 @@ from yandex_music.exceptions import Unauthorized, BadRequest, NetworkError, Yand
USER_AGENT = 'Yandex-Music-API'
HEADERS = {
'X-Yandex-Music-Client': 'WindowsPhone/3.20',
'X-Yandex-Music-Client': 'YandexMusicAndroid/23020055',
}
reserved_names = [name.lower() for name in dir(builtins)] + ['client']
@ -76,7 +77,7 @@ class Request:
return cleaned_object
def _parse(self, json_data) -> Response:
def _parse(self, json_data) -> Optional[Response]:
try:
decoded_s = json_data.decode('utf-8')
data = json.loads(decoded_s, object_hook=Request._object_hook)