From 0240eb6cb99877eefe30f092d802c3f685554182 Mon Sep 17 00:00:00 2001 From: Il`ya Semyonov Date: Sat, 6 Jun 2020 17:06:23 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81=20MetaData.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=BB=D0=B5=20error=20=D0=BA?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D1=81=D1=83=20Artist.=20=D0=9A=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81=20User=20=D1=80=D0=B0=D1=81=D1=88=D0=B8=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=BA=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8F=20use?= =?UTF-8?q?r=5Finfo=20=D0=B8=D0=B7=20Track=20(=D0=BF=D0=BE=D0=BB=D1=8F=20f?= =?UTF-8?q?ull=5Fname,=20display=5Fname).=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8F=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=83?= =?UTF-8?q?=20Track:=20substituted,=20matched=5Ftrack,=20can=5Fpublish,=20?= =?UTF-8?q?state,=20desired=5Fvisibility,=20filename,=20user=5Finfo,=20met?= =?UTF-8?q?a=5Fdata.=20=D0=9D=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0=20Cover:=20copyri?= =?UTF-8?q?ght=5Fname,=20copyright=5Fcline.=20=D0=94=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=BB=D0=B5=20dir?= =?UTF-8?q?ect=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=83=20DownloadInfo.=20?= =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=83=D0=BF=D1=80=D0=B5=D0=B6=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=20=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BF=D0=BE=D0=BB=D1=8F=D1=85=20=D0=B2=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B5=D0=BD=D1=8B=20=D0=BF=D0=BE=20=D1=83=D0=BC?= =?UTF-8?q?=D0=BE=D0=BB=D1=87=D0=B0=D0=BD=D0=B8=D1=8E.=20=D0=94=D0=BE?= =?UTF-8?q?=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B8=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=BA=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=BC=20=D0=BF=D0=BE=D0=BB=D1=8F.=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/yandex_music.track.meta_data.rst | 7 +++ docs/source/yandex_music.track.rst | 1 + tests/__init__.py | 1 + tests/conftest.py | 48 +++++++++++------ tests/test_artist.py | 5 +- tests/test_cover.py | 9 +++- tests/test_download_info.py | 16 +++--- tests/test_meta_data.py | 42 +++++++++++++++ tests/test_track.py | 31 +++++++++-- tests/test_user.py | 20 +++---- yandex_music/__init__.py | 3 +- yandex_music/artist/artist.py | 4 ++ yandex_music/client.py | 6 +-- yandex_music/cover.py | 8 +++ yandex_music/download_info.py | 4 ++ yandex_music/playlist/user.py | 28 +++++++--- yandex_music/track/meta_data.py | 57 ++++++++++++++++++++ yandex_music/track/track.py | 49 ++++++++++++++--- 18 files changed, 285 insertions(+), 54 deletions(-) create mode 100644 docs/source/yandex_music.track.meta_data.rst create mode 100644 tests/test_meta_data.py create mode 100644 yandex_music/track/meta_data.py diff --git a/docs/source/yandex_music.track.meta_data.rst b/docs/source/yandex_music.track.meta_data.rst new file mode 100644 index 0000000..8c426f4 --- /dev/null +++ b/docs/source/yandex_music.track.meta_data.rst @@ -0,0 +1,7 @@ +yandex_music.MetaData +===================== + +.. autoclass:: yandex_music.MetaData + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/yandex_music.track.rst b/docs/source/yandex_music.track.rst index 0426fa6..a34177d 100644 --- a/docs/source/yandex_music.track.rst +++ b/docs/source/yandex_music.track.rst @@ -6,3 +6,4 @@ yandex_music.track.normalization yandex_music.track.major yandex_music.track.track + yandex_music.track.meta_data diff --git a/tests/__init__.py b/tests/__init__.py index b3bb0b7..75e0782 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -78,3 +78,4 @@ from .test_shot_data import TestShotData from .test_shot import TestShot from .test_renewable_remainder import TestRenewableRemainder from .test_tag import TestTag +from .test_meta_data import TestMetaData diff --git a/tests/conftest.py b/tests/conftest.py index cd9e676..ee18e5c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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, ChartInfoMenuItem, ChartInfoMenu, ChartInfo, Tag + RenewableRemainder, ChartInfoMenuItem, ChartInfoMenu, ChartInfo, Tag, MetaData 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,20 +17,20 @@ 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, TestChartInfoMenuItem, TestChartInfo, TestTag + TestRenewableRemainder, TestChartInfoMenuItem, TestChartInfo, TestTag, TestMetaData @pytest.fixture(scope='session') def artist_factory(cover, counts, ratings, link, description): class ArtistFactory: def get(self, popular_tracks): - return Artist(TestArtist.id, TestArtist.reason, TestArtist.name, cover, TestArtist.various, - TestArtist.composer, TestArtist.genres, TestArtist.og_image, TestArtist.op_image, - TestArtist.no_pictures_from_search, counts, TestArtist.available, ratings, [link], - TestArtist.tickets_available, TestArtist.likes_count, popular_tracks, TestArtist.regions, - TestArtist.decomposed, TestArtist.full_names, description, TestArtist.countries, - TestArtist.en_wikipedia_link, TestArtist.db_aliases, TestArtist.aliases, TestArtist.init_date, - TestArtist.end_date) + return Artist(TestArtist.id, TestArtist.error, TestArtist.reason, TestArtist.name, cover, + TestArtist.various, TestArtist.composer, TestArtist.genres, TestArtist.og_image, + TestArtist.op_image, TestArtist.no_pictures_from_search, counts, TestArtist.available, + ratings, [link], TestArtist.tickets_available, TestArtist.likes_count, popular_tracks, + TestArtist.regions, TestArtist.decomposed, TestArtist.full_names, description, + TestArtist.countries, TestArtist.en_wikipedia_link, TestArtist.db_aliases, TestArtist.aliases, + TestArtist.init_date, TestArtist.end_date) return ArtistFactory() @@ -46,14 +46,16 @@ def artist_without_tracks(artist_factory): @pytest.fixture(scope='session') -def track_factory(major, normalization): +def track_factory(major, normalization, user, meta_data): class TrackFactory: - def get(self, artists, albums): + def get(self, artists, albums, track_without_nested_tracks=None): return Track(TestTrack.id, TestTrack.title, TestTrack.available, artists, albums, TestTrack.available_for_premium_users, TestTrack.lyrics_available, TestTrack.best, TestTrack.real_id, TestTrack.og_image, TestTrack.type, TestTrack.cover_uri, major, - TestTrack.duration_ms, TestTrack.storage_dir, TestTrack.file_size, normalization, - TestTrack.error, TestTrack.regions, TestTrack.available_as_rbt, TestTrack.content_warning, + TestTrack.duration_ms, TestTrack.storage_dir, TestTrack.file_size, track_without_nested_tracks, + track_without_nested_tracks, normalization, TestTrack.error, TestTrack.can_publish, + TestTrack.state, TestTrack.desired_visibility, TestTrack.filename, user, meta_data, + TestTrack.regions, TestTrack.available_as_rbt, TestTrack.content_warning, TestTrack.explicit, TestTrack.preview_duration_ms, TestTrack.available_full_without_permission, TestTrack.version, TestTrack.remember_position) @@ -61,8 +63,8 @@ def track_factory(major, normalization): @pytest.fixture(scope='session') -def track(track_factory, artist, album): - return track_factory.get([artist], [album]) +def track(track_factory, artist, album, track_without_nested_tracks): + return track_factory.get([artist], [album], track_without_nested_tracks) @pytest.fixture(scope='session') @@ -80,6 +82,11 @@ def track_without_artists_and_albums(track_factory): return track_factory.get([], []) +@pytest.fixture(scope='session') +def track_without_nested_tracks(artist, album, track_factory): + return track_factory.get([artist], [album]) + + @pytest.fixture(scope='session') def album_factory(label, track_position): class AlbumFactory: @@ -207,7 +214,13 @@ def icon(): @pytest.fixture(scope='session') def cover(): return Cover(TestCover.type, TestCover.uri, TestCover.items_uri, TestCover.dir, TestCover.version, - TestCover.custom, TestCover.is_custom, TestCover.prefix, TestCover.error) + TestCover.custom, TestCover.is_custom, TestCover.copyright_name, TestCover.copyright_cline, + TestCover.prefix, TestCover.error) + + +@pytest.fixture(scope='session') +def meta_data(): + return MetaData(TestMetaData.album, TestMetaData.volume, TestMetaData.year) @pytest.fixture(scope='session') @@ -361,7 +374,8 @@ def renewable_remainder(): @pytest.fixture(scope='session') def user(): - return User(TestUser.uid, TestUser.login, TestUser.name, TestUser.sex, TestUser.verified) + return User(TestUser.uid, TestUser.login, TestUser.name, TestUser.display_name, + TestUser.full_name, TestUser.sex, TestUser.verified) @pytest.fixture(scope='session') diff --git a/tests/test_artist.py b/tests/test_artist.py index 9733fc9..f3595c1 100644 --- a/tests/test_artist.py +++ b/tests/test_artist.py @@ -3,6 +3,7 @@ from yandex_music import Artist class TestArtist: id = 10987 + error = 'not-found' reason = 'not-found' name = 'Elvis Presley' various = False @@ -26,6 +27,7 @@ class TestArtist: def test_expected_values(self, artist, cover, counts, ratings, link, track_without_artists_and_albums, description): assert artist.id == self.id + assert artist.error == self.error assert artist.reason == self.reason assert artist.name == self.name assert artist.various == self.various @@ -66,7 +68,7 @@ class TestArtist: assert artist.id == self.id def test_de_json_all(self, client, cover, counts, ratings, link, track_without_artists, description): - json_dict = {'id_': self.id, 'reason': self.reason, 'name': self.name, + json_dict = {'id_': self.id, 'reason': self.reason, 'error': self.error, 'name': self.name, 'various': self.various, 'composer': self.composer, 'cover': cover.to_dict(), 'genres': self.genres, 'op_image': self.op_image, 'og_image': self.og_image, 'no_pictures_from_search': self.no_pictures_from_search, 'counts': counts.to_dict(), @@ -80,6 +82,7 @@ class TestArtist: artist = Artist.de_json(json_dict, client) assert artist.id == self.id + assert artist.error == self.error assert artist.reason == self.reason assert artist.name == self.name assert artist.various == self.various diff --git a/tests/test_cover.py b/tests/test_cover.py index d80e308..631d4e3 100644 --- a/tests/test_cover.py +++ b/tests/test_cover.py @@ -10,6 +10,8 @@ class TestCover: custom = True is_custom = True prefix = None + copyright_name = 'ТАСС' + copyright_cline = 'imago stock&people' error = None def test_expected_values(self, cover): @@ -20,6 +22,8 @@ class TestCover: assert cover.version == self.version assert cover.custom == self.custom assert cover.is_custom == self.is_custom + assert cover.copyright_name == self.copyright_name + assert cover.copyright_cline == self.copyright_cline assert cover.prefix == self.prefix assert cover.error == self.error @@ -36,7 +40,8 @@ class TestCover: def test_de_json_all(self, client): json_dict = {'type_': self.type, 'uri': self.uri, 'items_uri': self.items_uri, 'dir_': self.dir, 'version': self.version, 'custom': self.custom, 'is_custom': self.is_custom, 'prefix': self.prefix, - 'error': self.error} + 'error': self.error, 'copyright_name': self.copyright_name, + 'copyright_cline': self.copyright_cline} cover = Cover.de_json(json_dict, client) assert cover.type == self.type @@ -46,6 +51,8 @@ class TestCover: assert cover.version == self.version assert cover.custom == self.custom assert cover.is_custom == self.is_custom + assert cover.copyright_name == self.copyright_name + assert cover.copyright_cline == self.copyright_cline assert cover.prefix == self.prefix assert cover.error == self.error diff --git a/tests/test_download_info.py b/tests/test_download_info.py index 200023b..6c36ff1 100644 --- a/tests/test_download_info.py +++ b/tests/test_download_info.py @@ -6,7 +6,7 @@ from yandex_music import DownloadInfo @pytest.fixture(scope='class') def download_info(): return DownloadInfo(TestDownloadInfo.codec, TestDownloadInfo.bitrate_in_kbps, TestDownloadInfo.gain, - TestDownloadInfo.preview, TestDownloadInfo.download_info_url) + TestDownloadInfo.preview, TestDownloadInfo.download_info_url, TestDownloadInfo.direct) class TestDownloadInfo: @@ -16,6 +16,7 @@ class TestDownloadInfo: preview = False download_info_url = 'https://storage.mds.yandex.net/file-download-info/136146_d158926e.14534319.6.10994777/320' \ '?sign=8caf5ea72c946d4753f15298e4033b961c7acb1bb4db48eb5e6b59621e387d64&ts=5dc4a6f2 ' + direct = False def test_expected_values(self, download_info): assert download_info.codec == self.codec @@ -23,6 +24,7 @@ class TestDownloadInfo: assert download_info.gain == self.gain assert download_info.preview == self.preview assert download_info.download_info_url == self.download_info_url + assert download_info.direct == self.direct def test_de_json_none(self, client): assert DownloadInfo.de_json({}, client) is None @@ -32,7 +34,7 @@ class TestDownloadInfo: def test_de_json_required(self, client): json_dict = {'codec': self.codec, 'bitrate_in_kbps': self.bitrate_in_kbps, 'gain': self.gain, - 'preview': self.preview, 'download_info_url': self.download_info_url} + 'preview': self.preview, 'download_info_url': self.download_info_url, 'direct': self.direct} download_info = DownloadInfo.de_json(json_dict, client) assert download_info.codec == self.codec @@ -40,10 +42,11 @@ class TestDownloadInfo: assert download_info.gain == self.gain assert download_info.preview == self.preview assert download_info.download_info_url == self.download_info_url + assert download_info.direct == self.direct def test_de_json_all(self, client): json_dict = {'codec': self.codec, 'bitrate_in_kbps': self.bitrate_in_kbps, 'gain': self.gain, - 'preview': self.preview, 'download_info_url': self.download_info_url} + 'preview': self.preview, 'download_info_url': self.download_info_url, 'direct': self.direct} download_info = DownloadInfo.de_json(json_dict, client) assert download_info.codec == self.codec @@ -51,11 +54,12 @@ class TestDownloadInfo: assert download_info.gain == self.gain assert download_info.preview == self.preview assert download_info.download_info_url == self.download_info_url + assert download_info.direct == self.direct def test_equality(self): - a = DownloadInfo(self.codec, self.bitrate_in_kbps, self.gain, self.preview, self.download_info_url) - b = DownloadInfo(self.codec, 128, self.gain, True, self.download_info_url) - c = DownloadInfo(self.codec, self.bitrate_in_kbps, self.gain, self.preview, self.download_info_url) + a = DownloadInfo(self.codec, self.bitrate_in_kbps, self.gain, self.preview, self.download_info_url, self.direct) + b = DownloadInfo(self.codec, 128, self.gain, True, self.download_info_url, True) + c = DownloadInfo(self.codec, self.bitrate_in_kbps, self.gain, self.preview, self.download_info_url, self.direct) assert a != b assert hash(a) != hash(b) diff --git a/tests/test_meta_data.py b/tests/test_meta_data.py new file mode 100644 index 0000000..92b8669 --- /dev/null +++ b/tests/test_meta_data.py @@ -0,0 +1,42 @@ +from yandex_music import MetaData + + +class TestMetaData: + album = 'VK Virus Bot' + volume = 1 + year = 2018 + + def test_expected_values(self, meta_data): + assert meta_data.album == self.album + assert meta_data.volume == self.volume + assert meta_data.year == self.year + + def test_de_json_none(self, client): + assert MetaData.de_json({}, client) is None + + def test_de_json_required(self, client): + json_dict = {'album': self.album, 'volume': self.volume, 'year': self.year} + meta_data = MetaData.de_json(json_dict, client) + + assert meta_data.album == self.album + assert meta_data.volume == self.volume + assert meta_data.year == self.year + + def test_de_json_all(self, client): + json_dict = {'album': self.album, 'volume': self.volume, 'year': self.year} + meta_data = MetaData.de_json(json_dict, client) + + assert meta_data.album == self.album + assert meta_data.volume == self.volume + assert meta_data.year == self.year + + def test_equality(self): + a = MetaData(self.album, self.volume, self.year) + b = MetaData(self.album, 0, 2016) + c = MetaData(self.album, self.volume, self.year) + + assert a != b + assert hash(a) != hash(b) + assert a is not b + + assert a == c diff --git a/tests/test_track.py b/tests/test_track.py index 19e5f45..e168e23 100644 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -16,6 +16,10 @@ class TestTrack: storage_dir = '51327_109b74ca.36526310.1.609676' file_size = 6036792 error = None + can_publish = False + state = 'playable' + desired_visibility = 'private' + filename = 'Ты не так плох.mp3' regions = None available_as_rbt = None content_warning = None @@ -25,7 +29,8 @@ class TestTrack: version = 'Radio Edit' remember_position = False - def test_expected_values(self, track, artist, album, major, normalization): + def test_expected_values(self, track, artist, album, major, normalization, + track_without_nested_tracks, user, meta_data): assert track.id == self.id assert track.title == self.title assert track.available == self.available @@ -42,8 +47,11 @@ class TestTrack: assert track.duration_ms == self.duration_ms assert track.storage_dir == self.storage_dir assert track.file_size == self.file_size + assert track.substituted == track_without_nested_tracks + assert track.matched_track == track_without_nested_tracks assert track.normalization == normalization assert track.error == self.error + assert track.meta_data == meta_data assert track.regions == self.regions assert track.available_as_rbt == self.available_as_rbt assert track.content_warning == self.content_warning @@ -52,6 +60,11 @@ class TestTrack: assert track.available_full_without_permission == self.available_full_without_permission assert track.version == self.version assert track.remember_position == self.remember_position + assert track.can_publish == self.can_publish + assert track.state == self.state + assert track.desired_visibility == self.desired_visibility + assert track.filename == self.filename + assert track.user_info == user def test_de_json_none(self, client): assert Track.de_json({}, client) is None @@ -65,7 +78,8 @@ class TestTrack: assert track.id == self.id - def test_de_json_all(self, client, artist, album, major, normalization): + def test_de_json_all(self, client, artist, album, major, normalization, + track_without_nested_tracks, user, meta_data): json_dict = {'id_': self.id, 'title': self.title, 'available': self.available, 'available_for_premium_users': self.available_for_premium_users, 'artists': [artist.to_dict()], 'albums': [album.to_dict()], @@ -77,7 +91,10 @@ class TestTrack: 'content_warning': self.content_warning, 'explicit': self.explicit, 'preview_duration_ms': self.preview_duration_ms, 'version': self.version, 'available_full_without_permission': self.available_full_without_permission, - 'remember_position': self.remember_position} + 'remember_position': self.remember_position, 'substituted': track_without_nested_tracks.to_dict(), + 'matched_track': track_without_nested_tracks.to_dict(), 'can_publish': self.can_publish, + 'state': self.state, 'desired_visibility': self.desired_visibility, 'filename': self.filename, + 'user_info': user.to_dict(), 'meta_data': meta_data.to_dict()} track = Track.de_json(json_dict, client) assert track.id == self.id @@ -96,8 +113,11 @@ class TestTrack: assert track.duration_ms == self.duration_ms assert track.storage_dir == self.storage_dir assert track.file_size == self.file_size + assert track.substituted == track_without_nested_tracks + assert track.matched_track == track_without_nested_tracks assert track.normalization == normalization assert track.error == self.error + assert track.meta_data == meta_data assert track.regions == self.regions assert track.available_as_rbt == self.available_as_rbt assert track.content_warning == self.content_warning @@ -106,6 +126,11 @@ class TestTrack: assert track.available_full_without_permission == self.available_full_without_permission assert track.version == self.version assert track.remember_position == self.remember_position + assert track.can_publish == self.can_publish + assert track.state == self.state + assert track.desired_visibility == self.desired_visibility + assert track.filename == self.filename + assert track.user_info == user def test_equality(self): a = Track(self.id) diff --git a/tests/test_user.py b/tests/test_user.py index 89efa2c..804f29a 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -5,6 +5,8 @@ class TestUser: uid = 503646255 login = 'yamusic-daily' name = 'yamusic-daily' + display_name = 'Ilya (Marshal)' + full_name = 'Илья' sex = 'unknown' verified = False @@ -12,6 +14,8 @@ class TestUser: assert user.uid == self.uid assert user.login == self.login assert user.name == self.name + assert user.display_name == self.display_name + assert user.full_name == self.full_name assert user.sex == self.sex assert user.verified == self.verified @@ -19,31 +23,29 @@ class TestUser: assert User.de_json({}, client) is None def test_de_json_required(self, client): - json_dict = {'uid': self.uid, 'login': self.login, 'name': self.name, 'sex': self.sex, - 'verified': self.verified} + json_dict = {'uid': self.uid, 'login': self.login} user = User.de_json(json_dict, client) assert user.uid == self.uid assert user.login == self.login - assert user.name == self.name - assert user.sex == self.sex - assert user.verified == self.verified def test_de_json_all(self, client): json_dict = {'uid': self.uid, 'login': self.login, 'name': self.name, 'sex': self.sex, - 'verified': self.verified} + 'verified': self.verified, 'display_name': self.display_name, 'full_name': self.full_name} user = User.de_json(json_dict, client) assert user.uid == self.uid assert user.login == self.login assert user.name == self.name + assert user.display_name == self.display_name + assert user.full_name == self.full_name assert user.sex == self.sex assert user.verified == self.verified def test_equality(self): - a = User(self.uid, self.login, self.name, self.sex, self.verified) - b = User(1, self.login, self.name, self.sex, self.verified) - c = User(self.uid, self.login, '', self.sex, self.verified) + a = User(self.uid, self.login) + b = User(1, self.login) + c = User(self.uid, self.login) assert a != b assert hash(a) != hash(b) diff --git a/yandex_music/__init__.py b/yandex_music/__init__.py index 39a6bb7..349eb42 100644 --- a/yandex_music/__init__.py +++ b/yandex_music/__init__.py @@ -47,6 +47,7 @@ from .shot.shot_event import ShotEvent from .tracks_list import TracksList from .track.major import Major +from .track.meta_data import MetaData from .track.normalization import Normalization from .track.track import Track from .track.tracks_similar import SimilarTracks @@ -127,4 +128,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', 'PlaylistRecommendations', 'LandingList'] + 'ChartInfoMenuItem', 'Tag', 'TagResult', 'PlaylistRecommendations', 'LandingList', 'MetaData'] diff --git a/yandex_music/artist/artist.py b/yandex_music/artist/artist.py index a573238..baa06ef 100644 --- a/yandex_music/artist/artist.py +++ b/yandex_music/artist/artist.py @@ -11,6 +11,7 @@ class Artist(YandexMusicObject): Attributes: id (:obj:`int`): Уникальный идентификатор. + error (:obj:`str`): Сообщение об ошибке с объяснением почему не вернуло исполнителя. reason (:obj:`str`): Причина отсутствия исполнителя (сообщение об ошибке). name (:obj:`str`): Название. cover (:obj:`yandex_music.Cover` | :obj:`None`): Обложка. @@ -41,6 +42,7 @@ class Artist(YandexMusicObject): Args: id_ (:obj:`int`): Уникальный идентификатор. + error (:obj:`str`, optional): Сообщение об ошибке с объяснением почему не вернуло исполнителя. reason (:obj:`str`, optional): Причина отсутствия исполнителя (сообщение об ошибке). name (:obj:`str`, optional): Название. cover (:obj:`yandex_music.Cover`, optional): Обложка. @@ -73,6 +75,7 @@ class Artist(YandexMusicObject): def __init__(self, id_: int, + error: Optional[str] = None, reason: Optional[str] = None, name: Optional[str] = None, cover: Optional['Cover'] = None, @@ -103,6 +106,7 @@ class Artist(YandexMusicObject): **kwargs) -> None: self.id = id_ + self.error = error self.reason = reason self.name = name self.cover = cover diff --git a/yandex_music/client.py b/yandex_music/client.py index 1ac7424..7b898f4 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -49,7 +49,7 @@ class Client(YandexMusicObject): uid аккаунта для отправки запроса. Так же в большинстве методов придётся передавать `uid` явно. Attributes: - logger (:obj:`logging.Logger`): Объект логера. + logger (:obj:`logging.Logger`): Объект логгера. token (:obj:`str`): Уникальный ключ для аутентификации. base_url (:obj:`str`): Ссылка на API Yandex Music. oauth_url (:obj:`str`): Ссылка на OAuth Yandex Music. @@ -67,7 +67,7 @@ class Client(YandexMusicObject): """ def __init__(self, token: str = None, fetch_account_status: bool = True, base_url: str = None, - oauth_url: str = None, request: Request = None, report_new_fields=False) -> None: + oauth_url: str = None, request: Request = None, report_new_fields=True) -> None: self.logger = logging.getLogger(__name__) self.token = token @@ -133,7 +133,7 @@ class Client(YandexMusicObject): @classmethod def from_token(cls, token: str, *args, **kwargs) -> 'Client': - """Инициализция клиента по токену. + """Инициализация клиента по токену. Note: Ничем не отличается от `Client(token)`. Так исторически сложилось. diff --git a/yandex_music/cover.py b/yandex_music/cover.py index 739d787..0e2a025 100644 --- a/yandex_music/cover.py +++ b/yandex_music/cover.py @@ -18,6 +18,8 @@ class Cover(YandexMusicObject): is_custom (:obj:`bool`): Является ли обложка пользовательской. custom (:obj:`bool`): Является ли обложка пользовательской. prefix (:obj:`str`): Уникальный идентификатор. + copyright_name (:obj:`str`): Название владельца авторским правом. + copyright_cline (:obj:`str`): Владелец прав на музыку (автор текста и т.д.), а не её записи. error (:obj:`str`): Сообщение об ошибке. client (:obj:`yandex_music.Client`): Клиент Yandex Music. @@ -30,6 +32,8 @@ class Cover(YandexMusicObject): is_custom (:obj:`bool`, optional): Является ли обложка пользовательской. custom (:obj:`bool`, optional): Является ли обложка пользовательской. prefix (:obj:`str`, optional): Уникальный идентификатор. + copyright_name (:obj:`str`, optional): Название владельца авторским правом. + copyright_cline (:obj:`str`, optional): Владелец прав на музыку (автор текста и т.д.), а не её записи. error (:obj:`str`, optional): Сообщение об ошибке. client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music. **kwargs: Произвольные ключевые аргументы полученные от API. @@ -43,6 +47,8 @@ class Cover(YandexMusicObject): version: Optional[str] = None, custom: Optional[bool] = None, is_custom: Optional[bool] = None, + copyright_name: Optional[str] = None, + copyright_cline: Optional[str] = None, prefix: Optional[str] = None, error: Optional[str] = None, client: Optional['Client'] = None, @@ -55,6 +61,8 @@ class Cover(YandexMusicObject): self.version = version self.custom = custom self.is_custom = is_custom + self.copyright_name = copyright_name + self.copyright_cline = copyright_cline self.error = error self.client = client diff --git a/yandex_music/download_info.py b/yandex_music/download_info.py index e093729..76c2944 100644 --- a/yandex_music/download_info.py +++ b/yandex_music/download_info.py @@ -19,6 +19,7 @@ class DownloadInfo(YandexMusicObject): gain (:obj:`bool`): Усиление TODO. preview (:obj:`bool`): Предварительный просмотр TODO. download_info_url (:obj:`str`): Ссылка на XML документ содержащий данные для загрузки трека. + direct (:obj:`bool`): Прямая ли ссылка. direct_link (:obj:`str`): Прямая ссылка на загрузку. Доступна после получения ссылки. client (:obj:`yandex_music.Client`): Клиент Yandex Music. @@ -28,6 +29,7 @@ class DownloadInfo(YandexMusicObject): gain (:obj:`bool`): Усиление TODO. preview (:obj:`bool`): Предварительный просмотр TODO. download_info_url (:obj:`str`): Ссылка на XML документ содержащий данные для загрузки трека. + direct (:obj:`bool`): Прямая ли ссылка. client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music. **kwargs: Произвольные ключевые аргументы полученные от API. """ @@ -38,6 +40,7 @@ class DownloadInfo(YandexMusicObject): gain: bool, preview: bool, download_info_url: str, + direct: bool, client: Optional['Client'] = None, **kwargs): self.codec = codec @@ -45,6 +48,7 @@ class DownloadInfo(YandexMusicObject): self.gain = gain self.preview = preview self.download_info_url = download_info_url + self.direct = direct self.direct_link = None diff --git a/yandex_music/playlist/user.py b/yandex_music/playlist/user.py index f327ce3..814dba5 100644 --- a/yandex_music/playlist/user.py +++ b/yandex_music/playlist/user.py @@ -9,10 +9,19 @@ if TYPE_CHECKING: class User(YandexMusicObject): """Класс, представляющий пользователя. + Note: + Когда данный класс используется в `MadeFor` и `Playlist, то доступны все поля кроме `display_name` и + `full_name`. + + При наличии экземпляра класса в `user_info` у `Track` (у самозагруженных треков) доступны только `uid`, + '`login`, 'display_name` и `full_name`. + Attributes: uid (:obj:`int`): Идентификатор пользователя. login (:obj:`str`): Логин пользователя. name (:obj:`str`): Имя пользователя. + display_name (:obj:`str`, optional): Отображаемое пользователя. + full_name (:obj:`str`, optional): Полное имя пользователя. sex (:obj:`str`): Пол пользователя. verified (:obj:`bool`): Участвует ли пользователь в генерации плейлистов дня и т.д., и т.п. client (:obj:`yandex_music.Client`): Клиент Yandex Music. @@ -20,9 +29,11 @@ class User(YandexMusicObject): Args: uid (:obj:`int`): Идентификатор пользователя. login (:obj:`str`): Логин пользователя. - name (:obj:`str`): Имя пользователя. - sex (:obj:`str`): Пол пользователя. - verified (:obj:`bool`): Участвует ли пользователь в генерации плейлистов дня и т.д., и т.п. + name (:obj:`str`, optional): Имя пользователя. + display_name (:obj:`str`, optional): Отображаемое пользователя. + full_name (:obj:`str`, optional): Полное имя пользователя. + sex (:obj:`str`, optional): Пол пользователя. + verified (:obj:`bool`, optional): Участвует ли пользователь в генерации плейлистов дня и т.д., и т.п. client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music. **kwargs: Произвольные ключевые аргументы полученные от API. """ @@ -30,14 +41,19 @@ class User(YandexMusicObject): def __init__(self, uid: int, login: str, - name: str, - sex: str, - verified: bool, + name: Optional[str] = None, + display_name: Optional[str] = None, + full_name: Optional[str] = None, + sex: Optional[str] = None, + verified: Optional[bool] = None, client: Optional['Client'] = None, **kwargs) -> None: self.uid = uid self.login = login + self.name = name + self.display_name = display_name + self.full_name = full_name self.sex = sex self.verified = verified diff --git a/yandex_music/track/meta_data.py b/yandex_music/track/meta_data.py new file mode 100644 index 0000000..e80bb66 --- /dev/null +++ b/yandex_music/track/meta_data.py @@ -0,0 +1,57 @@ +from typing import TYPE_CHECKING, Optional + +from yandex_music import YandexMusicObject + +if TYPE_CHECKING: + from yandex_music import Client + + +class MetaData(YandexMusicObject): + """Класс, представляющий метаданные трека. + + Attributes: + album (:obj:`str`): Название альбома. + volume (:obj:`int`): Диск (раздел). + year (:obj:`int`): Год выхода. + client (:obj:`yandex_music.Client`): Клиент Yandex Music. + + Args: + album (:obj:`str`): Название альбома. + volume (:obj:`int`): Диск (раздел). + year (:obj:`int`): Год выхода. + client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music. + **kwargs: Произвольные ключевые аргументы полученные от API. + """ + + def __init__(self, + album: str, + volume: int, + year: int, + client: Optional['Client'] = None, + **kwargs) -> None: + self.album = album + self.volume = volume + self.year = year + + self.client = client + self._id_attrs = (self.album, self.volume, self.year) + + super().handle_unknown_kwargs(self, **kwargs) + + @classmethod + def de_json(cls, data: dict, client: 'Client') -> Optional['MetaData']: + """Десериализация объекта. + + Args: + data (:obj:`dict`): Поля и значения десериализуемого объекта. + client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music. + + Returns: + :obj:`yandex_music.MetaData`: Метаданные трека + """ + if not data: + return None + + data = super(MetaData, cls).de_json(data, client) + + return cls(client=client, **data) diff --git a/yandex_music/track/track.py b/yandex_music/track/track.py index ec2267e..9b1d10c 100644 --- a/yandex_music/track/track.py +++ b/yandex_music/track/track.py @@ -4,7 +4,7 @@ from yandex_music import YandexMusicObject from yandex_music.exceptions import InvalidBitrate if TYPE_CHECKING: - from yandex_music import Client, Normalization, Major, Album, Artist, Supplement, DownloadInfo + from yandex_music import Client, Normalization, Major, Album, Artist, Supplement, DownloadInfo, User, MetaData class Track(YandexMusicObject): @@ -15,6 +15,9 @@ class Track(YandexMusicObject): Известные значения поля `type`: `music`. + Поля `can_publish`, `state`, `desired_visibility`, `filename`, `user_info` доступны только у треков что были + загружены пользователем. + Attributes: id (:obj:`int` | :obj:`str`): Уникальный идентификатор. title (:obj:`str`): Название. @@ -32,8 +35,16 @@ class Track(YandexMusicObject): duration_ms (:obj:`int`): Длительность трека в миллисекундах. storage_dir (:obj:`str`): В какой папке на сервере хранится файл TODO. file_size (:obj:`int`): Размер файла. TODO добавить единицу измерения. + substituted (:obj:`yandex_music.Track`): Замещённый трек. + matched_track (:obj:`yandex_music.Track`): Соответствующий трек TODO. normalization (:obj:`list` из :obj:`yandex_music.Normalization`): Значения для нормализации трека. error (:obj:`str`): Сообщение об ошибке. + can_publish (:obj:`bool`): Может ли быть опубликован. + state (:obj:`str`): Состояние, например, playable. + desired_visibility (:obj:`str`): Видимость трека. + filename (:obj:`str`): Название файла. + user_info (:obj:`yandex_music.User`): Информация о пользователе, который загрузил трек. + meta_data (:obj:`yandex_music.MetaData`): Информация о метаданных трека. regions (:obj:`list` из :obj:`str`): Регион TODO. available_as_rbt (:obj:`bool`): TODO. content_warning (:obj:`str`): Тип откровенного контента. @@ -62,8 +73,16 @@ class Track(YandexMusicObject): duration_ms (:obj:`int`, optional): Длительность трека в миллисекундах. storage_dir (:obj:`str`, optional): В какой папке на сервере хранится файл TODO. file_size (:obj:`int`, optional): Размер файла. TODO добавить единицу измерения. + substituted (:obj:`yandex_music.Track`, optional): Замещённый трек. + matched_track (:obj:`yandex_music.Track`, optional): Соответствующий трек TODO. normalization (:obj:`list` из :obj:`yandex_music.Normalization`, optional): Значения для нормализации трека. error (:obj:`str`, optional): Сообщение об ошибке. + can_publish (:obj:`bool`, optional): Может ли быть опубликован. + state (:obj:`str`, optional): Состояние, например, playable. + desired_visibility (:obj:`str`, optional): Видимость трека. + filename (:obj:`str`, optional): Название файла. + user_info (:obj:`yandex_music.User`, optional): Информация о пользователе, который загрузил трек. + meta_data (:obj:`yandex_music.MetaData`, optional): Информация о метаданных трека. regions (:obj:`list` из :obj:`str`, optional): Регион TODO. available_as_rbt (:obj:`bool`, optional): TODO. content_warning (:obj:`str`, optional): Тип откровенного контента. @@ -93,8 +112,16 @@ class Track(YandexMusicObject): duration_ms: Optional[int] = None, storage_dir: Optional[str] = None, file_size: Optional[int] = None, + substituted: Optional['Track'] = None, + matched_track: Optional['Track'] = None, normalization: Optional['Normalization'] = None, error: Optional[str] = None, + can_publish: Optional[bool] = None, + state: Optional[str] = None, + desired_visibility: Optional[str] = None, + filename: Optional[str] = None, + user_info: Optional['User'] = None, + meta_data: Optional['MetaData'] = None, regions: Optional[List[str]] = None, available_as_rbt: Optional[bool] = None, content_warning: Optional[str] = None, @@ -122,8 +149,16 @@ class Track(YandexMusicObject): self.duration_ms = duration_ms self.storage_dir = storage_dir self.file_size = file_size + self.substituted = substituted + self.matched_track = matched_track self.normalization = normalization self.error = error + self.can_publish = can_publish + self.state = state + self.desired_visibility = desired_visibility + self.filename = filename + self.user_info = user_info + self.meta_data = meta_data self.regions = regions self.available_as_rbt = available_as_rbt self.content_warning = content_warning @@ -238,11 +273,15 @@ class Track(YandexMusicObject): return None data = super(Track, cls).de_json(data, client) - from yandex_music import Normalization, Major, Album, Artist + from yandex_music import Normalization, Major, Album, Artist, User, MetaData data['albums'] = Album.de_list(data.get('albums'), client) data['artists'] = Artist.de_list(data.get('artists'), client) data['normalization'] = Normalization.de_json(data.get('normalization'), client) data['major'] = Major.de_json(data.get('major'), client) + data['substituted'] = Track.de_json(data.get('substituted'), client) + data['matched_track'] = Track.de_json(data.get('matched_track'), client) + data['user_info'] = User.de_json(data.get('user_info'), client) + data['meta_data'] = MetaData.de_json(data.get('meta_data'), client) return cls(client=client, **data) @@ -260,11 +299,7 @@ class Track(YandexMusicObject): if not data: return [] - tracks = list() - for track in data: - tracks.append(cls.de_json(track, client)) - - return tracks + return [cls.de_json(track, client) for track in data] # camelCase псевдонимы