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+ )
0 commit comments