mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2025-10-23 15:11:08 +00:00
feat(tags): Implement session management for API requests with retry logic
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user