feat(tags): Implement session management for API requests with retry logic

This commit is contained in:
Andy
2025-08-14 02:14:46 +00:00
parent 8e598f7d6a
commit 990084ab1f

View File

@@ -10,6 +10,7 @@ from pathlib import Path
from typing import Optional, Tuple from typing import Optional, Tuple
import requests import requests
from requests.adapters import HTTPAdapter, Retry
from unshackle.core import binaries from unshackle.core import binaries
from unshackle.core.config import config from unshackle.core.config import config
@@ -25,6 +26,22 @@ HEADERS = {"User-Agent": "unshackle-tags/1.0"}
log = logging.getLogger("TAGS") log = logging.getLogger("TAGS")
def _get_session() -> requests.Session:
"""Create a requests session with retry logic for network failures."""
session = requests.Session()
session.headers.update(HEADERS)
retry = Retry(
total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
def _api_key() -> Optional[str]: def _api_key() -> Optional[str]:
return config.tmdb_api_key or os.getenv("TMDB_API_KEY") return config.tmdb_api_key or os.getenv("TMDB_API_KEY")
@@ -59,7 +76,8 @@ def search_simkl(title: str, year: Optional[int], kind: str) -> Tuple[Optional[d
filename += " 2160p.mkv" filename += " 2160p.mkv"
try: try:
resp = requests.post("https://api.simkl.com/search/file", json={"file": filename}, headers=HEADERS, timeout=30) session = _get_session()
resp = session.post("https://api.simkl.com/search/file", json={"file": filename}, timeout=30)
resp.raise_for_status() resp.raise_for_status()
data = resp.json() data = resp.json()
log.debug("Simkl API response received") log.debug("Simkl API response received")
@@ -139,10 +157,11 @@ def search_tmdb(title: str, year: Optional[int], kind: str) -> Tuple[Optional[in
if year is not None: if year is not None:
params["year" if kind == "movie" else "first_air_date_year"] = year params["year" if kind == "movie" else "first_air_date_year"] = year
r = requests.get( try:
session = _get_session()
r = session.get(
f"https://api.themoviedb.org/3/search/{kind}", f"https://api.themoviedb.org/3/search/{kind}",
params=params, params=params,
headers=HEADERS,
timeout=30, timeout=30,
) )
r.raise_for_status() r.raise_for_status()
@@ -151,6 +170,9 @@ def search_tmdb(title: str, year: Optional[int], kind: str) -> Tuple[Optional[in
log.debug("TMDB returned %d results", len(results)) log.debug("TMDB returned %d results", len(results))
if not results: if not results:
return None, None return None, None
except requests.RequestException as exc:
log.warning("Failed to search TMDB for %s: %s", title, exc)
return None, None
best_ratio = 0.0 best_ratio = 0.0
best_id: Optional[int] = None best_id: Optional[int] = None
@@ -196,10 +218,10 @@ def get_title(tmdb_id: int, kind: str) -> Optional[str]:
return None return None
try: try:
r = requests.get( session = _get_session()
r = session.get(
f"https://api.themoviedb.org/3/{kind}/{tmdb_id}", f"https://api.themoviedb.org/3/{kind}/{tmdb_id}",
params={"api_key": api_key}, params={"api_key": api_key},
headers=HEADERS,
timeout=30, timeout=30,
) )
r.raise_for_status() r.raise_for_status()
@@ -219,10 +241,10 @@ def get_year(tmdb_id: int, kind: str) -> Optional[int]:
return None return None
try: try:
r = requests.get( session = _get_session()
r = session.get(
f"https://api.themoviedb.org/3/{kind}/{tmdb_id}", f"https://api.themoviedb.org/3/{kind}/{tmdb_id}",
params={"api_key": api_key}, params={"api_key": api_key},
headers=HEADERS,
timeout=30, timeout=30,
) )
r.raise_for_status() r.raise_for_status()
@@ -243,16 +265,21 @@ def external_ids(tmdb_id: int, kind: str) -> dict:
return {} return {}
url = f"https://api.themoviedb.org/3/{kind}/{tmdb_id}/external_ids" url = f"https://api.themoviedb.org/3/{kind}/{tmdb_id}/external_ids"
log.debug("Fetching external IDs for %s %s", kind, tmdb_id) log.debug("Fetching external IDs for %s %s", kind, tmdb_id)
r = requests.get(
try:
session = _get_session()
r = session.get(
url, url,
params={"api_key": api_key}, params={"api_key": api_key},
headers=HEADERS,
timeout=30, timeout=30,
) )
r.raise_for_status() r.raise_for_status()
js = r.json() js = r.json()
log.debug("External IDs response: %s", js) log.debug("External IDs response: %s", js)
return js return js
except requests.RequestException as exc:
log.warning("Failed to fetch external IDs for %s %s: %s", kind, tmdb_id, exc)
return {}
def _apply_tags(path: Path, tags: dict[str, str]) -> None: def _apply_tags(path: Path, tags: dict[str, str]) -> None: