11from __future__ import annotations
22
33import urllib .parse
4+ from itertools import islice , zip_longest
45from pathlib import Path
56from time import sleep
67from typing import TYPE_CHECKING , Any , TypeVar
1314from .authenticate import TidalToken
1415
1516if TYPE_CHECKING :
17+ from collections .abc import Iterable
18+
1619 from .api_types import (
1720 AlbumDocument ,
1821 Document ,
2629
2730
2831API_BASE = "https://openapi.tidal.com/v2"
32+ MAX_FILTER_SIZE = 20
33+
34+
35+ def _batched (iterable : Iterable [T ], n : int ) -> Iterable [list [T ]]:
36+ # FIXME: Replace with itertools.batched once
37+ # we upgrade to python > 3.12
38+ it = iter (iterable )
39+ while batch := list (islice (it , n )):
40+ yield batch
2941
3042
3143class TidalSession (TimeoutAndRetrySession ):
@@ -129,7 +141,7 @@ def get_paginated(
129141 ** kwargs ,
130142 )
131143 page_doc = res .json ()
132- doc = self ._merge_multiresource_pagination (doc , page_doc )
144+ doc = self .merge_multiresource_pagination (doc , page_doc )
133145
134146 # Dedupe include
135147 doc ["included" ] = list (
@@ -140,8 +152,8 @@ def get_paginated(
140152 )
141153 return doc
142154
143- def _merge_multiresource_pagination (
144- self ,
155+ @ staticmethod
156+ def merge_multiresource_pagination (
145157 a : Document [list [T ]],
146158 b : Document [list [T ]],
147159 ) -> Document [list [T ]]:
@@ -229,61 +241,80 @@ def search_results(
229241
230242 def get_tracks (
231243 self ,
232- # filters
233- ids : list [str ] | str | None = None ,
234- isrcs : list [str ] | str | None = None ,
235- * ,
236- include : list [str ] | str | None = None ,
244+ ids : list [str ] | None = None ,
245+ isrcs : list [str ] | None = None ,
246+ include : list [str ] | None = None ,
237247 country_code : str = "US" ,
238248 ) -> TrackDocument :
239249 """Fetch tracks resolving pagination and included items.
240250
241- Should only ever be called with 20 items as
242- tidal does not support more per requests. This does not mean more than
243- 20 cant be returned.
244-
245251 https://tidal-music.github.io/tidal-api-reference/#/tracks/get_tracks
246252 """
247- params : dict [str , str | list [str ]] = {}
248- if country_code :
249- params ["countryCode" ] = country_code
250- if ids :
251- params ["filter[id]" ] = ids
252- if isrcs :
253- params ["filter[isrc]" ] = isrcs
254-
255- return self .session .get_paginated (
256- f"{ API_BASE } /tracks" ,
257- include ,
258- params = params ,
259- )
253+ ids = ids or []
254+ isrcs = isrcs or []
255+
256+ # Tidal allows at max 20 filters per request. This needs a bit of extra
257+ # logic sadly.
258+ doc : TrackDocument = {
259+ "data" : [],
260+ "included" : [],
261+ }
262+ for id_batch , isrc_batch in zip_longest (
263+ _batched (ids , MAX_FILTER_SIZE ),
264+ _batched (isrcs , MAX_FILTER_SIZE ),
265+ fillvalue = (),
266+ ):
267+ params : dict [str , Any ] = {"countryCode" : country_code }
268+ if id_batch :
269+ params ["filter[id]" ] = id_batch
270+ if isrc_batch :
271+ params ["filter[isrc]" ] = isrc_batch
272+
273+ doc = self .session .merge_multiresource_pagination (
274+ doc ,
275+ self .session .get_paginated (
276+ f"{ API_BASE } /tracks" , include , params = params
277+ ),
278+ )
279+
280+ return doc
260281
261282 def get_albums (
262283 self ,
263- # filters
264- ids : list [str ] | str | None = None ,
265- barcode_ids : list [str ] | str | None = None ,
266- * ,
267- include : list [str ] | str | None = None ,
284+ ids : list [str ] | None = None ,
285+ barcode_ids : list [str ] | None = None ,
286+ include : list [str ] | None = None ,
268287 country_code : str = "US" ,
269288 ) -> AlbumDocument :
270289 """Fetch Albums resolving pagination and included items.
271290
272- Should only ever be called with 20 items as tidal does not support more per
273- requests. This does not mean more than 20 cant be returned.
274-
275291 https://tidal-music.github.io/tidal-api-reference/#/albums/get_albums
276292 """
277- params : dict [str , str | list [str ]] = {}
278- if country_code :
279- params ["countryCode" ] = country_code
280- if ids :
281- params ["filter[id]" ] = ids
282- if barcode_ids :
283- params ["filter[barcodeId]" ] = barcode_ids
284-
285- return self .session .get_paginated (
286- f"{ API_BASE } /albums" ,
287- include ,
288- params = params ,
289- )
293+ ids = ids or []
294+ barcode_ids = barcode_ids or []
295+
296+ # Tidal allows at max 20 filters per request. This needs a bit of extra
297+ # logic sadly.
298+ doc : AlbumDocument = {
299+ "data" : [],
300+ "included" : [],
301+ }
302+ for id_batch , barcode_batch in zip_longest (
303+ _batched (ids , MAX_FILTER_SIZE ),
304+ _batched (barcode_ids , MAX_FILTER_SIZE ),
305+ fillvalue = (),
306+ ):
307+ params : dict [str , Any ] = {"countryCode" : country_code }
308+ if id_batch :
309+ params ["filter[id]" ] = id_batch
310+ if barcode_batch :
311+ params ["filter[barcodeId]" ] = barcode_batch
312+
313+ doc = self .session .merge_multiresource_pagination (
314+ doc ,
315+ self .session .get_paginated (
316+ f"{ API_BASE } /albums" , include , params = params
317+ ),
318+ )
319+
320+ return doc
0 commit comments