@@ -116,7 +116,9 @@ def __init__(self, msg, path_to_item=None):
116116
117117
118118class ApiException (OpenApiException ):
119- def __init__ (self , status = None , reason = None , http_resp = None ):
119+ def __init__ (
120+ self , status = None , reason = None , http_resp = None , * , operation_name = None
121+ ):
120122 if http_resp :
121123 try :
122124 headers = http_resp .headers .items ()
@@ -138,14 +140,37 @@ def __init__(self, status=None, reason=None, http_resp=None):
138140 self ._parsed_exception = None
139141 self .header = dict ()
140142
143+ self .operation_name = operation_name
144+
141145 def __str__ (self ):
142- """Custom error messages for exception"""
143- error_message = f"({ self .status } )\n Reason: { self .reason } \n "
146+ """
147+ Format error with operation context and structured details.
148+ Returns formatted string like:
149+ [write] HTTP 400 type 'invalid_type' not found (validation_error) [request-id: abc-123]
150+ """
151+ parts = []
152+
153+ # Add operation context
154+ if self .operation_name :
155+ parts .append (f"[{ self .operation_name } ]" )
156+
157+ # Add error type/status
158+ if self .status :
159+ parts .append (f"HTTP { self .status } " )
160+
161+ # Add error message (parsed or reason)
162+ if self .error_message :
163+ parts .append (self .error_message )
164+
165+ # Add error code in parentheses
166+ if self .code :
167+ parts .append (f"({ self .code } )" )
144168
145- if self .body :
146- error_message += f"HTTP response body: { self .body } \n "
169+ # Add request ID for debugging
170+ if self .request_id :
171+ parts .append (f"[request-id: { self .request_id } ]" )
147172
148- return error_message
173+ return " " . join ( parts ) if parts else "Unknown API error"
149174
150175 @property
151176 def parsed_exception (self ):
@@ -161,40 +186,165 @@ def parsed_exception(self, content):
161186 """
162187 self ._parsed_exception = content
163188
189+ @property
190+ def code (self ):
191+ """
192+ Get the error code from the parsed exception.
193+
194+ Returns:
195+ Error code string (e.g., "validation_error") or None
196+ """
197+ if self ._parsed_exception and hasattr (self ._parsed_exception , "code" ):
198+ code_value = self ._parsed_exception .code
199+ # Handle enum types
200+ if hasattr (code_value , "value" ):
201+ return code_value .value
202+ return str (code_value ) if code_value is not None else None
203+ return None
204+
205+ @property
206+ def error_message (self ):
207+ """
208+ Get the human-readable error message.
209+
210+ Returns:
211+ Error message from API or HTTP reason phrase
212+ """
213+ if self ._parsed_exception and hasattr (self ._parsed_exception , "message" ):
214+ message = self ._parsed_exception .message
215+ if message :
216+ return message
217+ return self .reason or "Unknown error"
218+
219+ @property
220+ def request_id (self ):
221+ """
222+ Get the request ID for debugging and support.
223+
224+ Returns:
225+ FGA request ID from response headers or None
226+ """
227+ if not self .header :
228+ return None
229+ # HTTP headers are case-insensitive, try different cases
230+ for key in self .header :
231+ if key .lower () == FGA_REQUEST_ID :
232+ return self .header [key ]
233+ return None
234+
235+ def is_validation_error (self ):
236+ """
237+ Check if this is a validation error.
238+
239+ Returns:
240+ True if error code indicates validation failure
241+ """
242+ return isinstance (self , ValidationException ) or (
243+ self .code and "validation" in self .code .lower ()
244+ )
245+
246+ def is_not_found_error (self ):
247+ """
248+ Check if this is a not found (404) error.
249+
250+ Returns:
251+ True if HTTP status is 404
252+ """
253+ return isinstance (self , NotFoundException ) or self .status == 404
254+
255+ def is_authentication_error (self ):
256+ """
257+ Check if this is an authentication (401) error.
258+
259+ Returns:
260+ True if HTTP status is 401
261+ """
262+ return self .status == 401
263+
264+ def is_rate_limit_error (self ):
265+ """
266+ Check if this is a rate limit (429) error.
267+
268+ Returns:
269+ True if HTTP status is 429 or error code indicates rate limiting
270+ """
271+ return self .status == 429 or (self .code and "rate_limit" in self .code .lower ())
272+
273+ def is_retryable (self ):
274+ """
275+ Check if this error should be retried.
276+
277+ Returns:
278+ True if error is temporary and retrying may succeed
279+ """
280+ return self .status in [429 , 500 , 502 , 503 , 504 ] if self .status else False
281+
282+ def is_client_error (self ):
283+ """
284+ Check if this is a client error (4xx).
285+
286+ Returns:
287+ True if HTTP status is in 400-499 range
288+ """
289+ return 400 <= self .status < 500 if self .status else False
290+
291+ def is_server_error (self ):
292+ """
293+ Check if this is a server error (5xx).
294+
295+ Returns:
296+ True if HTTP status is in 500-599 range
297+ """
298+ return 500 <= self .status < 600 if self .status else False
299+
164300
165301class NotFoundException (ApiException ):
166- def __init__ (self , status = None , reason = None , http_resp = None ):
167- super ().__init__ (status , reason , http_resp )
302+ def __init__ (
303+ self , status = None , reason = None , http_resp = None , * , operation_name = None
304+ ):
305+ super ().__init__ (status , reason , http_resp , operation_name = operation_name )
168306
169307
170308class UnauthorizedException (ApiException ):
171- def __init__ (self , status = None , reason = None , http_resp = None ):
172- super ().__init__ (status , reason , http_resp )
309+ def __init__ (
310+ self , status = None , reason = None , http_resp = None , * , operation_name = None
311+ ):
312+ super ().__init__ (status , reason , http_resp , operation_name = operation_name )
173313
174314
175315class ForbiddenException (ApiException ):
176- def __init__ (self , status = None , reason = None , http_resp = None ):
177- super ().__init__ (status , reason , http_resp )
316+ def __init__ (
317+ self , status = None , reason = None , http_resp = None , * , operation_name = None
318+ ):
319+ super ().__init__ (status , reason , http_resp , operation_name = operation_name )
178320
179321
180322class ServiceException (ApiException ):
181- def __init__ (self , status = None , reason = None , http_resp = None ):
182- super ().__init__ (status , reason , http_resp )
323+ def __init__ (
324+ self , status = None , reason = None , http_resp = None , * , operation_name = None
325+ ):
326+ super ().__init__ (status , reason , http_resp , operation_name = operation_name )
183327
184328
185329class ValidationException (ApiException ):
186- def __init__ (self , status = None , reason = None , http_resp = None ):
187- super ().__init__ (status , reason , http_resp )
330+ def __init__ (
331+ self , status = None , reason = None , http_resp = None , * , operation_name = None
332+ ):
333+ super ().__init__ (status , reason , http_resp , operation_name = operation_name )
188334
189335
190336class AuthenticationError (ApiException ):
191- def __init__ (self , status = None , reason = None , http_resp = None ):
192- super ().__init__ (status , reason , http_resp )
337+ def __init__ (
338+ self , status = None , reason = None , http_resp = None , * , operation_name = None
339+ ):
340+ super ().__init__ (status , reason , http_resp , operation_name = operation_name )
193341
194342
195343class RateLimitExceededError (ApiException ):
196- def __init__ (self , status = None , reason = None , http_resp = None ):
197- super ().__init__ (status , reason , http_resp )
344+ def __init__ (
345+ self , status = None , reason = None , http_resp = None , * , operation_name = None
346+ ):
347+ super ().__init__ (status , reason , http_resp , operation_name = operation_name )
198348
199349
200350def render_path (path_to_item ):
0 commit comments