class DSPError(Exception): """Base exception for DSP errors.""" class DSPNotRegisteredError(DSPError): """Raised when a requested DSP is not registered in the gateway.""" def __init__(self, name: str) -> None: self.name = name super().__init__(f"DSP '{name}' is not registered") class TokenRefreshError(DSPError): """Raised when an OAuth token refresh fails (transient — network, 5xx).""" class TokenRevokedError(TokenRefreshError): """Raised when a refresh token is permanently invalid (invalid_grant). Indicates the fan revoked access or the token expired beyond recovery. Not retryable — fan should be skipped and marked stale. """ class StreamingAPIError(DSPError): """Raised when a DSP API call fails.""" def __init__( self, message: str, status_code: int | None = None, retry_after: float | None = None, ) -> None: self.status_code = status_code self.retry_after = retry_after super().__init__(message) class DSPWaitTimeoutError(DSPError): """Raised when waiting for a paused DSP to resume exceeds the deadline.""" def __init__(self, name: str) -> None: self.name = name super().__init__(f"Timed out waiting for DSP '{name}' to resume") class RateLimitError(StreamingAPIError): """Raised when Retry-After exceeds threshold or retries are exhausted. Signals severe throttling — caller should abort the batch for retry rather than skipping individual fans. """