2026-02-01 09:31:38 +01:00

308 lines
10 KiB
Python

"""PRAW exception classes.
Includes two main exceptions: :class:`.RedditAPIException` for when something goes wrong
on the server side, and :class:`.ClientException` when something goes wrong on the
client side. Both of these classes extend :class:`.PRAWException`.
All other exceptions are subclassed from :class:`.ClientException`.
"""
from __future__ import annotations
from typing import Any
from warnings import warn
from .util import _deprecate_args
class PRAWException(Exception):
"""The base PRAW Exception that all other exception classes extend."""
class RedditErrorItem:
"""Represents a single error returned from Reddit's API."""
@property
def error_message(self) -> str:
"""Get the completed error message string."""
error_str = self.error_type
if self.message:
error_str += f": {self.message!r}"
if self.field:
error_str += f" on field {self.field!r}"
return error_str
def __eq__(self, other: RedditErrorItem | list[str]) -> bool:
"""Check for equality."""
if isinstance(other, RedditErrorItem):
return (self.error_type, self.message, self.field) == (
other.error_type,
other.message,
other.field,
)
return super().__eq__(other)
@_deprecate_args("error_type", "message", "field")
def __init__(
self,
error_type: str,
*,
field: str | None = None,
message: str | None = None,
):
"""Initialize a :class:`.RedditErrorItem` instance.
:param error_type: The error type set on Reddit's end.
:param field: The input field associated with the error, if available.
:param message: The associated message for the error.
"""
self.error_type = error_type
self.message = message
self.field = field
def __repr__(self) -> str:
"""Return an object initialization representation of the instance."""
return (
f"{self.__class__.__name__}(error_type={self.error_type!r},"
f" message={self.message!r}, field={self.field!r})"
)
def __str__(self) -> str:
"""Get the message returned from str(self)."""
return self.error_message
class ClientException(PRAWException):
"""Indicate exceptions that don't involve interaction with Reddit's API."""
class DuplicateReplaceException(ClientException):
"""Indicate exceptions that involve the replacement of :class:`.MoreComments`."""
def __init__(self):
"""Initialize a :class:`.DuplicateReplaceException` instance."""
super().__init__(
"A duplicate comment has been detected. Are you attempting to call"
" 'replace_more_comments' more than once?"
)
class InvalidFlairTemplateID(ClientException):
"""Indicate exceptions where an invalid flair template ID is given."""
def __init__(self, template_id: str):
"""Initialize an :class:`.InvalidFlairTemplateID` instance."""
super().__init__(
f"The flair template ID '{template_id}' is invalid. If you are trying to"
" create a flair, please use the 'add' method."
)
class InvalidImplicitAuth(ClientException):
"""Indicate exceptions where an implicit auth type is used incorrectly."""
def __init__(self):
"""Initialize an :class:`.InvalidImplicitAuth` instance."""
super().__init__("Implicit authorization can only be used with installed apps.")
class InvalidURL(ClientException):
"""Indicate exceptions where an invalid URL is entered."""
@_deprecate_args("url", "message")
def __init__(self, url: str, *, message: str = "Invalid URL: {}"):
"""Initialize an :class:`.InvalidURL` instance.
:param url: The invalid URL.
:param message: The message to display. Must contain a format identifier (``{}``
or ``{0}``) (default: ``"Invalid URL: {}"``).
"""
super().__init__(message.format(url))
class MissingRequiredAttributeException(ClientException):
"""Indicate exceptions caused by not including a required attribute."""
class ReadOnlyException(ClientException):
"""Raised when a method call requires :attr:`.read_only` mode to be disabled."""
class TooLargeMediaException(ClientException):
"""Indicate exceptions from uploading media that's too large."""
@_deprecate_args("maximum_size", "actual")
def __init__(self, *, actual: int, maximum_size: int):
"""Initialize a :class:`.TooLargeMediaException` instance.
:param actual: The actual size of the uploaded media.
:param maximum_size: The maximum size of the uploaded media.
"""
self.maximum_size = maximum_size
self.actual = actual
super().__init__(
f"The media that you uploaded was too large (maximum size is {maximum_size}"
f" bytes, uploaded {actual} bytes)"
)
class WebSocketException(ClientException):
"""Indicate exceptions caused by use of WebSockets."""
@property
def original_exception(self) -> Exception:
"""Access the ``original_exception`` attribute (now deprecated)."""
warn(
"Accessing the attribute 'original_exception' is deprecated. Please rewrite"
" your code in such a way that this attribute does not need to be used. It"
" will be removed in PRAW 8.0.",
category=DeprecationWarning,
stacklevel=2,
)
return self._original_exception
@original_exception.setter
def original_exception(self, value: Exception):
self._original_exception = value
@original_exception.deleter
def original_exception(self):
del self._original_exception
def __init__(self, message: str, exception: Exception | None):
"""Initialize a :class:`.WebSocketException` instance.
:param message: The exception message.
:param exception: The exception thrown by the websocket library.
.. note::
This parameter is deprecated. It will be removed in PRAW 8.0.
"""
super().__init__(message)
self._original_exception = exception
class MediaPostFailed(WebSocketException):
"""Indicate exceptions where media uploads failed.."""
def __init__(self):
"""Initialize a :class:`.MediaPostFailed` instance."""
super().__init__(
"The attempted media upload action has failed. Possible causes include the"
" corruption of media files. Check that the media file can be opened on"
" your local machine.",
None,
)
class APIException(PRAWException):
"""Old class preserved for alias purposes.
.. deprecated:: 7.0
Class :class:`.APIException` has been deprecated in favor of
:class:`.RedditAPIException`. This class will be removed in PRAW 8.0.
"""
@staticmethod
def parse_exception_list(
exceptions: list[RedditErrorItem | list[str]],
) -> list[RedditErrorItem]:
"""Covert an exception list into a :class:`.RedditErrorItem` list."""
return [
(
exception
if isinstance(exception, RedditErrorItem)
else RedditErrorItem(
error_type=exception[0],
field=exception[2] if bool(exception[2]) else "",
message=exception[1] if bool(exception[1]) else "",
)
)
for exception in exceptions
]
@property
def error_type(self) -> str:
"""Get error_type.
.. deprecated:: 7.0
Accessing attributes through instances of :class:`.RedditAPIException` is
deprecated. This behavior will be removed in PRAW 8.0. Check out the
:ref:`PRAW 7 Migration tutorial <Exception_Handling>` on how to migrate code
from this behavior.
"""
return self._get_old_attr("error_type")
@property
def field(self) -> str:
"""Get field.
.. deprecated:: 7.0
Accessing attributes through instances of :class:`.RedditAPIException` is
deprecated. This behavior will be removed in PRAW 8.0. Check out the
:ref:`PRAW 7 Migration tutorial <Exception_Handling>` on how to migrate code
from this behavior.
"""
return self._get_old_attr("field")
@property
def message(self) -> str:
"""Get message.
.. deprecated:: 7.0
Accessing attributes through instances of :class:`.RedditAPIException` is
deprecated. This behavior will be removed in PRAW 8.0. Check out the
:ref:`PRAW 7 Migration tutorial <Exception_Handling>` on how to migrate code
from this behavior.
"""
return self._get_old_attr("message")
def __init__(
self,
items: list[RedditErrorItem | list[str] | str] | str,
*optional_args: str,
):
"""Initialize a :class:`.RedditAPIException` instance.
:param items: Either a list of instances of :class:`.RedditErrorItem` or a list
containing lists of unformed errors.
:param optional_args: Takes the second and third arguments that
:class:`.APIException` used to take.
"""
if isinstance(items, str):
items = [[items, *optional_args]]
elif isinstance(items, list) and isinstance(items[0], str):
items = [items]
self.items = self.parse_exception_list(items)
super().__init__(*self.items)
def _get_old_attr(self, attrname: str) -> Any:
warn(
f"Accessing attribute '{attrname}' through APIException is deprecated."
" This behavior will be removed in PRAW 8.0. Check out"
" https://praw.readthedocs.io/en/latest/package_info/praw7_migration.html"
" to learn how to migrate your code.",
category=DeprecationWarning,
stacklevel=3,
)
return getattr(self.items[0], attrname)
class RedditAPIException(APIException):
"""Container for error messages from Reddit's API."""