From 25fab0345e641c89ef6990709d1a78e21daa21fd Mon Sep 17 00:00:00 2001 From: FL550 <36160004+FL550@users.noreply.github.com> Date: Thu, 29 Jan 2026 07:48:33 +0100 Subject: [PATCH] Adds date_added field to Tidal provider (#2969) * feat: adds date_added to tidal provider * applied copilot suggestions * Update snapshot --------- Co-authored-by: FL550 --- music_assistant/providers/tidal/api_client.py | 6 +- music_assistant/providers/tidal/library.py | 16 +-- music_assistant/providers/tidal/parsers.py | 100 ++++++++++-------- .../tidal/__snapshots__/test_parsers.ambr | 8 +- .../tidal/fixtures/albums/album.json | 81 +++++++------- .../tidal/fixtures/artists/artist.json | 27 ++--- .../tidal/fixtures/playlists/playlist.json | 45 ++++---- .../tidal/fixtures/tracks/track.json | 97 +++++++++-------- tests/providers/tidal/test_library.py | 25 +++-- 9 files changed, 219 insertions(+), 186 deletions(-) diff --git a/music_assistant/providers/tidal/api_client.py b/music_assistant/providers/tidal/api_client.py index 259e66e3..41f62de8 100644 --- a/music_assistant/providers/tidal/api_client.py +++ b/music_assistant/providers/tidal/api_client.py @@ -164,7 +164,6 @@ class TidalAPIClient: self, endpoint: str, item_key: str = "items", - nested_key: str | None = None, limit: int = 50, cursor_based: bool = False, **kwargs: Any, @@ -192,10 +191,7 @@ class TidalAPIClient: break for item in items: - if nested_key and nested_key in item and item[nested_key]: - yield item[nested_key] - else: - yield item + yield item if cursor_based: cursor = response.get("cursor") diff --git a/music_assistant/providers/tidal/library.py b/music_assistant/providers/tidal/library.py index 4e0ea294..00cb67af 100644 --- a/music_assistant/providers/tidal/library.py +++ b/music_assistant/providers/tidal/library.py @@ -31,22 +31,22 @@ class TidalLibraryManager: async def get_artists(self) -> AsyncGenerator[Artist, None]: """Retrieve library artists.""" path = f"users/{self.auth.user_id}/favorites/artists" - async for item in self.api.paginate(path, nested_key="item"): - if item and item.get("id"): + async for item in self.api.paginate(path): + if item and item.get("item") and item["item"].get("id"): yield parse_artist(self.provider, item) async def get_albums(self) -> AsyncGenerator[Album, None]: """Retrieve library albums.""" path = f"users/{self.auth.user_id}/favorites/albums" - async for item in self.api.paginate(path, nested_key="item"): - if item and item.get("id"): + async for item in self.api.paginate(path): + if item and item.get("item") and item["item"].get("id"): yield parse_album(self.provider, item) async def get_tracks(self) -> AsyncGenerator[Track, None]: """Retrieve library tracks.""" path = f"users/{self.auth.user_id}/favorites/tracks" - async for item in self.api.paginate(path, nested_key="item"): - if item and item.get("id"): + async for item in self.api.paginate(path): + if item and item.get("item") and item["item"].get("id"): yield parse_track(self.provider, item) async def get_playlists(self) -> AsyncGenerator[Playlist, None]: @@ -60,8 +60,8 @@ class TidalLibraryManager: # 2. Get user playlists path = f"users/{self.auth.user_id}/playlistsAndFavoritePlaylists" - async for item in self.api.paginate(path, nested_key="playlist"): - if item and item.get("uuid"): + async for item in self.api.paginate(path): + if item and item.get("playlist") and item["playlist"].get("uuid"): yield parse_playlist(self.provider, item) async def add_item(self, item: MediaItemType) -> bool: diff --git a/music_assistant/providers/tidal/parsers.py b/music_assistant/providers/tidal/parsers.py index 30ea0847..3ced097c 100644 --- a/music_assistant/providers/tidal/parsers.py +++ b/music_assistant/providers/tidal/parsers.py @@ -34,11 +34,13 @@ if TYPE_CHECKING: def parse_artist(provider: TidalProvider, artist_obj: dict[str, Any]) -> Artist: """Parse tidal artist object to generic layout.""" - artist_id = str(artist_obj["id"]) + # Handle both full artist objects and nested ones coming from albums/tracks + artist_obj_data = artist_obj.get("item", artist_obj) + artist_id = str(artist_obj_data["id"]) artist = Artist( item_id=artist_id, provider=provider.instance_id, - name=artist_obj["name"], + name=artist_obj_data["name"], provider_mappings={ ProviderMapping( item_id=artist_id, @@ -51,8 +53,11 @@ def parse_artist(provider: TidalProvider, artist_obj: dict[str, Any]) -> Artist: }, ) # metadata - if artist_obj["picture"]: - picture_id = artist_obj["picture"].replace("-", "/") + if "created" in artist_obj: + with suppress(ValueError): + artist.date_added = datetime.fromisoformat(artist_obj["created"]) + if artist_obj_data["picture"]: + picture_id = artist_obj_data["picture"].replace("-", "/") image_url = f"{RESOURCES_URL}/{picture_id}/750x750.jpg" artist.metadata.images = UniqueList( [ @@ -70,11 +75,12 @@ def parse_artist(provider: TidalProvider, artist_obj: dict[str, Any]) -> Artist: def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album: """Parse tidal album object to generic layout.""" + album_obj_data = album_obj.get("item", album_obj) name, version = parse_title_and_version( - album_obj.get("title", "Unknown Album"), - album_obj.get("version") or None, + album_obj_data.get("title", "Unknown Album"), + album_obj_data.get("version") or None, ) - album_id = str(album_obj.get("id", "")) + album_id = str(album_obj_data.get("id", "")) album = Album( item_id=album_id, @@ -97,7 +103,7 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album: # Safely handle artists array various_artist_album: bool = False - for artist_obj in album_obj.get("artists", []): + for artist_obj in album_obj_data.get("artists", []): try: if artist_obj.get("name") == "Various Artists": various_artist_album = True @@ -106,7 +112,7 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album: provider.logger.warning("Error parsing artist in album %s: %s", name, err) # Safely determine album type - album_type = album_obj.get("type", "ALBUM") + album_type = album_obj_data.get("type", "ALBUM") if album_type == "COMPILATION" or various_artist_album: album.album_type = AlbumType.COMPILATION elif album_type == "ALBUM": @@ -122,7 +128,7 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album: album.album_type = inferred_type # Safely parse year - if release_date := album_obj.get("releaseDate", ""): + if release_date := album_obj_data.get("releaseDate", ""): try: album.year = int(release_date.split("-")[0]) except (ValueError, IndexError): @@ -131,16 +137,19 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album: album.metadata.release_date = datetime.fromisoformat(release_date) # Safely set metadata - upc = album_obj.get("upc") + if "created" in album_obj: + with suppress(ValueError): + album.date_added = datetime.fromisoformat(album_obj["created"]) + upc = album_obj_data.get("upc") if upc: album.external_ids.add((ExternalID.BARCODE, upc)) - album.metadata.copyright = album_obj.get("copyright", "") - album.metadata.explicit = album_obj.get("explicit", False) - album.metadata.popularity = album_obj.get("popularity", 0) + album.metadata.copyright = album_obj_data.get("copyright", "") + album.metadata.explicit = album_obj_data.get("explicit", False) + album.metadata.popularity = album_obj_data.get("popularity", 0) # Safely handle cover image - cover = album_obj.get("cover") + cover = album_obj_data.get("cover") if cover: picture_id = cover.replace("-", "/") image_url = f"{RESOURCES_URL}/{picture_id}/750x750.jpg" @@ -164,12 +173,13 @@ def parse_track( lyrics: dict[str, str] | None = None, ) -> Track: """Parse tidal track object to generic layout.""" + track_obj_data = track_obj.get("item", track_obj) name, version = parse_title_and_version( - track_obj.get("title", "Unknown"), - track_obj.get("version") or None, + track_obj_data.get("title", "Unknown"), + track_obj_data.get("version") or None, ) - track_id = str(track_obj.get("id", 0)) - media_metadata = track_obj.get("mediaMetadata") or {} + track_id = str(track_obj_data.get("id", 0)) + media_metadata = track_obj_data.get("mediaMetadata") or {} tags = media_metadata.get("tags", []) hi_res_lossless = any(tag in tags for tag in ["HIRES_LOSSLESS", "HI_RES_LOSSLESS"]) track = Track( @@ -177,7 +187,7 @@ def parse_track( provider=provider.instance_id, name=name, version=version, - duration=track_obj.get("duration", 0), + duration=track_obj_data.get("duration", 0), provider_mappings={ ProviderMapping( item_id=str(track_id), @@ -188,37 +198,40 @@ def parse_track( bit_depth=24 if hi_res_lossless else 16, ), url=f"https://tidal.com/track/{track_id}", - available=track_obj["streamReady"], + available=track_obj_data["streamReady"], ) }, - disc_number=track_obj.get("volumeNumber", 0) or 0, - track_number=track_obj.get("trackNumber", 0) or 0, + disc_number=track_obj_data.get("volumeNumber", 0) or 0, + track_number=track_obj_data.get("trackNumber", 0) or 0, ) - if "isrc" in track_obj: - track.external_ids.add((ExternalID.ISRC, track_obj["isrc"])) + if "isrc" in track_obj_data: + track.external_ids.add((ExternalID.ISRC, track_obj_data["isrc"])) track.artists = UniqueList() - for track_artist in track_obj["artists"]: + for track_artist in track_obj_data["artists"]: artist = parse_artist(provider, track_artist) track.artists.append(artist) # metadata - track.metadata.explicit = track_obj["explicit"] - track.metadata.popularity = track_obj["popularity"] - if "copyright" in track_obj: - track.metadata.copyright = track_obj["copyright"] + if "created" in track_obj: + with suppress(ValueError): + track.date_added = datetime.fromisoformat(track_obj["created"]) + track.metadata.explicit = track_obj_data["explicit"] + track.metadata.popularity = track_obj_data["popularity"] + if "copyright" in track_obj_data: + track.metadata.copyright = track_obj_data["copyright"] if lyrics and "lyrics" in lyrics: track.metadata.lyrics = lyrics["lyrics"] if lyrics and "subtitles" in lyrics: track.metadata.lrc_lyrics = lyrics["subtitles"] - if track_obj["album"]: + if track_obj_data["album"]: # Here we use an ItemMapping as Tidal returns # minimal data when getting an Album from a Track track.album = provider.get_item_mapping( media_type=MediaType.ALBUM, - key=str(track_obj["album"]["id"]), - name=track_obj["album"]["title"], + key=str(track_obj_data["album"]["id"]), + name=track_obj_data["album"]["title"], ) - if track_obj["album"]["cover"]: - picture_id = track_obj["album"]["cover"].replace("-", "/") + if track_obj_data["album"]["cover"]: + picture_id = track_obj_data["album"]["cover"].replace("-", "/") image_url = f"{RESOURCES_URL}/{picture_id}/750x750.jpg" track.metadata.images = UniqueList( [ @@ -237,8 +250,9 @@ def parse_playlist( provider: TidalProvider, playlist_obj: dict[str, Any], is_mix: bool = False ) -> Playlist: """Parse tidal playlist object to generic layout.""" + playlist_obj_data = playlist_obj.get("playlist", playlist_obj) # Get ID based on playlist type - raw_id = str(playlist_obj.get("id" if is_mix else "uuid", "")) + raw_id = str(playlist_obj_data.get("id" if is_mix else "uuid", "")) # Add prefix for mixes to distinguish them playlist_id = f"mix_{raw_id}" if is_mix else raw_id @@ -249,7 +263,7 @@ def parse_playlist( is_editable = False else: creator_id = None - creator = playlist_obj.get("creator", {}) + creator = playlist_obj_data.get("creator", {}) if creator: creator_id = creator.get("id") is_editable = bool(creator_id and str(creator_id) == str(provider.auth.user_id)) @@ -269,7 +283,7 @@ def parse_playlist( playlist = Playlist( item_id=playlist_id, provider=provider.instance_id, - name=playlist_obj.get("title", "Unknown"), + name=playlist_obj_data.get("title", "Unknown"), owner=owner_name, provider_mappings={ ProviderMapping( @@ -284,16 +298,18 @@ def parse_playlist( ) # Metadata - different fields based on type - + if "created" in playlist_obj: + with suppress(ValueError): + playlist.date_added = datetime.fromisoformat(playlist_obj["created"]) # Add the description from the subtitle for mixes if is_mix: - subtitle = playlist_obj.get("subTitle") + subtitle = playlist_obj_data.get("subTitle") if subtitle: playlist.metadata.description = subtitle # Handle images differently based on type if is_mix: - if pictures := playlist_obj.get("images", {}).get("MEDIUM"): + if pictures := playlist_obj_data.get("images", {}).get("MEDIUM"): image_url = pictures.get("url", "") if image_url: playlist.metadata.images = UniqueList( @@ -306,7 +322,7 @@ def parse_playlist( ) ] ) - elif picture := (playlist_obj.get("squareImage") or playlist_obj.get("image")): + elif picture := (playlist_obj_data.get("squareImage") or playlist_obj_data.get("image")): picture_id = picture.replace("-", "/") image_url = f"{RESOURCES_URL}/{picture_id}/750x750.jpg" playlist.metadata.images = UniqueList( diff --git a/tests/providers/tidal/__snapshots__/test_parsers.ambr b/tests/providers/tidal/__snapshots__/test_parsers.ambr index 541491bd..2cc814c1 100644 --- a/tests/providers/tidal/__snapshots__/test_parsers.ambr +++ b/tests/providers/tidal/__snapshots__/test_parsers.ambr @@ -70,7 +70,7 @@ 'version': '', }), ]), - 'date_added': None, + 'date_added': '2024-06-10T12:00:00+00:00', 'external_ids': list([ list([ 'barcode', @@ -143,7 +143,7 @@ # --- # name: test_parse_artist[artist] dict({ - 'date_added': None, + 'date_added': '2024-06-10T12:00:00+00:00', 'external_ids': list([ ]), 'favorite': False, @@ -281,7 +281,7 @@ # --- # name: test_parse_playlist[playlist] dict({ - 'date_added': None, + 'date_added': '2024-06-10T12:00:00+00:00', 'external_ids': list([ ]), 'favorite': False, @@ -434,7 +434,7 @@ 'version': '', }), ]), - 'date_added': None, + 'date_added': '2024-06-10T12:00:00+00:00', 'disc_number': 1, 'duration': 180, 'external_ids': list([ diff --git a/tests/providers/tidal/fixtures/albums/album.json b/tests/providers/tidal/fixtures/albums/album.json index 7e27d683..f449f258 100644 --- a/tests/providers/tidal/fixtures/albums/album.json +++ b/tests/providers/tidal/fixtures/albums/album.json @@ -1,41 +1,44 @@ { - "id": 67890, - "title": "Test Album", - "version": "Deluxe Edition", - "artists": [ - { - "id": 12345, - "name": "Test Artist", - "picture": "1234-5678-90ab-cdef", - "type": "MAIN" - } - ], - "type": "ALBUM", - "releaseDate": "2023-01-01", - "availabilityDate": "2023-01-01", - "duration": 2400, - "numberOfTracks": 12, - "numberOfVolumes": 1, - "upc": "123456789012", - "copyright": "℗ 2023 Test Label", - "explicit": false, - "popularity": 50, - "cover": "abcd-ef01-2345-6789", - "videoCover": null, - "streamReady": true, - "streamStartDate": "2023-01-01T00:00:00.000+0000", - "allowStreaming": true, - "premiumStreamingOnly": false, - "numberOfVideos": 0, - "audioQuality": "LOSSLESS", - "audioModes": [ - "STEREO" - ], - "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] - }, - "url": "http://www.tidal.com/album/67890" + "created": "2024-06-10T12:00:00Z", + "item": { + "id": 67890, + "title": "Test Album", + "version": "Deluxe Edition", + "artists": [ + { + "id": 12345, + "name": "Test Artist", + "picture": "1234-5678-90ab-cdef", + "type": "MAIN" + } + ], + "type": "ALBUM", + "releaseDate": "2023-01-01", + "availabilityDate": "2023-01-01", + "duration": 2400, + "numberOfTracks": 12, + "numberOfVolumes": 1, + "upc": "123456789012", + "copyright": "℗ 2023 Test Label", + "explicit": false, + "popularity": 50, + "cover": "abcd-ef01-2345-6789", + "videoCover": null, + "streamReady": true, + "streamStartDate": "2023-01-01T00:00:00.000+0000", + "allowStreaming": true, + "premiumStreamingOnly": false, + "numberOfVideos": 0, + "audioQuality": "LOSSLESS", + "audioModes": [ + "STEREO" + ], + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + }, + "url": "http://www.tidal.com/album/67890" + } } diff --git a/tests/providers/tidal/fixtures/artists/artist.json b/tests/providers/tidal/fixtures/artists/artist.json index a6d286f6..e8e85ed1 100644 --- a/tests/providers/tidal/fixtures/artists/artist.json +++ b/tests/providers/tidal/fixtures/artists/artist.json @@ -1,15 +1,18 @@ { - "id": 12345, - "name": "Test Artist", - "picture": "1234-5678-90ab-cdef", - "url": "http://www.tidal.com/artist/12345", - "artistTypes": [ - "ARTIST" - ], - "popularity": 75, - "banner": "banner-1234-5678-90ab", - "releaseDateOriginal": "2010-01-01", - "mixes": { - "ARTIST_MIX": "artist_mix_id_123" + "created": "2024-06-10T12:00:00Z", + "item": { + "id": 12345, + "name": "Test Artist", + "picture": "1234-5678-90ab-cdef", + "url": "http://www.tidal.com/artist/12345", + "artistTypes": [ + "ARTIST" + ], + "popularity": 75, + "banner": "banner-1234-5678-90ab", + "releaseDateOriginal": "2010-01-01", + "mixes": { + "ARTIST_MIX": "artist_mix_id_123" + } } } diff --git a/tests/providers/tidal/fixtures/playlists/playlist.json b/tests/providers/tidal/fixtures/playlists/playlist.json index 2d2e6fd1..034a695b 100644 --- a/tests/providers/tidal/fixtures/playlists/playlist.json +++ b/tests/providers/tidal/fixtures/playlists/playlist.json @@ -1,23 +1,26 @@ { - "uuid": "aabbcc-1122-3344-5566", - "title": "Test Playlist", - "description": "A test playlist for testing", - "creator": { - "id": 99999, - "name": "Test User", - "picture": null - }, - "type": "USER", - "publicPlaylist": true, - "created": "2023-01-01T00:00:00.000+0000", - "lastUpdated": "2023-06-15T12:00:00.000+0000", - "numberOfTracks": 25, - "numberOfVideos": 0, - "duration": 5400, - "popularity": 45, - "image": "playlist-image-id", - "squareImage": "playlist-square-image-id", - "url": "http://www.tidal.com/playlist/aabbcc-1122-3344-5566", - "promotedArtists": [], - "lastItemAddedAt": "2023-06-15T12:00:00.000+0000" + "created": "2024-06-10T12:00:00Z", + "playlist": { + "uuid": "aabbcc-1122-3344-5566", + "title": "Test Playlist", + "description": "A test playlist for testing", + "creator": { + "id": 99999, + "name": "Test User", + "picture": null + }, + "type": "USER", + "publicPlaylist": true, + "created": "2023-01-01T00:00:00.000+0000", + "lastUpdated": "2023-06-15T12:00:00.000+0000", + "numberOfTracks": 25, + "numberOfVideos": 0, + "duration": 5400, + "popularity": 45, + "image": "playlist-image-id", + "squareImage": "playlist-square-image-id", + "url": "http://www.tidal.com/playlist/aabbcc-1122-3344-5566", + "promotedArtists": [], + "lastItemAddedAt": "2023-06-15T12:00:00.000+0000" + } } diff --git a/tests/providers/tidal/fixtures/tracks/track.json b/tests/providers/tidal/fixtures/tracks/track.json index 04917ae4..9504ba3d 100644 --- a/tests/providers/tidal/fixtures/tracks/track.json +++ b/tests/providers/tidal/fixtures/tracks/track.json @@ -1,49 +1,52 @@ { - "id": 112233, - "title": "Test Track", - "version": "Remastered", - "duration": 180, - "replayGain": -8.5, - "peak": 0.95, - "allowStreaming": true, - "streamReady": true, - "streamStartDate": "2023-01-01T00:00:00.000+0000", - "premiumStreamingOnly": false, - "trackNumber": 1, - "volumeNumber": 1, - "isrc": "US1234567890", - "copyright": "℗ 2023 Test Label", - "artists": [ - { - "id": 12345, - "name": "Test Artist", - "picture": "1234-5678-90ab-cdef", - "type": "MAIN" - } - ], - "album": { - "id": 67890, - "title": "Test Album", - "cover": "abcd-ef01-2345-6789", - "videoCover": null, - "releaseDate": "2023-01-01" - }, - "explicit": false, - "audioQuality": "LOSSLESS", - "audioModes": [ - "STEREO" - ], - "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] - }, - "popularity": 60, - "mixes": { - "TRACK_MIX": "track_mix_id_456" - }, - "url": "http://www.tidal.com/track/112233", - "djReady": true, - "stemReady": false + "created": "2024-06-10T12:00:00Z", + "item": { + "id": 112233, + "title": "Test Track", + "version": "Remastered", + "duration": 180, + "replayGain": -8.5, + "peak": 0.95, + "allowStreaming": true, + "streamReady": true, + "streamStartDate": "2023-01-01T00:00:00.000+0000", + "premiumStreamingOnly": false, + "trackNumber": 1, + "volumeNumber": 1, + "isrc": "US1234567890", + "copyright": "℗ 2023 Test Label", + "artists": [ + { + "id": 12345, + "name": "Test Artist", + "picture": "1234-5678-90ab-cdef", + "type": "MAIN" + } + ], + "album": { + "id": 67890, + "title": "Test Album", + "cover": "abcd-ef01-2345-6789", + "videoCover": null, + "releaseDate": "2023-01-01" + }, + "explicit": false, + "audioQuality": "LOSSLESS", + "audioModes": [ + "STEREO" + ], + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + }, + "popularity": 60, + "mixes": { + "TRACK_MIX": "track_mix_id_456" + }, + "url": "http://www.tidal.com/track/112233", + "djReady": true, + "stemReady": false + } } diff --git a/tests/providers/tidal/test_library.py b/tests/providers/tidal/test_library.py index 3efb038a..09f58ae4 100644 --- a/tests/providers/tidal/test_library.py +++ b/tests/providers/tidal/test_library.py @@ -56,7 +56,9 @@ async def test_get_artists( mock_parse_artist: Mock, library_manager: TidalLibraryManager, provider_mock: Mock ) -> None: """Test get_artists.""" - provider_mock.api.paginate.return_value = [{"id": 1, "name": "Test Artist"}] + provider_mock.api.paginate.return_value = [ + {"created": "2024-01-01T00:00:00", "item": {"id": 1, "name": "Test Artist"}} + ] mock_parse_artist.return_value = Mock(item_id="1") artists = [a async for a in library_manager.get_artists()] @@ -65,7 +67,6 @@ async def test_get_artists( assert artists[0].item_id == "1" provider_mock.api.paginate.assert_called_with( "users/12345/favorites/artists", - nested_key="item", ) mock_parse_artist.assert_called_once() @@ -75,7 +76,9 @@ async def test_get_albums( mock_parse_album: Mock, library_manager: TidalLibraryManager, provider_mock: Mock ) -> None: """Test get_albums.""" - provider_mock.api.paginate.return_value = [{"id": 1, "title": "Test Album"}] + provider_mock.api.paginate.return_value = [ + {"created": "2024-01-01T00:00:00", "item": {"id": 1, "title": "Test Album"}} + ] mock_parse_album.return_value = Mock(item_id="1") albums = [a async for a in library_manager.get_albums()] @@ -84,7 +87,6 @@ async def test_get_albums( assert albums[0].item_id == "1" provider_mock.api.paginate.assert_called_with( "users/12345/favorites/albums", - nested_key="item", ) mock_parse_album.assert_called_once() @@ -94,7 +96,9 @@ async def test_get_tracks( mock_parse_track: Mock, library_manager: TidalLibraryManager, provider_mock: Mock ) -> None: """Test get_tracks.""" - provider_mock.api.paginate.return_value = [{"id": 1, "title": "Test Track"}] + provider_mock.api.paginate.return_value = [ + {"created": "2024-01-01T00:00:00", "item": {"id": 1, "title": "Test Track"}} + ] mock_parse_track.return_value = Mock(item_id="1") tracks = [t async for t in library_manager.get_tracks()] @@ -103,7 +107,6 @@ async def test_get_tracks( assert tracks[0].item_id == "1" provider_mock.api.paginate.assert_called_with( "users/12345/favorites/tracks", - nested_key="item", ) mock_parse_track.assert_called_once() @@ -116,7 +119,12 @@ async def test_get_playlists( # Mock mixes response mixes_response = [{"id": "mix_1", "title": "Mix 1"}] # Mock playlists response - playlists_response = [{"uuid": "pl_1", "title": "Playlist 1"}] + playlists_response = [ + { + "created": "2024-01-01T00:00:00", + "playlist": {"uuid": "pl_1", "title": "Playlist 1"}, + } + ] # Configure paginate side effect async def paginate_side_effect( @@ -126,7 +134,8 @@ async def test_get_playlists( for item in mixes_response: yield item else: - for item in playlists_response: + # The ignore[assignment] is needed because of the different return types + for item in playlists_response: # type: ignore[assignment] yield item provider_mock.api.paginate.side_effect = paginate_side_effect -- 2.34.1