11from __future__ import annotations
22
3- from typing import TYPE_CHECKING
3+ from typing import TYPE_CHECKING , Any
44
55from apify_client ._docs import docs_group
66
@@ -43,19 +43,22 @@ class ApifyApiError(ApifyClientError):
4343 data: Additional error data from the API response.
4444 """
4545
46+ _apify_error_payload : dict [str , Any ] | None
47+
4648 def __new__ (cls , response : HttpResponse , attempt : int , method : str = 'GET' ) -> Self : # noqa: ARG004
4749 """Dispatch to the subclass matching the response's error `type`, if any."""
50+ payload = _extract_error_payload (response )
4851 target_cls : type [ApifyApiError ] = cls
49- if cls is ApifyApiError :
50- # Local import to avoid the circular dependency (`_generated_errors` imports us).
51- from apify_client ._generated_errors import API_ERROR_CLASS_BY_TYPE # noqa: PLC0415
52+ if cls is ApifyApiError and payload is not None :
53+ error_type = payload .get ('type' )
54+ if isinstance (error_type , str ):
55+ # avoid circular import with _generated_errors
56+ from apify_client ._generated_errors import API_ERROR_CLASS_BY_TYPE # noqa: PLC0415
5257
53- error_type = _extract_error_type (response )
54- if error_type is not None :
55- subclass = API_ERROR_CLASS_BY_TYPE .get (error_type )
56- if subclass is not None :
57- target_cls = subclass
58- return super ().__new__ (target_cls )
58+ target_cls = API_ERROR_CLASS_BY_TYPE .get (error_type , cls )
59+ instance = super ().__new__ (target_cls )
60+ instance ._apify_error_payload = payload
61+ return instance
5962
6063 def __init__ (self , response : HttpResponse , attempt : int , method : str = 'GET' ) -> None :
6164 """Initialize the API error from a failed response.
@@ -65,27 +68,21 @@ def __init__(self, response: HttpResponse, attempt: int, method: str = 'GET') ->
6568 attempt: The attempt number when the request failed (1-indexed).
6669 method: The HTTP method of the failed request.
6770 """
68- self .message : str | None = None
71+ # Prefer the payload stashed by __new__; fall back to re-parsing for direct subclass
72+ # instantiation (e.g. if a user constructs a subclass without going through the base class).
73+ payload = getattr (self , '_apify_error_payload' , None )
74+ if payload is None :
75+ payload = _extract_error_payload (response )
76+
77+ self .message : str | None = f'Unexpected error: { response .text } '
6978 self .type : str | None = None
7079 self .data = dict [str , str ]()
71- self .message = f'Unexpected error: { response .text } '
72-
73- try :
74- response_data = response .json ()
7580
76- if (
77- isinstance (response_data , dict )
78- and 'error' in response_data
79- and isinstance (response_data ['error' ], dict )
80- ):
81- self .message = response_data ['error' ]['message' ]
82- self .type = response_data ['error' ]['type' ]
83-
84- if 'data' in response_data ['error' ]:
85- self .data = response_data ['error' ]['data' ]
86-
87- except ValueError :
88- pass
81+ if payload is not None :
82+ self .message = payload ['message' ]
83+ self .type = payload ['type' ]
84+ if 'data' in payload :
85+ self .data = payload ['data' ]
8986
9087 super ().__init__ (self .message )
9188
@@ -95,19 +92,16 @@ def __init__(self, response: HttpResponse, attempt: int, method: str = 'GET') ->
9592 self .http_method = method
9693
9794
98- def _extract_error_type (response : HttpResponse ) -> str | None :
99- """Return the `error.type` field from the response body, or None if absent or unparsable."""
95+ def _extract_error_payload (response : HttpResponse ) -> dict [ str , Any ] | None :
96+ """Return the `error` dict from the response body, or None if absent or unparsable."""
10097 try :
10198 data = response .json ()
10299 except ValueError :
103100 return None
104101 if not isinstance (data , dict ):
105102 return None
106103 error = data .get ('error' )
107- if not isinstance (error , dict ):
108- return None
109- error_type = error .get ('type' )
110- return error_type if isinstance (error_type , str ) else None
104+ return error if isinstance (error , dict ) else None
111105
112106
113107@docs_group ('Errors' )
@@ -132,8 +126,4 @@ def __init__(self, response: HttpResponse) -> None:
132126 self .response = response
133127
134128
135- # Re-export the generated per-type Exception subclasses so users can do e.g.
136- # `from apify_client.errors import RecordNotFoundError`. The star import is safe because
137- # `_generated_errors` defines an `__all__` and `errors.py` is fully initialized at the point
138- # the re-import triggers (the module-level classes above are already bound).
139129from apify_client ._generated_errors import * # noqa: E402, F403
0 commit comments