v0.5.59: item counter evidence for fears, catch up Web features to site version.
Ship OpenDota counter-stats reordering for feared items, finalize SITE_VERSION/docs for rankings/streamers/trends/matches/mechanics and draft archetypes, and ignore regenerable Web data caches. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+56
-6
@@ -14,20 +14,70 @@ from pathlib import Path
|
||||
|
||||
UA = "climperor"
|
||||
DEFAULT_TIMEOUT = 60
|
||||
# OpenDota and similar APIs occasionally 429; back off before failing CI/refresh.
|
||||
DEFAULT_RETRIES = 4
|
||||
DEFAULT_RETRY_BACKOFF = 5.0
|
||||
|
||||
HEROES_URL = "https://www.dota2.com/datafeed/herolist?language={lang}"
|
||||
ITEMLIST_URL = "https://www.dota2.com/datafeed/itemlist?language={lang}"
|
||||
|
||||
|
||||
def http_json(url: str, *, timeout: int = DEFAULT_TIMEOUT) -> dict | list:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
def _http_open(url: str, *, timeout: int, retries: int, backoff: float):
|
||||
"""urlopen with retries on 429 / 5xx / transient network errors."""
|
||||
last_err: BaseException | None = None
|
||||
for attempt in range(retries + 1):
|
||||
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
||||
try:
|
||||
return urllib.request.urlopen(req, timeout=timeout)
|
||||
except urllib.error.HTTPError as e:
|
||||
last_err = e
|
||||
if e.code not in (429, 500, 502, 503, 504) or attempt >= retries:
|
||||
raise
|
||||
sleep_s = backoff * (2**attempt)
|
||||
retry_after = e.headers.get("Retry-After") if e.headers else None
|
||||
if retry_after:
|
||||
try:
|
||||
sleep_s = max(sleep_s, float(retry_after))
|
||||
except ValueError:
|
||||
pass
|
||||
print(
|
||||
f"HTTP {e.code} {url} — retry {attempt + 1}/{retries} in {sleep_s:.0f}s",
|
||||
flush=True,
|
||||
)
|
||||
time.sleep(sleep_s)
|
||||
except (urllib.error.URLError, TimeoutError, OSError) as e:
|
||||
last_err = e
|
||||
if attempt >= retries:
|
||||
raise
|
||||
sleep_s = backoff * (2**attempt)
|
||||
print(
|
||||
f"HTTP error {e} {url} — retry {attempt + 1}/{retries} in {sleep_s:.0f}s",
|
||||
flush=True,
|
||||
)
|
||||
time.sleep(sleep_s)
|
||||
assert last_err is not None
|
||||
raise last_err
|
||||
|
||||
|
||||
def http_json(
|
||||
url: str,
|
||||
*,
|
||||
timeout: int = DEFAULT_TIMEOUT,
|
||||
retries: int = DEFAULT_RETRIES,
|
||||
backoff: float = DEFAULT_RETRY_BACKOFF,
|
||||
) -> dict | list:
|
||||
with _http_open(url, timeout=timeout, retries=retries, backoff=backoff) as resp:
|
||||
return json.loads(resp.read().decode())
|
||||
|
||||
|
||||
def http_bytes(url: str, *, timeout: int = DEFAULT_TIMEOUT) -> bytes:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
def http_bytes(
|
||||
url: str,
|
||||
*,
|
||||
timeout: int = DEFAULT_TIMEOUT,
|
||||
retries: int = DEFAULT_RETRIES,
|
||||
backoff: float = DEFAULT_RETRY_BACKOFF,
|
||||
) -> bytes:
|
||||
with _http_open(url, timeout=timeout, retries=retries, backoff=backoff) as resp:
|
||||
return resp.read()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user