wrongmove/rec/exceptions.py
Viktor Barzin f833309297
Refactor backend for cleaner error handling, DRY, and type safety
- Extract rate limiter DRY: consolidate 3 duplicated check/respond paths
  into _check_counter and _enforce_limit helpers, add proper type annotations
- Replace bare Exception raises with FloorplanDownloadError and
  RightmoveApiError; narrow catch clauses to specific exception types;
  fix Step base class to inherit from ABC
- Consolidate MAX_OCR_WORKERS into config/scraper_config.py; extract
  _find_tenure_value helper to deduplicate tenure parsing
- Extract _build_poi_distances_lookup from stream endpoint to reduce nesting
- Fix csv_exporter: optional decisions.json, NaN instead of -1 sentinels,
  guard against division by zero on missing square meters
- Fix notifications.py broken list[Surface]() constructor, database.py
  stale comments and missing type annotation, auth.py type:ignore,
  ui_exporter.py stale TODO
- Fix 3 pre-existing test failures: mock cache layer in streaming tests,
  bypass rate limiter for test isolation, fix cache invalidation test to
  account for two-pattern scan loop
2026-02-10 22:19:24 +00:00

94 lines
2.1 KiB
Python

"""Custom exceptions for Rightmove API errors."""
class RightmoveAPIError(Exception):
"""Base exception for all Rightmove API errors."""
pass
class ThrottlingError(RightmoveAPIError):
"""Base exception for throttling-related errors.
Indicates that Rightmove is limiting our requests and we should back off.
"""
pass
class RateLimitError(ThrottlingError):
"""HTTP 429 - Too Many Requests.
Rightmove is explicitly rate limiting our requests.
"""
pass
class ServiceUnavailableError(ThrottlingError):
"""HTTP 503 - Service Unavailable.
Rightmove's service is temporarily unavailable, possibly due to overload.
"""
pass
class IPBlockedError(ThrottlingError):
"""HTTP 403 - Forbidden (IP blocked).
Our IP may be blocked or blacklisted by Rightmove.
"""
pass
class SlowResponseError(ThrottlingError):
"""Response time exceeded threshold.
API is responding very slowly, indicating potential throttling or overload.
"""
pass
class UnexpectedEmptyResponseError(RightmoveAPIError):
"""Empty response received when data was expected."""
pass
class InvalidResponseError(RightmoveAPIError):
"""Response contains error messages or invalid data."""
pass
class CircuitBreakerOpenError(RightmoveAPIError):
"""Circuit breaker is open, requests are being blocked.
The circuit breaker has detected too many failures and is preventing
further requests to allow the service to recover.
"""
pass
class FloorplanDownloadError(Exception):
"""Raised when a floorplan image download fails."""
def __init__(self, url: str, status_code: int) -> None:
self.url = url
self.status_code = status_code
super().__init__(f"HTTP {status_code} downloading floorplan from {url}")
class RoutingApiError(Exception):
"""Error from the Google Routes API."""
def __init__(self, status_code: int, response_body: dict):
self.status_code = status_code
self.response_body = response_body
super().__init__(
f"Routes API returned status {status_code}: {response_body}"
)