add incremental enrich controls with progress and retry limits
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Douban provider implementation.
|
||||
|
||||
This provider is optional. If no endpoint is configured, it silently degrades.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import Optional
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import urlopen, Request
|
||||
|
||||
from vlm.providers.base import ProviderResult
|
||||
|
||||
|
||||
class DoubanProvider:
|
||||
"""Fetch reputation data from a configurable Douban-compatible API."""
|
||||
|
||||
name = "douban"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str],
|
||||
endpoint: Optional[str] = None,
|
||||
timeout_seconds: int = 6,
|
||||
retries: int = 2,
|
||||
min_interval_seconds: float = 0.4,
|
||||
) -> None:
|
||||
self.api_key = api_key
|
||||
self.endpoint = endpoint
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.retries = retries
|
||||
self.min_interval_seconds = min_interval_seconds
|
||||
self._last_request_at = 0.0
|
||||
|
||||
def enrich(self, *, title: str, media_type: str, year: Optional[int] = None) -> Optional[ProviderResult]:
|
||||
if not self.endpoint:
|
||||
return None
|
||||
|
||||
params = {
|
||||
"q": title,
|
||||
"type": media_type,
|
||||
}
|
||||
if year is not None:
|
||||
params["year"] = year
|
||||
if self.api_key:
|
||||
params["api_key"] = self.api_key
|
||||
|
||||
data = self._get_json(self.endpoint, params)
|
||||
if not data:
|
||||
return None
|
||||
|
||||
items = data.get("items") or data.get("subjects") or []
|
||||
if not items:
|
||||
return None
|
||||
|
||||
item = items[0]
|
||||
score = item.get("rating") or item.get("score")
|
||||
votes = item.get("vote_count") or item.get("ratings_count")
|
||||
title_zh = item.get("title")
|
||||
title_en = item.get("original_title")
|
||||
|
||||
return ProviderResult(
|
||||
provider=self.name,
|
||||
canonical_id=f"douban:{item.get('id', 'unknown')}",
|
||||
title_zh=title_zh,
|
||||
title_en=title_en,
|
||||
translation_source=self.name if title_zh or title_en else None,
|
||||
reputation_score=float(score) if score is not None else None,
|
||||
reputation_votes=int(votes) if votes is not None else None,
|
||||
reputation_source=self.name,
|
||||
raw_metadata={"id": str(item.get("id", ""))},
|
||||
)
|
||||
|
||||
def _wait_for_rate_limit(self) -> None:
|
||||
if self.min_interval_seconds <= 0:
|
||||
return
|
||||
now = time.monotonic()
|
||||
elapsed = now - self._last_request_at
|
||||
if elapsed < self.min_interval_seconds:
|
||||
time.sleep(self.min_interval_seconds - elapsed)
|
||||
|
||||
def _get_json(self, url: str, params: dict) -> Optional[dict]:
|
||||
full_url = f"{url}?{urlencode(params)}"
|
||||
request = Request(full_url, headers={"Accept": "application/json"})
|
||||
for _ in range(max(self.retries + 1, 1)):
|
||||
self._wait_for_rate_limit()
|
||||
try:
|
||||
with urlopen(request, timeout=self.timeout_seconds) as response:
|
||||
payload = response.read().decode("utf-8")
|
||||
self._last_request_at = time.monotonic()
|
||||
return json.loads(payload)
|
||||
except Exception:
|
||||
self._last_request_at = time.monotonic()
|
||||
continue
|
||||
return None
|
||||
Reference in New Issue
Block a user