From 39964698e4f354f8d19a5ad89feaaf306e40562d Mon Sep 17 00:00:00 2001 From: tehkillerbee Date: Thu, 11 Dec 2025 19:51:02 +0100 Subject: [PATCH 1/3] Fix: Final chunk count must not exceed maximum number of playlists (Fixes #389). Added pagination tests --- tidalapi/user.py | 1 + tidalapi/workers.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tidalapi/user.py b/tidalapi/user.py index 2b514e49..1e534e8a 100644 --- a/tidalapi/user.py +++ b/tidalapi/user.py @@ -840,6 +840,7 @@ def get_tracks_count( This performs a minimal API request (limit=1) to fetch metadata about the tracks without retrieving all of them. The API response contains 'totalNumberOfItems', which represents the total items (tracks) available. + Note: This number also includes track that may not be available for playback :return: The number of items available. """ params = {"limit": 1, "offset": 0} diff --git a/tidalapi/workers.py b/tidalapi/workers.py index adc1a473..ed7f1faa 100644 --- a/tidalapi/workers.py +++ b/tidalapi/workers.py @@ -41,7 +41,17 @@ def get_items( items = [] with ThreadPoolExecutor(processes) as pool: - args_list = [(func, chunk_size, offset, *args) for offset in offsets] + # Build argument tuples for each worker. + # The limit is capped using `total_count` so the final chunk does not exceed + # the available number of items (e.g., last chunk may be smaller than chunk_size). + # i.e. for a total number of 123 tracks, the following ranges will be generated + # (func, 50, 0, ...) + # (func, 50, 50, ...) + # (func, 23, 100, ...) + args_list = [ + (func, min(chunk_size, total_count - offset), offset, *args) + for offset in offsets + ] for page_items in pool.map(func_wrapper, args_list): items.extend(page_items) From 0c3b851a6324692ed9a2a3f8a6bf811344cc1f7f Mon Sep 17 00:00:00 2001 From: tehkillerbee Date: Thu, 11 Dec 2025 19:53:49 +0100 Subject: [PATCH 2/3] Added pagination tests for playlist, updated misc tests --- tests/test_user.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/test_user.py b/tests/test_user.py index 3973f2eb..3733d3cc 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -430,7 +430,8 @@ def test_get_favorite_tracks(session): favorites = session.user.favorites tracks = favorites.tracks_paginated() tracks_count = favorites.get_tracks_count() - assert len(tracks) > 0 # and tracks_count == len(tracks) + # Only the available tracks are returned so the final track count might be lower + assert len(tracks) > 0 and tracks_count >= len(tracks) assert isinstance(tracks[0], tidalapi.Track) @@ -486,7 +487,7 @@ def test_get_favorite_videos(session): favorites = session.user.favorites videos = favorites.videos_paginated() videos_count = favorites.get_videos_count() - assert len(videos) == videos_count and videos_count > 0 + assert videos_count > 0 and len(videos) == videos_count assert isinstance(videos[0], tidalapi.media.Video) @@ -496,6 +497,14 @@ def test_add_remove_favorite_video(session): add_remove(video_id, favorites.add_video, favorites.remove_video, favorites.videos) +def test_get_favorite_playlists(session): + favorites = session.user.favorites + playlists = favorites.playlists_paginated() + playlists_count = favorites.get_playlists_count() + assert len(playlists) > 0 and playlists_count == len(playlists) + assert isinstance(playlists[0], tidalapi.Playlist) + + def test_get_favorite_playlists_order(session): # Add 5 favourite playlists to ensure enough playlists exist for the tests playlist_ids = [ From 08742362ee8cef7df1362387cef60c2846aa84ee Mon Sep 17 00:00:00 2001 From: tehkillerbee Date: Thu, 11 Dec 2025 19:54:46 +0100 Subject: [PATCH 3/3] Fix track version if empty (Fixes #388). Updated tests --- tests/test_media.py | 19 ++++++------------- tidalapi/media.py | 2 +- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/tests/test_media.py b/tests/test_media.py index 1f362cf6..c162da64 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -504,25 +504,18 @@ def test_video_image(session): verify_image_resolution(session, video.image(1080, 720), 1270, 1270) -def test_full_name_track_1(session): - track = session.track(149119714) - assert track.name == "Fibonacci Progressions (Keemiyo Remix)" - assert track.version is None - assert track.full_name == "Fibonacci Progressions (Keemiyo Remix)" - - -def test_full_name_track_2(session): +def test_full_name_track_version_available(session): track = session.track(78495659) assert track.name == "Bullitt" assert track.version == "Bonus Track" assert track.full_name == "Bullitt (Bonus Track)" -def test_full_name_track_3(session): - track = session.track(98849340) - assert track.name == "Magical place (feat. IOVA)" - assert track.version == "Dj Dark & MD Dj Remix" - assert track.full_name == "Magical place (feat. IOVA) (Dj Dark & MD Dj Remix)" +def test_full_name_track_version_none(session): + track = session.track(113210329) + assert track.name == "Enormous" + assert track.version is None + assert track.full_name == "Enormous" def test_track_media_metadata_tags(session): diff --git a/tidalapi/media.py b/tidalapi/media.py index 38fb9466..8cb4d2cb 100644 --- a/tidalapi/media.py +++ b/tidalapi/media.py @@ -367,7 +367,7 @@ def parse_track(self, json_obj: JsonObj, album: Optional[Album] = None) -> Track self.date_added = self.user_date_added self.description = json_obj.get("description") - self.version = json_obj.get("version") + self.version = json_obj.get("version") if json_obj.get("version") else None self.copyright = json_obj.get("copyright") self.bpm = json_obj.get("bpm")