• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

praw-dev / praw / 3768928224

pending completion
3768928224

Pull #1923

github

GitHub
Merge 33b610e6e into ffe9f71d6
Pull Request #1923: Improve tests, clean up test code, and sort test functions/classes

4109 of 4109 relevant lines covered (100.0%)

4.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/praw/exceptions.py
1
"""PRAW exception classes.
2

3
Includes two main exceptions: :class:`.RedditAPIException` for when something goes wrong
4
on the server side, and :class:`.ClientException` when something goes wrong on the
5
client side. Both of these classes extend :class:`.PRAWException`.
6

7
All other exceptions are subclassed from :class:`.ClientException`.
8

9
"""
10
from typing import List, Optional, Union
4✔
11
from warnings import warn
4✔
12

13
from .util import _deprecate_args
4✔
14

15

16
class PRAWException(Exception):
4✔
17
    """The base PRAW Exception that all other exception classes extend."""
18

19

20
class RedditErrorItem:
4✔
21
    """Represents a single error returned from Reddit's API."""
22

23
    @property
4✔
24
    def error_message(self) -> str:
4✔
25
        """Get the completed error message string."""
26
        error_str = self.error_type
4✔
27
        if self.message:
4✔
28
            error_str += f": {self.message!r}"
4✔
29
        if self.field:
4✔
30
            error_str += f" on field {self.field!r}"
4✔
31
        return error_str
4✔
32

33
    @_deprecate_args("error_type", "message", "field")
4✔
34
    def __init__(
4✔
35
        self,
36
        error_type: str,
37
        *,
38
        field: Optional[str] = None,
39
        message: Optional[str] = None,
40
    ):
41
        """Initialize a :class:`.RedditErrorItem` instance.
42

43
        :param error_type: The error type set on Reddit's end.
44
        :param field: The input field associated with the error, if available.
45
        :param message: The associated message for the error.
46

47
        """
48
        self.error_type = error_type
4✔
49
        self.message = message
4✔
50
        self.field = field
4✔
51

52
    def __eq__(self, other: Union["RedditErrorItem", List[str]]):
4✔
53
        """Check for equality."""
54
        if isinstance(other, RedditErrorItem):
4✔
55
            return (self.error_type, self.message, self.field) == (
4✔
56
                other.error_type,
57
                other.message,
58
                other.field,
59
            )
60
        return super().__eq__(other)
4✔
61

62
    def __repr__(self) -> str:
4✔
63
        """Return an object initialization representation of the instance."""
64
        return (
4✔
65
            f"{self.__class__.__name__}(error_type={self.error_type!r},"
66
            f" message={self.message!r}, field={self.field!r})"
67
        )
68

69
    def __str__(self):
4✔
70
        """Get the message returned from str(self)."""
71
        return self.error_message
4✔
72

73

74
class APIException(PRAWException):
4✔
75
    """Old class preserved for alias purposes.
76

77
    .. deprecated:: 7.0
78

79
        Class :class:`.APIException` has been deprecated in favor of
80
        :class:`.RedditAPIException`. This class will be removed in PRAW 8.0.
81

82
    """
83

84
    @staticmethod
4✔
85
    def parse_exception_list(exceptions: List[Union[RedditErrorItem, List[str]]]):
4✔
86
        """Covert an exception list into a :class:`.RedditErrorItem` list."""
87
        return [
4✔
88
            exception
89
            if isinstance(exception, RedditErrorItem)
90
            else RedditErrorItem(
91
                error_type=exception[0],
92
                field=exception[2] if bool(exception[2]) else "",
93
                message=exception[1] if bool(exception[1]) else "",
94
            )
95
            for exception in exceptions
96
        ]
97

98
    @property
4✔
99
    def error_type(self) -> str:
4✔
100
        """Get error_type.
101

102
        .. deprecated:: 7.0
103

104
            Accessing attributes through instances of :class:`.RedditAPIException` is
105
            deprecated. This behavior will be removed in PRAW 8.0. Check out the
106
            :ref:`PRAW 7 Migration tutorial <Exception_Handling>` on how to migrate code
107
            from this behavior.
108

109
        """
110
        return self._get_old_attr("error_type")
4✔
111

112
    @property
4✔
113
    def message(self) -> str:
4✔
114
        """Get message.
115

116
        .. deprecated:: 7.0
117

118
            Accessing attributes through instances of :class:`.RedditAPIException` is
119
            deprecated. This behavior will be removed in PRAW 8.0. Check out the
120
            :ref:`PRAW 7 Migration tutorial <Exception_Handling>` on how to migrate code
121
            from this behavior.
122

123
        """
124
        return self._get_old_attr("message")
4✔
125

126
    @property
4✔
127
    def field(self) -> str:
4✔
128
        """Get field.
129

130
        .. deprecated:: 7.0
131

132
            Accessing attributes through instances of :class:`.RedditAPIException` is
133
            deprecated. This behavior will be removed in PRAW 8.0. Check out the
134
            :ref:`PRAW 7 Migration tutorial <Exception_Handling>` on how to migrate code
135
            from this behavior.
136

137
        """
138
        return self._get_old_attr("field")
4✔
139

140
    def _get_old_attr(self, attrname):
4✔
141
        warn(
4✔
142
            f"Accessing attribute '{attrname}' through APIException is deprecated."
143
            " This behavior will be removed in PRAW 8.0. Check out"
144
            " https://praw.readthedocs.io/en/latest/package_info/praw7_migration.html"
145
            " to learn how to migrate your code.",
146
            category=DeprecationWarning,
147
            stacklevel=3,
148
        )
149
        return getattr(self.items[0], attrname)
4✔
150

