Получение чартов (#328)

Добавлен метод получения чарта - `chart()`. Добавлены новые классы: `ChartInfo`, `ChartInfoMenu`, `ChartInfoMenuItem`

Co-authored-by: Angel <angel777da@gmail.com>
このコミットが含まれているのは:
Il'ya 2020-05-14 21:02:34 +03:00 committed by GitHub
コミット f4edd6348d
この署名に対応する既知のキーがデータベースに存在しません
GPGキーID: 4AEE18F83AFDEB23
14個のファイルの変更415行の追加4行の削除

ファイルの表示

@ -0,0 +1,7 @@
yandex_music.ChartInfo
======================
.. autoclass:: yandex_music.ChartInfo
:members:
:undoc-members:
:show-inheritance:

ファイルの表示

@ -0,0 +1,7 @@
yandex_music.ChartInfoMenu
==========================
.. autoclass:: yandex_music.ChartInfoMenu
:members:
:undoc-members:
:show-inheritance:

ファイルの表示

@ -0,0 +1,7 @@
yandex_music.ChartInfoMenuItem
==============================
.. autoclass:: yandex_music.ChartInfoMenuItem
:members:
:undoc-members:
:show-inheritance:

ファイルの表示

@ -15,3 +15,6 @@
yandex_music.landing.personal_playlists_data
yandex_music.landing.promotion
yandex_music.landing.block
yandex_music.landing.chart_info
yandex_music.landing.chart_info_menu
yandex_music.landing.chart_info_menu_item

ファイルの表示

@ -17,6 +17,9 @@ from .test_description import TestDescription
from .test_discrete_scale import TestDiscreteScale
from .test_enum import TestEnum
from .test_event import TestEvent
from .test_chart_info_menu_item import TestChartInfoMenuItem
from .test_chart_info_menu import TestChartInfoMenu
from .test_chart_info import TestChartInfo
from .test_generated_playlist import TestGeneratedPlaylist
from .test_genre import TestGenre
from .test_icon import TestIcon

ファイルの表示

@ -7,7 +7,7 @@ from yandex_music import Counts, TrackId, CaseForms, Ratings, Icon, Album, Lyric
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, ShotType, ShotData, Shot, \
RenewableRemainder
RenewableRemainder, ChartInfoMenuItem, ChartInfoMenu, ChartInfo
from . import TestCounts, TestTrackId, TestCaseForms, TestRatings, TestIcon, TestAlbum, TestLyrics, \
TestTrack, TestInvocationInfo, TestPlaylist, TestAutoRenewable, TestStation, TestNormalization, TestMajor, \
TestTrackPosition, TestBest, TestChart, TestPermissions, TestPlus, TestProduct, TestCover, TestPlayCounter, \
@ -17,7 +17,7 @@ from . import TestCounts, TestTrackId, TestCaseForms, TestRatings, TestIcon, Tes
TestTrackShortOld, TestPager, TestStatus, TestSettings, TestStationResult, TestLabel, TestTrackWithAds, \
TestVideoSupplement, TestEvent, TestDay, TestPlayContext, TestGeneratedPlaylist, TestVideo, TestVinyl, \
TestSearchResult, TestBlockEntity, TestBlock, TestPlaylistAbsence, TestShot, TestShotData, TestShotType, \
TestRenewableRemainder
TestRenewableRemainder, TestChartInfoMenuItem, TestChartInfo
@pytest.fixture(scope='session')
@ -423,6 +423,22 @@ def event(track, artist_event, album_event):
[album_event], TestEvent.message, TestEvent.device, TestEvent.tracks_count)
@pytest.fixture(scope='session')
def chart_info_menu_item():
return ChartInfoMenuItem(TestChartInfoMenuItem.title, TestChartInfoMenuItem.url, TestChartInfoMenuItem.selected)
@pytest.fixture(scope='session')
def chart_info_menu(chart_info_menu_item):
return ChartInfoMenu([chart_info_menu_item])
@pytest.fixture(scope='session')
def chart_info(playlist, chart_info_menu):
return ChartInfo(TestChartInfo.id, TestChartInfo.type, TestChartInfo.type_for_from, TestChartInfo.title,
chart_info_menu, playlist, TestChartInfo.chart_description)
@pytest.fixture(scope='session')
def track_id():
return TrackId(TestTrackId.id, TestTrackId.album_id)

74
tests/test_chart_info.py ノーマルファイル
ファイルの表示

@ -0,0 +1,74 @@
from yandex_music import ChartInfo
class TestChartInfo:
id = 'KpXst7X4'
type = 'chart'
type_for_from = 'chart'
title = 'Треки, популярные на Яндекс.Музыке прямо сейчас'
chart_description = 'Слушателей за день'
def test_expected_values(self, chart_info, chart_info_menu, playlist):
assert chart_info.id == self.id
assert chart_info.type == self.type
assert chart_info.type_for_from == self.type_for_from
assert chart_info.title == self.title
assert chart_info.chart == playlist
assert chart_info.menu == chart_info_menu
def test_de_json_none(self, client):
assert ChartInfo.de_json({}, client) is None
def test_de_json_required(self, playlist, chart_info_menu, client):
json_dict = {
'id_': self.id,
'type_': self.type,
'type_for_from': self.type_for_from,
'title': self.title,
'chart_description': self.chart_description,
'menu': chart_info_menu.to_dict(),
'chart': playlist.to_dict()
}
chart_info = ChartInfo.de_json(json_dict, client)
assert chart_info.id == self.id
assert chart_info.type == self.type
assert chart_info.type_for_from == self.type_for_from
assert chart_info.title == self.title
assert chart_info.chart_description == self.chart_description
def test_de_json_all(self, client, playlist, chart_info_menu):
json_dict = {
'id_': self.id,
'type_': self.type,
'type_for_from': self.type_for_from,
'title': self.title,
'chart_description': self.chart_description,
'menu': chart_info_menu.to_dict(),
'chart': playlist.to_dict()
}
chart_info = ChartInfo.de_json(json_dict, client)
assert chart_info.id == self.id
assert chart_info.type == self.type
assert chart_info.type_for_from == self.type_for_from
assert chart_info.title == self.title
assert chart_info.chart_description == self.chart_description
assert chart_info.menu == chart_info_menu
assert chart_info.chart == playlist
def test_equality(self, playlist, chart_info_menu):
a = ChartInfo(self.id, self.type, self.type_for_from, self.title, self.chart_description, chart_info_menu,
playlist)
b = ChartInfo("no_id", self.type, self.type_for_from, self.title, self.chart_description, chart_info_menu,
playlist)
c = ChartInfo(self.id, self.type, self.type_for_from, self.title, self.chart_description, chart_info_menu,
playlist)
assert a != b
assert hash(a) != hash(b)
assert a is not b
assert a == c

29
tests/test_chart_info_menu.py ノーマルファイル
ファイルの表示

@ -0,0 +1,29 @@
from yandex_music import ChartInfoMenu, ChartInfoMenuItem
class TestChartInfoMenu:
def test_expected_values(self, chart_info_menu, chart_info_menu_item):
assert chart_info_menu.items == [chart_info_menu_item]
def test_de_json_none(self, client):
assert ChartInfoMenu.de_json({}, client) is None
def test_de_json_required(self, chart_info_menu_item, client):
json_dict = {
'items': [chart_info_menu_item.to_dict()],
}
chart_info_menu = ChartInfoMenu.de_json(json_dict, client)
assert chart_info_menu.items == [chart_info_menu_item]
def test_equality(self, chart_info_menu_item):
a = ChartInfoMenu([chart_info_menu_item])
b = ChartInfoMenu([ChartInfoMenuItem("tt", "no_url")])
c = ChartInfoMenu([chart_info_menu_item])
assert a != b
assert hash(a) != hash(b)
assert a is not b
assert a == c

50
tests/test_chart_info_menu_item.py ノーマルファイル
ファイルの表示

@ -0,0 +1,50 @@
from yandex_music import ChartInfoMenuItem
class TestChartInfoMenuItem:
title = 'Россия'
url = 'russia'
selected = True
def test_expected_values(self, chart_info_menu_item):
assert chart_info_menu_item.title == self.title
assert chart_info_menu_item.url == self.url
assert chart_info_menu_item.selected == self.selected
def test_de_json_none(self, client):
assert ChartInfoMenuItem.de_json({}, client) is None
def test_de_json_required(self, client):
json_dict = {
'title': self.title,
'url': self.url,
}
chart_info_menu_item = ChartInfoMenuItem.de_json(json_dict, client)
assert chart_info_menu_item.title == self.title
assert chart_info_menu_item.url == self.url
def test_de_json_all(self, client):
json_dict = {
'title': self.title,
'url': self.url,
'selected': self.selected,
}
chart_info_menu_item = ChartInfoMenuItem.de_json(json_dict, client)
assert chart_info_menu_item.title == self.title
assert chart_info_menu_item.url == self.url
assert chart_info_menu_item.selected == self.selected
def test_equality(self):
a = ChartInfoMenuItem(self.title, self.url, self.selected)
b = ChartInfoMenuItem(self.title, "no_url", self.selected)
c = ChartInfoMenuItem(self.title, self.url, self.selected)
assert a != b
assert hash(a) != hash(b)
assert a is not b
assert a == c

ファイルの表示

@ -73,6 +73,9 @@ from .landing.promotion import Promotion
from .landing.block_entity import BlockEntity
from .landing.landing import Landing
from .landing.block import Block
from .landing.chart_info_menu_item import ChartInfoMenuItem
from .landing.chart_info_menu import ChartInfoMenu
from .landing.chart_info import ChartInfo
from .landing.track_id import TrackId
from .landing.chart import Chart
from .landing.play_contexts_data import PlayContextsData
@ -119,4 +122,5 @@ __all__ = ['YandexMusicObject', 'Client', 'Account', 'PassportPhone', 'Invocatio
'Dashboard', 'RotorSettings', 'AdParams', 'Restrictions', 'Value', 'Enum', 'DiscreteScale', 'StationResult',
'Sequence', 'StationTracksResult', 'BriefInfo', 'Description', 'PlaylistId', 'Vinyl', 'Supplement', 'Lyrics',
'VideoSupplement', 'ArtistTracks', 'Pager', 'ArtistAlbums', 'PlaylistAbsence', 'Shot', 'ShotEvent',
'ShotType', 'ShotData', 'SimilarTracks', 'UserSettings', 'RenewableRemainder']
'ShotType', 'ShotData', 'SimilarTracks', 'UserSettings', 'RenewableRemainder', 'ChartInfo', 'ChartInfoMenu',
'ChartInfoMenuItem']

ファイルの表示

@ -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, SimilarTracks, \
StationResult, StationTracksResult, Status, Suggestions, Supplement, Track, TracksList, UserSettings, \
YandexMusicObject
YandexMusicObject, ChartInfo
from yandex_music.exceptions import Captcha, InvalidToken
from yandex_music.utils.difference import Difference
from yandex_music.utils.request import Request
@ -439,6 +439,36 @@ class Client(YandexMusicObject):
return Landing.de_json(result, self)
@log
def chart(self, chart_option: str = '', timeout: Union[int, float] = None, *args, **kwargs) -> Optional[ChartInfo]:
"""Получение чарта.
Note:
`chart_option` - это постфикс к запросу из поля `menu` чарта.
Например, на сайте можно выбрать глобальный (world) чарт или российский (russia).
Args:
chart_option (:obj:`str` optional): Параметры чарта.
timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания
ответа от сервера вместо указанного при создании пула.
**kwargs (:obj:`dict`, optional): Произвольные аргументы (будут переданы в запрос).
Returns:
:obj:`yandex_music.ChartInfo`: Чарт.
Raises:
:class:`yandex_music.exceptions.YandexMusicError`: Базовое исключение библиотеки.
"""
url = f'{self.base_url}/landing3/chart'
if chart_option:
url = f'{url}/{chart_option}'
result = self._request.get(url, timeout=timeout, *args, **kwargs)
return ChartInfo.de_json(result, self)
@log
def genres(self, timeout: Union[int, float] = None, *args, **kwargs) -> List[Genre]:
"""Получение жанров музыки.

72
yandex_music/landing/chart_info.py ノーマルファイル
ファイルの表示

@ -0,0 +1,72 @@
from typing import TYPE_CHECKING, Optional
from yandex_music import YandexMusicObject, Playlist, ChartInfoMenu
if TYPE_CHECKING:
from yandex_music import Client
class ChartInfo(YandexMusicObject):
"""Класс, представляющий чарт.
Attributes:
id (:obj:`str`): Уникальный идентификатор блока.
type (:obj:`str`): Тип блока.
type_for_from (:obj:`str`): Откуда получен блок (как к нему пришли).
title (:obj:`str`): Заголовок.
menu (:obj:`yandex_music.ChartInfoMenu` | :obj:`None`): Меню TODO.
chart (:obj:`yandex_music.Playlist` | :obj:`None`): Плейлист.
chart_description (:obj:`str`): Описание.
client (:obj:`yandex_music.Client`): Клиент Yandex Music.
Args:
id_ (:obj:`str`): Уникальный идентификатор блока.
type_ (:obj:`str`): Тип блока.
type_for_from (:obj:`str`): Откуда получен блок (как к нему пришли).
title (:obj:`str`): Заголовок.
menu (:obj:`yandex_music.ChartInfoMenu`, optional): Меню TODO.
chart (:obj:`yandex_music.Playlist`, optional): Плейлист.
chart_description (:obj:`str`, optional): Описание.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
"""
def __init__(self,
id_: str,
type_: str,
type_for_from: str,
title: str,
menu: Optional['ChartInfoMenu'],
chart: Optional['Playlist'],
chart_description: Optional[str] = None,
client: Optional['Client'] = None):
self.id = id_
self.type = type_
self.type_for_from = type_for_from
self.title = title
self.menu = menu
self.chart = chart
self.chart_description = chart_description
self.client = client
self._id_attrs = (id_,)
@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['ChartInfo']:
"""Десериализация объекта.
Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
Returns:
:obj:`yandex_music.ChartInfo`: Чарт.
"""
if not data:
return None
data = super(ChartInfo, cls).de_json(data, client)
data['chart'] = Playlist.de_json(data.get('chart'), client)
data['menu'] = ChartInfoMenu.de_json(data.get('menu'), client)
return cls(client=client, **data)

44
yandex_music/landing/chart_info_menu.py ノーマルファイル
ファイルの表示

@ -0,0 +1,44 @@
from typing import TYPE_CHECKING, List, Optional
from yandex_music import YandexMusicObject, ChartInfoMenuItem
if TYPE_CHECKING:
from yandex_music import Client
class ChartInfoMenu(YandexMusicObject):
"""Класс, представляющий меню чарта.
Attributes:
items (:obj:`list` из :obj:`yandex_music.ChartInfoMenuItem`): Список элементов меню.
client (:obj:`yandex_music.Client`): Клиент Yandex Music.
Args:
items (:obj:`list` из :obj:`yandex_music.ChartInfoMenuItem`): Список элементов меню.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
"""
def __init__(self, items: List['ChartInfoMenuItem'], client: Optional['Client'] = None):
self.items = items
self.client = client
self._id_attrs = (self.items, )
@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['ChartInfoMenu']:
"""Десериализация объекта.
Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
Returns:
:obj:`yandex_music.ChartInfoMenu`: Меню чарта.
"""
if not data:
return None
data = super(ChartInfoMenu, cls).de_json(data, client)
data['items'] = ChartInfoMenuItem.de_list(data.get('items'), client)
return cls(client=client, **data)

65
yandex_music/landing/chart_info_menu_item.py ノーマルファイル
ファイルの表示

@ -0,0 +1,65 @@
from typing import TYPE_CHECKING, Optional, List
from yandex_music import YandexMusicObject
if TYPE_CHECKING:
from yandex_music import Client
class ChartInfoMenuItem(YandexMusicObject):
"""Класс, представляющий элемент меню чарта.
Attributes:
title (:obj:`str`): Заголовок.
url (:obj:`str`): Постфикс для запроса чарта.
selected (:obj:`bool` | :obj:`None`): Текущий ли элемент.
client (:obj:`yandex_music.Client`): Клиент Yandex Music.
Args:
title (:obj:`str`): Заголовок.
url (:obj:`str`): Постфикс для запроса чарта.
selected (:obj:`bool`, optional): Текущий ли элемент.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
"""
def __init__(self, title: str, url: str, selected: Optional[bool] = None, client: Optional['Client'] = None):
self.title = title
self.url = url
self.selected = selected
self.client = client
self._id_attrs = (url, selected)
@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['ChartInfoMenuItem']:
"""Десериализация объекта.
Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
Returns:
:obj:`yandex_music.ChartInfoMenuItem`: Элемент меню.
"""
if not data:
return None
data = super(ChartInfoMenuItem, cls).de_json(data, client)
return cls(client=client, **data)
@classmethod
def de_list(cls, data: list, client: 'Client') -> List['ChartInfoMenuItem']:
"""Десериализация списка объектов.
Args:
data (:obj:`list`): Список словарей с полями и значениями десериализуемого объекта.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
Returns:
:obj:`list` из :obj:`yandex_music.ChartInfoMenuItem`: Список элементов меню чарта.
"""
if not data:
return []
return [cls.de_json(item, client) for item in data]