from __future__ import annotations
from typing import Any
__all__ = [
'OGUError',
'OGUAPIError',
'OGUAuthenticationError',
'OGUAuthorizationError',
'OGUNotFoundError',
'OGUValidationError',
'OGURateLimitError',
'OGUServerError',
'OGUNetworkError',
'OGUTimeoutError',
'OGUParseError',
'OGUSessionError',
'OGULoginError',
'OGUReputationError',
'OGUCreditsError',
]
[docs]
class OGUError(Exception):
"""Base class for every exception raised by ``ogu-api``.
Catch this when you want to handle anything the SDK might throw.
"""
[docs]
class OGUNetworkError(OGUError):
"""Network-level failure: connection refused, DNS error, TLS handshake, etc."""
[docs]
class OGUTimeoutError(OGUNetworkError):
"""Request exceeded :attr:`HttpClientConfig.timeout_seconds`."""
[docs]
class OGUAPIError(OGUError):
"""HTTP error response from the forum (4xx / 5xx).
Subclassed by status-code-specific errors (``OGUNotFoundError``, etc.).
The exact subclass is chosen by :class:`HttpClient` based on the status.
Attributes:
status_code: HTTP status code.
method: HTTP method used (``'GET'``, ``'POST'``, …).
url: Full URL of the failing request.
body: Response body, if available, for inspection.
"""
def __init__(
self,
message: str,
*,
status_code: int,
method: str,
url: str,
body: str | None = None,
) -> None:
super().__init__(message)
self.status_code: int = status_code
self.method: str = method
self.url: str = url
self.body: str | None = body
def __str__(self) -> str:
base = super().__str__()
return f'{base} [{self.method} {self.url} -> {self.status_code}]'
[docs]
class OGUAuthenticationError(OGUAPIError):
"""HTTP 401: missing or invalid session cookie."""
[docs]
class OGUAuthorizationError(OGUAPIError):
"""HTTP 403: session is valid but the action isn't permitted."""
[docs]
class OGUNotFoundError(OGUAPIError):
"""HTTP 404: resource (profile, thread, page) not found."""
[docs]
class OGUValidationError(OGUAPIError):
"""HTTP 400 / 422: form or query parameters were rejected."""
[docs]
class OGURateLimitError(OGUAPIError):
"""HTTP 429: rate-limited by the forum or fronting CDN.
Attributes:
retry_after_seconds: ``Retry-After`` header value when present, else
``None``. The HTTP layer honors this automatically when retries
are configured.
"""
def __init__(
self,
message: str,
*,
status_code: int,
method: str,
url: str,
body: str | None = None,
retry_after_seconds: float | None = None,
) -> None:
super().__init__(
message,
status_code = status_code,
method = method,
url = url,
body = body,
)
self.retry_after_seconds: float | None = retry_after_seconds
[docs]
class OGUServerError(OGUAPIError):
"""HTTP 5xx: forum or upstream returned a server error."""
[docs]
class OGUParseError(OGUError):
"""A page came back, but expected HTML structure was missing.
Raised when an extractor needed a specific element that wasn't present —
e.g. profile pages without ``#profileLink``.
"""
def __init__(self, message: str = 'Failed to parse response') -> None:
super().__init__(message)
[docs]
class OGUSessionError(OGUError):
"""The session cookie is missing or has expired.
Raised by helpers that depend on a logged-in session.
"""
def __init__(self, message: str = 'Session is invalid or expired') -> None:
super().__init__(message)
[docs]
class OGULoginError(OGUError):
"""Login flow returned an error indicator from the forum."""
def __init__(self, message: str = 'Login failed') -> None:
super().__init__(message)
[docs]
class OGUReputationError(OGUError):
"""The forum rejected a reputation submission.
Most commonly raised when the value passed to
:meth:`ReputationResource.send` isn't in the target user's allowed
options.
Attributes:
valid_amounts: List of allowed reputation values returned by the
target's modal, or ``None`` when not available.
"""
def __init__(
self,
message: str = 'Reputation request failed',
*,
valid_amounts: list[str] | None = None,
) -> None:
super().__init__(message)
self.valid_amounts: list[str] | None = valid_amounts
[docs]
class OGUCreditsError(OGUError):
"""The forum rejected a credits transfer."""
def __init__(self, message: str = 'Credits request failed') -> None:
super().__init__(message)