151
    def __init__(
4✔
152
        self,
153
        items: Union[List[Union[RedditErrorItem, List[str], str]], str],
154
        *optional_args: str,
155
    ):
156
        """Initialize a :class:`.RedditAPIException` instance.
157

158
        :param items: Either a list of instances of :class:`.RedditErrorItem` or a list
159
            containing lists of unformed errors.
160
        :param optional_args: Takes the second and third arguments that
161
            :class:`.APIException` used to take.
162

163
        """
164
        if isinstance(items, str):
4✔
165
            items = [[items, *optional_args]]
4✔
166
        elif isinstance(items, list) and isinstance(items[0], str):
4✔
167
            items = [items]
4✔
168
        self.items = self.parse_exception_list(items)
4✔
169
        super().__init__(*self.items)
4✔
170

171

172
class RedditAPIException(APIException):
4✔
173
    """Container for error messages from Reddit's API."""
174

175

176
class ClientException(PRAWException):
4✔
177
    """Indicate exceptions that don't involve interaction with Reddit's API."""
178

179

180
class DuplicateReplaceException(ClientException):
4✔
181
    """Indicate exceptions that involve the replacement of :class:`.MoreComments`."""
182

183
    def __init__(self):
4✔
184
        """Initialize a :class:`.DuplicateReplaceException` instance."""
185
        super().__init__(
4✔
186
            "A duplicate comment has been detected. Are you attempting to call"
187
            " 'replace_more_comments' more than once?"
188
        )
189

190

191
class InvalidFlairTemplateID(ClientException):
4✔
192
    """Indicate exceptions where an invalid flair template ID is given."""
193

194
    def __init__(self, template_id: str):
4✔
195
        """Initialize an :class:`.InvalidFlairTemplateID` instance."""
196
        super().__init__(
4✔
197
            f"The flair template ID '{template_id}' is invalid. If you are trying to"
198
            " create a flair, please use the 'add' method."
199
        )
200

201

202
class InvalidImplicitAuth(ClientException):
4✔
203
    """Indicate exceptions where an implicit auth type is used incorrectly."""
204

205
    def __init__(self):
4✔
206
        """Initialize an :class:`.InvalidImplicitAuth` instance."""
207
        super().__init__("Implicit authorization can only be used with installed apps.")
4✔
208

209

210
class InvalidURL(ClientException):
4✔
211
    """Indicate exceptions where an invalid URL is entered."""
212

213
    @_deprecate_args("url", "message")
4✔
214
    def __init__(self, url: str, *, message: str = "Invalid URL: {}"):
4✔
215
        """Initialize an :class:`.InvalidURL` instance.
216

217
        :param url: The invalid URL.
218
        :param message: The message to display. Must contain a format identifier (``{}``
219
            or ``{0}``) (default: ``"Invalid URL: {}"``).
220

221
        """
222
        super().__init__(message.format(url))
4✔
223

224

225
class MissingRequiredAttributeException(ClientException):
4✔
226
    """Indicate exceptions caused by not including a required attribute."""
227

228

229
class ReadOnlyException(ClientException):
4✔
230
    """Raised when a method call requires :attr:`.read_only` mode to be disabled."""
231

232

233
class TooLargeMediaException(ClientException):
4✔
234
    """Indicate exceptions from uploading media that's too large."""
235

236
    @_deprecate_args("maximum_size", "actual")
4✔
237
    def __init__(self, *, actual: int, maximum_size: int):
4✔
238
        """Initialize a :class:`.TooLargeMediaException` instance.
239

240
        :param actual: The actual size of the uploaded media.
241
        :param maximum_size: The maximum size of the uploaded media.
242

243
        """
244
        self.maximum_size = maximum_size
4✔
245
        self.actual = actual
4✔
246
        super().__init__(
4✔
247
            f"The media that you uploaded was too large (maximum size is {maximum_size}"
248
            f" bytes, uploaded {actual} bytes)"
249
        )
250

251

252
class WebSocketException(ClientException):
4✔
253
    """Indicate exceptions caused by use of WebSockets."""
254

255
    @property
4✔
256
    def original_exception(self) -> Exception:
4✔
257
        """Access the ``original_exception`` attribute (now deprecated)."""
258
        warn(
4✔
259
            "Accessing the attribute 'original_exception' is deprecated. Please rewrite"
260
            " your code in such a way that this attribute does not need to be used. It"
261
            " will be removed in PRAW 8.0.",
262
            category=DeprecationWarning,
263
            stacklevel=2,
264
        )
265
        return self._original_exception
4✔
266

267
    @original_exception.setter
4✔
268
    def original_exception(self, value: Exception):
4✔
269
        self._original_exception = value
4✔
270

271
    @original_exception.deleter
4✔
272
    def original_exception(self):
3✔
273
        del self._original_exception
4✔
274

275
    def __init__(self, message: str, exception: Optional[Exception]):
4✔
276
        """Initialize a :class:`.WebSocketException` instance.
277

278
        :param message: The exception message.
279
        :param exception: The exception thrown by the websocket library.
280

281
            .. note::
282

283
                This parameter is deprecated. It will be removed in PRAW 8.0.
284

285
        """
286
        super().__init__(message)
4✔
287
        self._original_exception = exception
4✔
288

289

290
class MediaPostFailed(WebSocketException):
4✔
291
    """Indicate exceptions where media uploads failed.."""
292

293
    def __init__(self):
4✔
294
        """Initialize a :class:`.MediaPostFailed` instance."""
295
        super().__init__(
4✔
296
            "The attempted media upload action has failed. Possible causes include the"
297
            " corruption of media files. Check that the media file can be opened on"
298
            " your local machine.",
299
            None,
300
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc