Skip to content

Commit 7c7a831

Browse files
committed
Added batching for max filter size.
1 parent 90f081e commit 7c7a831

1 file changed

Lines changed: 77 additions & 46 deletions

File tree

beetsplug/tidal/api.py

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import urllib.parse
4+
from itertools import islice, zip_longest
45
from pathlib import Path
56
from time import sleep
67
from typing import TYPE_CHECKING, Any, TypeVar
@@ -13,6 +14,8 @@
1314
from .authenticate import TidalToken
1415

1516
if TYPE_CHECKING:
17+
from collections.abc import Iterable
18+
1619
from .api_types import (
1720
AlbumDocument,
1821
Document,
@@ -26,6 +29,15 @@
2629

2730

2831
API_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

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

Comments
 (0)