11from __future__ import annotations
22
33import urllib .parse
4+ from itertools import batched , zip_longest
45from pathlib import Path
56from time import sleep
6- from typing import TYPE_CHECKING , Any , TypeVar
7+ from typing import TYPE_CHECKING , Any , TypeVar , overload
78
89import requests
910
2627
2728
2829API_BASE = "https://openapi.tidal.com/v2"
30+ MAX_FILTER_SIZE = 20
2931
3032
3133class TidalSession (TimeoutAndRetrySession ):
@@ -129,7 +131,7 @@ def get_paginated(
129131 ** kwargs ,
130132 )
131133 page_doc = res .json ()
132- doc = self ._merge_multiresource_pagination (doc , page_doc )
134+ doc = self .merge_multiresource_pagination (doc , page_doc )
133135
134136 # Dedupe include
135137 doc ["included" ] = list (
@@ -140,8 +142,8 @@ def get_paginated(
140142 )
141143 return doc
142144
143- def _merge_multiresource_pagination (
144- self ,
145+ @ staticmethod
146+ def merge_multiresource_pagination (
145147 a : Document [list [T ]],
146148 b : Document [list [T ]],
147149 ) -> Document [list [T ]]:
@@ -227,63 +229,120 @@ def search_results(
227229 params = params ,
228230 ).json ()
229231
232+ @overload
233+ def get_tracks (
234+ self ,
235+ * ,
236+ ids : list [str ] | str ,
237+ include : list [str ] | str | None = None ,
238+ country_code : str = "US" ,
239+ ) -> TrackDocument : ...
240+
241+ @overload
242+ def get_tracks (
243+ self ,
244+ * ,
245+ isrcs : list [str ] | str ,
246+ include : list [str ] | str | None = None ,
247+ country_code : str = "US" ,
248+ ) -> TrackDocument : ...
249+
230250 def get_tracks (
231251 self ,
232- # filters
233252 ids : list [str ] | str | None = None ,
234253 isrcs : list [str ] | str | None = None ,
235- * ,
236254 include : list [str ] | str | None = None ,
237255 country_code : str = "US" ,
238256 ) -> TrackDocument :
239257 """Fetch tracks resolving pagination and included items.
240258
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-
245259 https://tidal-music.github.io/tidal-api-reference/#/tracks/get_tracks
246260 """
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- )
261+ ids = [ids ] if isinstance (ids , str ) else ids or []
262+ isrcs = [isrcs ] if isinstance (isrcs , str ) else isrcs or []
263+
264+ # Tidal allows at max 20 filters per request. This needs a bit of extra
265+ # logic sadly.
266+ doc : TrackDocument = {
267+ "data" : [],
268+ "included" : [],
269+ }
270+ for id_batch , isrc_batch in zip_longest (
271+ batched (ids , MAX_FILTER_SIZE ),
272+ batched (isrcs , MAX_FILTER_SIZE ),
273+ fillvalue = (),
274+ ):
275+ params : dict [str , Any ] = {"countryCode" : country_code }
276+ if id_batch :
277+ params ["filter[id]" ] = id_batch
278+ if isrc_batch :
279+ params ["filter[isrc]" ] = isrc_batch
280+
281+ doc = self .session .merge_multiresource_pagination (
282+ doc ,
283+ self .session .get_paginated (
284+ f"{ API_BASE } /tracks" , include , params = params
285+ ),
286+ )
287+
288+ return doc
289+
290+ @overload
291+ def get_albums (
292+ self ,
293+ * ,
294+ ids : list [str ] | str ,
295+ include : list [str ] | str | None = None ,
296+ country_code : str = "US" ,
297+ ) -> AlbumDocument : ...
298+
299+ @overload
300+ def get_albums (
301+ self ,
302+ * ,
303+ barcode_ids : list [str ] | str ,
304+ include : list [str ] | str | None = None ,
305+ country_code : str = "US" ,
306+ ) -> AlbumDocument : ...
260307
261308 def get_albums (
262309 self ,
263- # filters
264310 ids : list [str ] | str | None = None ,
265311 barcode_ids : list [str ] | str | None = None ,
266- * ,
267312 include : list [str ] | str | None = None ,
268313 country_code : str = "US" ,
269314 ) -> AlbumDocument :
270315 """Fetch Albums resolving pagination and included items.
271316
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-
275317 https://tidal-music.github.io/tidal-api-reference/#/albums/get_albums
276318 """
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 ,
319+ ids = [ids ] if isinstance (ids , str ) else ids or []
320+ barcode_ids = (
321+ [barcode_ids ] if isinstance (barcode_ids , str ) else barcode_ids or []
289322 )
323+
324+ # Tidal allows at max 20 filters per request. This needs a bit of extra
325+ # logic sadly.
326+ doc : AlbumDocument = {
327+ "data" : [],
328+ "included" : [],
329+ }
330+ for id_batch , barcode_batch in zip_longest (
331+ batched (ids , MAX_FILTER_SIZE ),
332+ batched (barcode_ids , MAX_FILTER_SIZE ),
333+ fillvalue = (),
334+ ):
335+ params : dict [str , Any ] = {"countryCode" : country_code }
336+ if id_batch :
337+ params ["filter[id]" ] = id_batch
338+ if barcode_batch :
339+ params ["filter[barcodeId]" ] = barcode_batch
340+
341+ doc = self .session .merge_multiresource_pagination (
342+ doc ,
343+ self .session .get_paginated (
344+ f"{ API_BASE } /albums" , include , params = params
345+ ),
346+ )
347+
348+ return doc
0 commit comments