Skip to content

Commit 6f3c5eb

Browse files
committed
Added batching for max filter size.
1 parent 90f081e commit 6f3c5eb

1 file changed

Lines changed: 99 additions & 40 deletions

File tree

beetsplug/tidal/api.py

Lines changed: 99 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from __future__ import annotations
22

33
import urllib.parse
4+
from itertools import batched, zip_longest
45
from pathlib import Path
56
from time import sleep
6-
from typing import TYPE_CHECKING, Any, TypeVar
7+
from typing import TYPE_CHECKING, Any, TypeVar, overload
78

89
import requests
910

@@ -26,6 +27,7 @@
2627

2728

2829
API_BASE = "https://openapi.tidal.com/v2"
30+
MAX_FILTER_SIZE = 20
2931

3032

3133
class 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

Comments
 (0)