mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
retry
This commit is contained in:
parent
27c9752949
commit
c347865b9e
3 changed files with 34 additions and 3 deletions
|
|
@ -11,6 +11,7 @@ def call_with_retry(
|
||||||
max_retries: int = 5,
|
max_retries: int = 5,
|
||||||
backoff_base: float = 1.0,
|
backoff_base: float = 1.0,
|
||||||
backoff_multiplier: float = 2.0,
|
backoff_multiplier: float = 2.0,
|
||||||
|
max_backoff: float = 60.0,
|
||||||
) -> T:
|
) -> T:
|
||||||
last_exc: EpcRateLimitError | None = None
|
last_exc: EpcRateLimitError | None = None
|
||||||
for attempt in range(max_retries + 1):
|
for attempt in range(max_retries + 1):
|
||||||
|
|
@ -19,5 +20,9 @@ def call_with_retry(
|
||||||
except EpcRateLimitError as exc:
|
except EpcRateLimitError as exc:
|
||||||
last_exc = exc
|
last_exc = exc
|
||||||
if attempt < max_retries:
|
if attempt < max_retries:
|
||||||
time.sleep(backoff_base * (backoff_multiplier ** attempt))
|
if exc.retry_after is not None:
|
||||||
|
delay = exc.retry_after
|
||||||
|
else:
|
||||||
|
delay = backoff_base * (backoff_multiplier ** attempt)
|
||||||
|
time.sleep(min(delay, max_backoff))
|
||||||
raise last_exc # type: ignore[misc]
|
raise last_exc # type: ignore[misc]
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ from datatypes.epc.search import EpcSearchResult
|
||||||
|
|
||||||
class EpcClientService:
|
class EpcClientService:
|
||||||
BASE_URL = "https://api.get-energy-performance-data.communities.gov.uk"
|
BASE_URL = "https://api.get-energy-performance-data.communities.gov.uk"
|
||||||
|
REQUEST_TIMEOUT = 10.0
|
||||||
|
|
||||||
def __init__(self, auth_token: str) -> None:
|
def __init__(self, auth_token: str) -> None:
|
||||||
self._headers = {
|
self._headers = {
|
||||||
|
|
@ -25,6 +26,16 @@ class EpcClientService:
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_retry_after(resp: httpx.Response) -> Optional[float]:
|
||||||
|
header = resp.headers.get("Retry-After")
|
||||||
|
if header is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return float(header)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
def get_by_certificate_number(self, cert_num: str) -> EpcPropertyData:
|
def get_by_certificate_number(self, cert_num: str) -> EpcPropertyData:
|
||||||
raw = call_with_retry(lambda: self._fetch_certificate(cert_num))
|
raw = call_with_retry(lambda: self._fetch_certificate(cert_num))
|
||||||
return EpcPropertyDataMapper.from_api_response(raw)
|
return EpcPropertyDataMapper.from_api_response(raw)
|
||||||
|
|
@ -48,11 +59,15 @@ class EpcClientService:
|
||||||
f"{self.BASE_URL}/api/certificate",
|
f"{self.BASE_URL}/api/certificate",
|
||||||
params={"certificate_number": cert_num},
|
params={"certificate_number": cert_num},
|
||||||
headers=self._headers,
|
headers=self._headers,
|
||||||
|
timeout=self.REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
if resp.status_code == 404:
|
if resp.status_code == 404:
|
||||||
raise EpcNotFoundError(cert_num)
|
raise EpcNotFoundError(cert_num)
|
||||||
if resp.status_code == 429:
|
if resp.status_code == 429:
|
||||||
raise EpcRateLimitError("Rate limited by EPC API")
|
raise EpcRateLimitError(
|
||||||
|
"Rate limited by EPC API",
|
||||||
|
retry_after=self._parse_retry_after(resp),
|
||||||
|
)
|
||||||
if not resp.is_success:
|
if not resp.is_success:
|
||||||
raise EpcApiError(f"EPC API error {resp.status_code}: {resp.text}")
|
raise EpcApiError(f"EPC API error {resp.status_code}: {resp.text}")
|
||||||
return resp.json()["data"]
|
return resp.json()["data"]
|
||||||
|
|
@ -72,11 +87,15 @@ class EpcClientService:
|
||||||
f"{self.BASE_URL}/api/domestic/search",
|
f"{self.BASE_URL}/api/domestic/search",
|
||||||
params=params,
|
params=params,
|
||||||
headers=self._headers,
|
headers=self._headers,
|
||||||
|
timeout=self.REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
if resp.status_code == 404:
|
if resp.status_code == 404:
|
||||||
return []
|
return []
|
||||||
if resp.status_code == 429:
|
if resp.status_code == 429:
|
||||||
raise EpcRateLimitError("Rate limited by EPC API")
|
raise EpcRateLimitError(
|
||||||
|
"Rate limited by EPC API",
|
||||||
|
retry_after=self._parse_retry_after(resp),
|
||||||
|
)
|
||||||
if not resp.is_success:
|
if not resp.is_success:
|
||||||
raise EpcApiError(f"EPC API error {resp.status_code}: {resp.text}")
|
raise EpcApiError(f"EPC API error {resp.status_code}: {resp.text}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class EpcApiError(Exception):
|
class EpcApiError(Exception):
|
||||||
"""Base for all EPC client errors."""
|
"""Base for all EPC client errors."""
|
||||||
|
|
||||||
|
|
@ -8,3 +11,7 @@ class EpcNotFoundError(EpcApiError):
|
||||||
|
|
||||||
class EpcRateLimitError(EpcApiError):
|
class EpcRateLimitError(EpcApiError):
|
||||||
"""Raised when the API returns 429 and all retries are exhausted."""
|
"""Raised when the API returns 429 and all retries are exhausted."""
|
||||||
|
|
||||||
|
def __init__(self, message: str, retry_after: Optional[float] = None) -> None:
|
||||||
|
super().__init__(message)
|
||||||
|
self.retry_after = retry_after
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue