Skip to content

Commit 06f4924

Browse files
V3ntusi0bsFayeDelEepyElvyrapre-commit-ci[bot]
authored
ref: Improved error handler (#848)
* ADMIN/revert: remove unwanted file. This file was actually cloned outside of the client directory in a PR that had not appropriately adjusted to the new architectural layout of the library. * chore: Update requirements.txt * chore: Version bump. * chore/ADMIN: force version onto old pypi This is an executive decision made between Delta and I. We will be forcing a version 4.0 push onto our old branch to help transition old users using v3 to our new project. This does not necessarily mean this branch will now be receiving updates along with the rest. The goal is to make our current branch used as much as possible. * chore/ADMIN: fix content type * fix/ADMIN: correct improper description * revert/ADMIN: change back to original * ref: init new error handler * add: default error enums (EdVraz) * refactor: Change exception raising when requests are made * ci: correct from checks. * refactor: Change all imports and `raise`s to `LibraryException` * ci: correct from checks. * ref: variable renaming for readability Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * ref: variable renaming for readability Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * ref: variable renaming for readability Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * ref: variable renaming for readability Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * ref: simplify a dumb list comp thing I made Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com> * refactor: Change all imports and `raise`s to `LibraryException` Co-authored-by: fl0w <41456914+goverfl0w@users.noreply.github.com> Co-authored-by: DeltaX <33706469+DeltaXWizard@users.noreply.github.com> Co-authored-by: Sofia <41456914+ffl0w@users.noreply.github.com> Co-authored-by: James Walston <41456914+jameswalston@users.noreply.github.com> Co-authored-by: EdVraz <88881326+EdVraz@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Toricane <73972068+Toricane@users.noreply.github.com>
1 parent 44c44a1 commit 06f4924

File tree

8 files changed

+150
-287
lines changed

8 files changed

+150
-287
lines changed

interactions/api/error.py

Lines changed: 93 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,77 @@
1-
from enum import IntEnum
2-
from string import Formatter
3-
from typing import Any, Optional, Union
1+
from logging import getLogger
2+
from typing import List, Optional
43

5-
__all__ = (
6-
"ErrorFormatter",
7-
"InteractionException",
8-
"GatewayException",
9-
"HTTPException",
10-
"JSONException",
11-
)
4+
__all__ = ("LibraryException",)
125

6+
log = getLogger(__name__)
137

14-
class ErrorFormatter(Formatter):
15-
"""A customized error formatting script to return specific errors."""
168

17-
def get_value(self, key, args, kwargs) -> Any:
18-
if not isinstance(key, str):
19-
return Formatter.get_value(self, key=key, args=args, kwargs=kwargs)
20-
try:
21-
return kwargs[key]
22-
except KeyError:
23-
return key
9+
class LibraryException(Exception):
10+
code: Optional[int]
11+
severity: int
2412

13+
__slots__ = {"code", "severity", "message", "data"}
2514

26-
class InteractionException(Exception):
27-
"""
28-
An exception class for interactions.
29-
30-
.. note::
31-
This is a WIP. This isn't meant to be used yet, this is a baseline,
32-
and for extensive testing/review before integration.
33-
Likewise, this will show the concepts before use, and will be refined when time goes on.
34-
35-
:ivar interactions.api.error.ErrorFormatter _formatter: The built-in formatter.
36-
:ivar dict _lookup: A dictionary containing the values from the built-in Enum.
37-
38-
"""
39-
40-
__slots__ = ("_type", "_lookup", "__type", "_formatter", "kwargs")
41-
42-
def __init__(self, __type: Optional[Union[int, IntEnum]] = 0, **kwargs) -> None:
15+
@staticmethod
16+
def _parse(_data: dict) -> List[tuple]:
4317
"""
44-
:param __type: Type of error. This is decided from an IntEnum, which gives readable error messages instead of
45-
typical error codes. Subclasses of this class works fine, and so does regular integers.
46-
:type __type: Optional[Union[int, IntEnum]]
47-
:param kwargs: Any additional keyword arguments.
48-
:type **kwargs: dict
49-
50-
:: note::
51-
(given if 3 is "DUPLICATE_COMMAND" and with the right enum import, it will display 3 as the error code.)
52-
Example:
18+
Internal function that should not be executed externally.
19+
Parse the error data and set the code and message.
5320
54-
>>> raise InteractionException(2, message="msg")
55-
Exception raised: "msg" (error 2)
56-
57-
>>> raise InteractionException(Default_Error_Enum.DUPLICATE_COMMAND) # noqa
58-
Exception raised: Duplicate command name. (error 3)
21+
:param _data: The error data to parse.
22+
:type _data: dict
23+
:return: A list of tuples containing parsed errors.
24+
:rtype: List[tuple]
5925
"""
26+
_errors: list = []
27+
28+
def _inner(v, parent):
29+
if isinstance(v, dict):
30+
if (errs := v.get("_errors")) and isinstance(errs, list):
31+
for err in errs:
32+
_errors.append((err["code"], err["message"], parent))
33+
else:
34+
for k, v in v.items():
35+
if isinstance(v, dict):
36+
_inner(v, parent + "." + k)
37+
elif isinstance(v, list):
38+
for e in v:
39+
if isinstance(e, dict):
40+
_errors.append((e["code"], e["message"], parent + "." + k))
41+
elif isinstance(v, list) and parent == "_errors":
42+
for e in v:
43+
_errors.append((e["code"], e["message"], parent))
44+
45+
for _k, _v in _data.items():
46+
_inner(_v, _k)
47+
return _errors
48+
49+
def log(self, message: str, *args):
50+
"""
51+
Log the error message.
6052
61-
self._type = __type
62-
self.kwargs = kwargs
63-
self._formatter = ErrorFormatter()
64-
self._lookup = self.lookup()
65-
66-
self.error()
53+
:param message:
54+
:type message:
55+
:param args:
56+
:type args:
57+
"""
58+
if self.severity == 0: # NOTSET
59+
pass
60+
elif self.severity == 10: # DEBUG
61+
log.debug(message, *args)
62+
elif self.severity == 20: # INFO
63+
log.info(message, *args)
64+
elif self.severity == 30: # WARNING
65+
log.warning(message, *args)
66+
elif self.severity == 40: # ERROR
67+
log.error(message, *args)
68+
elif self.severity == 50: # CRITICAL
69+
log.critical(message, *args)
6770

6871
@staticmethod
69-
def lookup() -> dict:
70-
"""
71-
From the default error enum integer declaration,
72-
generate a dictionary containing the phrases used for the errors.
73-
"""
72+
def lookup(code: int) -> str:
7473
return {
74+
# Default error integer enum
7575
0: "Unknown error",
7676
1: "Request to Discord API has failed.",
7777
2: "Some formats are incorrect. See Discord API DOCS for proper format.",
@@ -84,87 +84,15 @@ def lookup() -> dict:
8484
9: "Incorrect data was passed to a slash command data object.",
8585
10: "The interaction was already responded to.",
8686
11: "Error creating your command.",
87-
}
88-
89-
@property
90-
def type(self) -> Optional[Union[int, IntEnum]]:
91-
"""
92-
Grabs the type attribute.
93-
Primarily useful to use it in conditions for integral v4 (potential) logic.
94-
"""
95-
return self._type
96-
97-
def error(self) -> None:
98-
"""This calls the exception."""
99-
_err_val = ""
100-
_err_unidentifiable = False
101-
_empty_space = " "
102-
_overrided = "message" in self.kwargs
103-
104-
if issubclass(type(self._type), IntEnum):
105-
_err_val = self.type.name
106-
_err_rep = self.type.value
107-
elif type(self.type) == int:
108-
_err_rep = self.type
109-
else: # unidentifiable.
110-
_err_rep = 0
111-
_err_unidentifiable = True
112-
113-
_err_msg = _default_err_msg = "Error code: {_err_rep}"
114-
115-
if self.kwargs != {} and _overrided:
116-
_err_msg = self.kwargs["message"]
117-
118-
self.kwargs["_err_rep"] = _err_rep
119-
120-
if not _err_unidentifiable:
121-
lookup_str = self._lookup[_err_rep] if _err_rep in self._lookup.keys() else _err_val
122-
_lookup_str = (
123-
lookup_str
124-
if max(self._lookup.keys()) >= _err_rep >= min(self._lookup.keys())
125-
else ""
126-
)
127-
else:
128-
_lookup_str = lookup_str = ""
129-
130-
custom_err_str = (
131-
self._formatter.format(_err_msg, **self.kwargs)
132-
if "_err_rep" in _err_msg
133-
else self._formatter.format(_err_msg + _empty_space + _default_err_msg, **self.kwargs)
134-
)
135-
136-
# This is just for writing notes meant to be for the developer(testers):
137-
#
138-
# Error code 4 represents dupe callback. In v3, that's "Duplicate component callback detected: "
139-
# f"message ID {message_id or '<any>'}, "
140-
# f"custom_id `{custom_id or '<any>'}`, "
141-
# f"component_type `{component_type or '<any>'}`"
142-
#
143-
# Error code 3 represents dupe command, i.e. "Duplicate command name detected: {name}"
144-
# Error code 1 represents Req. failure, i.e. "Request failed with resp: {self.status} | {self.msg}"
145-
#
146-
147-
super().__init__(
148-
f"{f'{lookup_str} ' if _err_val != '' else f'{_lookup_str + _empty_space if max(self._lookup.keys()) >= _err_rep >= min(self._lookup.keys()) else lookup_str}'}{custom_err_str}"
149-
)
150-
151-
152-
class GatewayException(InteractionException):
153-
"""
154-
This is a derivation of InteractionException in that this is used to represent Gateway closing OP codes.
155-
156-
:ivar ErrorFormatter _formatter: The built-in formatter.
157-
:ivar dict _lookup: A dictionary containing the values from the built-in Enum.
158-
"""
159-
160-
__slots__ = ("_type", "_lookup", "__type", "_formatter", "kwargs")
161-
162-
def __init__(self, __type, **kwargs):
163-
super().__init__(__type, **kwargs)
164-
165-
@staticmethod
166-
def lookup() -> dict:
167-
return {
87+
# HTTP errors
88+
400: "Bad Request. The request was improperly formatted, or the server couldn't understand it.",
89+
401: "Not authorized. Double check your token to see if it's valid.",
90+
403: "You do not have enough permissions to execute this.",
91+
404: "Resource does not exist.",
92+
405: "HTTP method not valid.", # ?
93+
429: "You are being rate limited. Please slow down on your requests.", # Definitely can be overclassed.
94+
502: "Gateway unavailable. Try again later.",
95+
# Gateway errors
16896
4000: "Unknown error. Try reconnecting?",
16997
4001: "Unknown opcode. Check your gateway opcode and/or payload.",
17098
4002: "Invalid payload.",
@@ -179,52 +107,7 @@ def lookup() -> dict:
179107
4012: "Invalid API version for the Gateway.",
180108
4013: "Invalid intent(s).",
181109
4014: "Some intent(s) requested are not allowed. Please double check.",
182-
}
183-
184-
185-
class HTTPException(InteractionException):
186-
"""
187-
This is a derivation of InteractionException in that this is used to represent HTTP Exceptions.
188-
189-
:ivar ErrorFormatter _formatter: The built-in formatter.
190-
:ivar dict _lookup: A dictionary containing the values from the built-in Enum.
191-
"""
192-
193-
__slots__ = ("_type", "_lookup", "__type", "_formatter", "kwargs")
194-
195-
def __init__(self, __type, **kwargs):
196-
super().__init__(__type, **kwargs)
197-
198-
@staticmethod
199-
def lookup() -> dict:
200-
return {
201-
400: "Bad Request. The request was improperly formatted, or the server couldn't understand it.",
202-
401: "Not authorized. Double check your token to see if it's valid.",
203-
403: "You do not have enough permissions to execute this.",
204-
404: "Resource does not exist.",
205-
405: "HTTP method not valid.", # ?
206-
429: "You are being rate limited. Please slow down on your requests.", # Definitely can be overclassed.
207-
502: "Gateway unavailable. Try again later.",
208-
}
209-
210-
211-
class JSONException(InteractionException):
212-
"""
213-
This is a derivation of InteractionException in that this is used to represent JSON API Exceptions.
214-
215-
:ivar ErrorFormatter _formatter: The built-in formatter.
216-
:ivar dict _lookup: A dictionary containing the values from the built-in Enum.
217-
"""
218-
219-
__slots__ = ("_type", "_lookup", "__type", "_formatter", "kwargs")
220-
221-
def __init__(self, __type, **kwargs):
222-
super().__init__(__type, **kwargs)
223-
224-
@staticmethod
225-
def lookup() -> dict:
226-
return {
227-
0: "Unknown Error.",
110+
# JSON errors
228111
10001: "Unknown Account.",
229112
10002: "Unknown Application.",
230113
10003: "Unknown Channel.",
@@ -377,4 +260,25 @@ def lookup() -> dict:
377260
170007: "Sticker animation duration exceeds maximum of 5 seconds",
378261
180000: "Cannot update a finished event",
379262
180002: "Failed to create stage needed for stage event",
380-
}
263+
}.get(code, f"Unknown error: {code}")
264+
265+
def __init__(self, message: str = None, code: int = 0, severity: int = 0, **kwargs):
266+
self.code: int = code
267+
self.severity: int = severity
268+
self.data: dict = kwargs.pop("data", None)
269+
self.message: str = message or self.lookup(self.code)
270+
_fmt_error: List[tuple] = []
271+
272+
if (
273+
self.data
274+
and isinstance(self.data, dict)
275+
and isinstance(self.data.get("errors", None), dict)
276+
):
277+
_fmt_error: List[tuple] = self._parse(self.data["errors"])
278+
279+
super().__init__(
280+
f"{self.message} (code: {self.code}, severity {self.severity})\n"
281+
+ "\n".join([f"Error at {e[2]}: {e[0]} - {e[1]}" for e in _fmt_error])
282+
if _fmt_error
283+
else None
284+
)

interactions/api/error.pyi

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,14 @@
1-
from enum import IntEnum
2-
from string import Formatter
3-
from typing import Any, Dict, Optional, Union
1+
from typing import Optional, List
42

5-
class ErrorFormatter(Formatter):
6-
def get_value(self, key, args, kwargs) -> Any: ...
3+
class LibraryException(Exception):
4+
message: Optional[str]
5+
code: Optional[int]
6+
severity: Optional[int]
77

8-
class InteractionException(Exception):
9-
_type: Union[int, IntEnum]
10-
__type: Optional[Union[int, IntEnum]]
11-
_formatter: ErrorFormatter
12-
kwargs: Dict[str, Any]
13-
_lookup: dict
14-
def __init__(self, __type: Optional[Union[int, IntEnum]] = 0, **kwargs) -> None: ...
158
@staticmethod
16-
def lookup() -> dict: ...
17-
@property
18-
def type(self) -> Optional[Union[int, IntEnum]]: ...
19-
def error(self) -> None: ...
9+
def _parse(_data: dict) -> List[tuple]: ...
2010

21-
class GatewayException(InteractionException):
22-
_type: Union[int, IntEnum]
23-
__type: Optional[Union[int, IntEnum]]
24-
_formatter: ErrorFormatter
25-
kwargs: Dict[str, Any]
26-
_lookup: dict
27-
def __init__(self, __type, **kwargs): ...
28-
@staticmethod
29-
def lookup() -> dict: ...
30-
31-
class HTTPException(InteractionException):
32-
_type: Union[int, IntEnum]
33-
__type: Optional[Union[int, IntEnum]]
34-
_formatter: ErrorFormatter
35-
kwargs: Dict[str, Any]
36-
_lookup: dict
37-
def __init__(self, __type, **kwargs): ...
38-
@staticmethod
39-
def lookup() -> dict: ...
11+
def log(self, message: str, *args) -> None: ...
4012

41-
class JSONException(InteractionException):
42-
_type: Union[int, IntEnum]
43-
__type: Optional[Union[int, IntEnum]]
44-
_formatter: ErrorFormatter
45-
kwargs: Dict[str, Any]
46-
_lookup: dict
47-
def __init__(self, __type, **kwargs): ...
4813
@staticmethod
49-
def lookup() -> dict: ...
14+
def lookup(code: int) -> str: ...

interactions/api/gateway/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from ...client.models import Option
2525
from ..dispatch import Listener
2626
from ..enums import OpCodeType
27-
from ..error import GatewayException
27+
from ..error import LibraryException
2828
from ..http.client import HTTPClient
2929
from ..models.attrs_utils import MISSING
3030
from ..models.flags import Intents
@@ -186,7 +186,7 @@ async def _establish_connection(
186186
break
187187

188188
if self._client.close_code in range(4010, 4014) or self._client.close_code == 4004:
189-
raise GatewayException(self._client.close_code)
189+
raise LibraryException(self._client.close_code)
190190

191191
await self._handle_connection(stream, shard, presence)
192192

0 commit comments

Comments
 (0)