from yuxi.channel.errors import ErrorSeverity class UrbitError(Exception): pass class UrbitUrlError(UrbitError): pass class UrbitHttpError(UrbitError): def __init__(self, status: int, body: str = ""): self.status = status self.body = body super().__init__(f"HTTP {status}: {body}") @property def severity(self) -> ErrorSeverity: if self.status == 429: return ErrorSeverity.RATE_LIMITED if self.status in (401, 403): return ErrorSeverity.FORBIDDEN if self.status >= 500: return ErrorSeverity.RETRYABLE if self.status >= 400: return ErrorSeverity.FATAL return ErrorSeverity.RETRYABLE class UrbitAuthError(UrbitError): pass class UrbitSSEError(UrbitError): pass class UrbitPokeError(UrbitError): pass class UrbitScryError(UrbitError): pass _ERROR_SEVERITY_MAP = { UrbitAuthError: ErrorSeverity.FATAL, UrbitSSEError: ErrorSeverity.RETRYABLE, UrbitPokeError: ErrorSeverity.RETRYABLE, UrbitScryError: ErrorSeverity.RETRYABLE, UrbitUrlError: ErrorSeverity.FATAL, } def classify_error(error: BaseException) -> object: from yuxi.channel.protocols import ClassifiedError if isinstance(error, UrbitHttpError): return ClassifiedError(severity=error.severity, original_error=error, error_message=str(error)) severity = ErrorSeverity.RETRYABLE for exc_type, sev in _ERROR_SEVERITY_MAP.items(): if isinstance(error, exc_type): severity = sev break return ClassifiedError(severity=severity, original_error=error, error_message=str(error)) def is_retryable(error: BaseException) -> bool: if isinstance(error, UrbitHttpError): return error.severity in (ErrorSeverity.RETRYABLE, ErrorSeverity.RATE_LIMITED) if isinstance(error, UrbitAuthError): return False if isinstance(error, UrbitUrlError): return False return isinstance(error, UrbitError)