189 lines
5.9 KiB
Python
189 lines
5.9 KiB
Python
"""Provide exception classes for the prawcore package."""
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
from urllib.parse import urlparse
|
|
|
|
if TYPE_CHECKING:
|
|
from requests.models import Response
|
|
|
|
|
|
class PrawcoreException(Exception): # noqa: N818
|
|
"""Base exception class for exceptions that occur within this package."""
|
|
|
|
|
|
class InvalidInvocation(PrawcoreException):
|
|
"""Indicate that the code to execute cannot be completed."""
|
|
|
|
|
|
class OAuthException(PrawcoreException):
|
|
"""Indicate that there was an OAuth2 related error with the request."""
|
|
|
|
def __init__(
|
|
self, response: Response, error: str, description: str | None = None
|
|
) -> None:
|
|
"""Initialize a OAuthException instance.
|
|
|
|
:param response: A ``requests.response`` instance.
|
|
:param error: The error type returned by Reddit.
|
|
:param description: A description of the error when provided.
|
|
|
|
"""
|
|
self.error = error
|
|
self.description = description
|
|
self.response = response
|
|
message = f"{error} error processing request"
|
|
if description:
|
|
message += f" ({description})"
|
|
PrawcoreException.__init__(self, message)
|
|
|
|
|
|
class RequestException(PrawcoreException):
|
|
"""Indicate that there was an error with the incomplete HTTP request."""
|
|
|
|
def __init__(
|
|
self,
|
|
original_exception: Exception,
|
|
request_args: tuple[Any, ...],
|
|
request_kwargs: dict[
|
|
str, bool | (dict[str, int] | (dict[str, str] | str)) | None
|
|
],
|
|
) -> None:
|
|
"""Initialize a RequestException instance.
|
|
|
|
:param original_exception: The original exception that occurred.
|
|
:param request_args: The arguments to the request function.
|
|
:param request_kwargs: The keyword arguments to the request function.
|
|
|
|
"""
|
|
self.original_exception = original_exception
|
|
self.request_args = request_args
|
|
self.request_kwargs = request_kwargs
|
|
super().__init__(f"error with request {original_exception}")
|
|
|
|
|
|
class ResponseException(PrawcoreException):
|
|
"""Indicate that there was an error with the completed HTTP request."""
|
|
|
|
def __init__(self, response: Response) -> None:
|
|
"""Initialize a ResponseException instance.
|
|
|
|
:param response: A ``requests.response`` instance.
|
|
|
|
"""
|
|
self.response = response
|
|
super().__init__(f"received {response.status_code} HTTP response")
|
|
|
|
|
|
class BadJSON(ResponseException):
|
|
"""Indicate the response did not contain valid JSON."""
|
|
|
|
|
|
class BadRequest(ResponseException):
|
|
"""Indicate invalid parameters for the request."""
|
|
|
|
|
|
class Conflict(ResponseException):
|
|
"""Indicate a conflicting change in the target resource."""
|
|
|
|
|
|
class Forbidden(ResponseException):
|
|
"""Indicate the authentication is not permitted for the request."""
|
|
|
|
|
|
class InsufficientScope(ResponseException):
|
|
"""Indicate that the request requires a different scope."""
|
|
|
|
|
|
class InvalidToken(ResponseException):
|
|
"""Indicate that the request used an invalid access token."""
|
|
|
|
|
|
class NotFound(ResponseException):
|
|
"""Indicate that the requested URL was not found."""
|
|
|
|
|
|
class Redirect(ResponseException):
|
|
"""Indicate the request resulted in a redirect.
|
|
|
|
This class adds the attribute ``path``, which is the path to which the response
|
|
redirects.
|
|
|
|
"""
|
|
|
|
def __init__(self, response: Response) -> None:
|
|
"""Initialize a Redirect exception instance.
|
|
|
|
:param response: A ``requests.response`` instance containing a location header.
|
|
|
|
"""
|
|
path = urlparse(response.headers["location"]).path
|
|
self.path = path[:-5] if path.endswith(".json") else path
|
|
self.response = response
|
|
msg = f"Redirect to {self.path}"
|
|
msg += (
|
|
" (You may be trying to perform a non-read-only action via a "
|
|
"read-only instance.)"
|
|
if "/login/" in self.path
|
|
else ""
|
|
)
|
|
PrawcoreException.__init__(self, msg)
|
|
|
|
|
|
class ServerError(ResponseException):
|
|
"""Indicate issues on the server end preventing request fulfillment."""
|
|
|
|
|
|
class SpecialError(ResponseException):
|
|
"""Indicate syntax or spam-prevention issues."""
|
|
|
|
def __init__(self, response: Response) -> None:
|
|
"""Initialize a SpecialError exception instance.
|
|
|
|
:param response: A ``requests.response`` instance containing a message and a
|
|
list of special errors.
|
|
|
|
"""
|
|
self.response = response
|
|
|
|
resp_dict = self.response.json() # assumes valid JSON
|
|
self.message = resp_dict.get("message", "")
|
|
self.reason = resp_dict.get("reason", "")
|
|
self.special_errors = resp_dict.get("special_errors", [])
|
|
PrawcoreException.__init__(self, f"Special error {self.message!r}")
|
|
|
|
|
|
class TooLarge(ResponseException):
|
|
"""Indicate that the request data exceeds the allowed limit."""
|
|
|
|
|
|
class TooManyRequests(ResponseException):
|
|
"""Indicate that the user has sent too many requests in a given amount of time."""
|
|
|
|
def __init__(self, response: Response) -> None:
|
|
"""Initialize a TooManyRequests exception instance.
|
|
|
|
:param response: A ``requests.response`` instance that may contain a retry-after
|
|
header and a message.
|
|
|
|
"""
|
|
self.response = response
|
|
self.retry_after = response.headers.get("retry-after")
|
|
self.message = response.text # Not all response bodies are valid JSON
|
|
|
|
msg = f"received {response.status_code} HTTP response"
|
|
if self.retry_after:
|
|
msg += (
|
|
f". Please wait at least {float(self.retry_after)} seconds before"
|
|
f" re-trying this request."
|
|
)
|
|
PrawcoreException.__init__(self, msg)
|
|
|
|
|
|
class URITooLong(ResponseException):
|
|
"""Indicate that the length of the request URI exceeds the allowed limit."""
|
|
|
|
|
|
class UnavailableForLegalReasons(ResponseException):
|
|
"""Indicate that the requested URL is unavailable due to legal reasons."""
|