🐛 Other API exceptions are now handled (#212)

* add retry logic for `res.json()`

* add wrapper for handling resources

* lower api limits
This commit is contained in:
Oskar Dudziński
2025-11-10 18:39:57 +01:00
committed by GitHub
parent 244a4bc07b
commit 0924b4c755
3 changed files with 47 additions and 11 deletions
+11 -1
View File
@@ -9,6 +9,7 @@ from rich.live import Live
from typing_extensions import Annotated
from tiddl.core.metadata import add_track_metadata, add_video_metadata, Cover
from tiddl.core.api import ApiError
from tiddl.core.api.models import Album, Track, Video, AlbumItemsCredits
from tiddl.core.utils.format import format_template
from tiddl.core.utils.m3u import save_tracks_to_m3u
@@ -492,7 +493,16 @@ def download_callback(
console=ctx.obj.console,
transient=True,
):
await asyncio.gather(*(handle_resource(r) for r in ctx.obj.resources))
async def wrapper(r: TidalResource):
try:
await handle_resource(r)
except ApiError as e:
ctx.obj.console.print(f"[red]API Error:[/] {e} at {r}")
except Exception as e:
ctx.obj.console.print(f"[red]Error:[/] {e} at {r}")
await asyncio.gather(*(wrapper(r) for r in ctx.obj.resources))
rich_output.show_stats()
+9 -9
View File
@@ -34,20 +34,20 @@ ID: TypeAlias = str | int
class Limits:
# TODO test every max limit
ARTIST_ALBUMS = 50
ARTIST_ALBUMS_MAX = 200
ARTIST_ALBUMS = 10
ARTIST_ALBUMS_MAX = 100
ARTIST_VIDEOS = 50
ARTIST_VIDEOS_MAX = 200
ARTIST_VIDEOS = 10
ARTIST_VIDEOS_MAX = 100
ALBUM_ITEMS = 100
ALBUM_ITEMS = 20
ALBUM_ITEMS_MAX = 100
PLAYLIST_ITEMS = 50
PLAYLIST_ITEMS_MAX = 200
PLAYLIST_ITEMS = 20
PLAYLIST_ITEMS_MAX = 100
MIX_ITEMS = 100
MIX_ITEMS_MAX = 200
MIX_ITEMS = 20
MIX_ITEMS_MAX = 100
class TidalAPI:
+27 -1
View File
@@ -4,6 +4,9 @@ from pathlib import Path
from typing import Any, Type, TypeVar
from pydantic import BaseModel
from time import sleep
from requests.exceptions import JSONDecodeError
from requests_cache import (
CachedSession,
StrOrPath,
@@ -15,6 +18,8 @@ from .exceptions import ApiError
T = TypeVar("T", bound=BaseModel)
API_URL = "https://api.tidal.com/v1"
MAX_RETRIES = 5
RETRY_DELAY = 2
log = getLogger(__name__)
@@ -48,6 +53,7 @@ class TidalClient:
endpoint: str,
params: dict[str, Any] = {},
expire_after: int = NEVER_EXPIRE,
_attempt: int = 1,
) -> T:
"""
Fetch data from the API endpoint
@@ -62,7 +68,27 @@ class TidalClient:
f"{endpoint} {params} '{'HIT' if res.from_cache else 'MISS'}' [{res.status_code}]",
)
data = res.json()
try:
data = res.json()
except JSONDecodeError as e:
if _attempt >= MAX_RETRIES:
log.error(f"JSON decode failed after {MAX_RETRIES} attempts: {e}")
raise ApiError(
status=res.status_code,
subStatus="0",
userMessage="Response body does not contain valid json.",
)
log.warning(f"JSON decode error, retrying {_attempt}/{MAX_RETRIES}")
sleep(RETRY_DELAY)
return self.fetch(
model=model,
endpoint=endpoint,
params=params,
expire_after=expire_after,
_attempt=_attempt + 1,
)
if self.debug_path:
file = self.debug_path / f"{endpoint}.json"