diff --git a/models/schedule.py b/models/schedule.py index 2916b8c..6f10de0 100644 --- a/models/schedule.py +++ b/models/schedule.py @@ -10,7 +10,7 @@ import time from typing import Any from urllib.error import HTTPError, URLError from urllib.parse import quote -from urllib.request import Request, urlopen +from urllib.request import ProxyHandler, Request, build_opener from zoneinfo import ZoneInfo import config @@ -30,9 +30,11 @@ MOSCOW = ZoneInfo("Europe/Moscow") _CACHE_TTL = 300.0 _REQUEST_TIMEOUT = 10.0 _FAILURE_RETRY_DELAY = 30.0 +_REQUEST_ATTEMPTS = 2 _cache: dict[str, tuple[float, list[dict[str, Any]]]] = {} _failures: dict[str, tuple[float, str]] = {} _cache_lock = threading.RLock() +_direct_opener = build_opener(ProxyHandler({})) class ScheduleAPIError(RuntimeError): @@ -55,19 +57,32 @@ def _load_result(path: str) -> list[dict[str, Any]]: return cached[1] raise ScheduleAPIError(failure[1]) - request = Request(_api_url(path), headers={"Accept": "application/json"}) - try: - with urlopen(request, timeout=_REQUEST_TIMEOUT) as response: - payload = json.load(response) - except (HTTPError, URLError, TimeoutError, OSError, ValueError) as error: - message = f"Не удалось получить {path}: {error}" + url = _api_url(path) + request = Request(url, headers={"Accept": "application/json"}) + error: Exception | None = None + payload: Any = None + for _ in range(_REQUEST_ATTEMPTS): + try: + with _direct_opener.open(request, timeout=_REQUEST_TIMEOUT) as response: + payload = json.load(response) + error = None + break + except (HTTPError, URLError, TimeoutError, OSError, ValueError) as attempt_error: + error = attempt_error + + if error is not None: + message = f"Не удалось получить {url}: {error}" with _cache_lock: _failures[path] = (now + _FAILURE_RETRY_DELAY, message) if cached: return cached[1] raise ScheduleAPIError(message) from error - if payload.get("ok") is not True or not isinstance(payload.get("result"), list): + if ( + not isinstance(payload, dict) + or payload.get("ok") is not True + or not isinstance(payload.get("result"), list) + ): if cached: return cached[1] raise ScheduleAPIError(f"Сервер вернул некорректный ответ для {path}")