55# mypy: disable-error-code=unreachable
66
77import asyncio
8+ import json
89from typing import Any , Callable , Dict , List , Optional
910from urllib .parse import urljoin
1011
@@ -80,6 +81,36 @@ async def close(self):
8081 if self ._client and not self ._client .is_closed :
8182 await self ._client .aclose ()
8283
84+ @staticmethod
85+ def _truncate_response (text : str , max_length : int = 200 ) -> str :
86+ """Truncate response text for error messages"""
87+ if len (text ) > max_length :
88+ return text [:max_length ] + "..."
89+ return text
90+
91+ @staticmethod
92+ def _safe_parse_json (response : httpx .Response ) -> Dict [str , Any ]:
93+ """
94+ Safely parse JSON response, handling HTML error pages gracefully.
95+
96+ Args:
97+ response: The httpx response object
98+
99+ Returns:
100+ Parsed JSON as a dictionary
101+
102+ Raises:
103+ RuntimeError: If the response cannot be parsed as JSON
104+ """
105+ try :
106+ return response .json ()
107+ except json .JSONDecodeError :
108+ preview = LingoDotDevEngine ._truncate_response (response .text )
109+ raise RuntimeError (
110+ f"Failed to parse API response as JSON (status { response .status_code } ). "
111+ f"This may indicate a gateway or proxy error. Response: { preview } "
112+ )
113+
83114 async def _localize_raw (
84115 self ,
85116 payload : Dict [str , Any ],
@@ -184,19 +215,31 @@ async def _localize_chunk(
184215 response = await self ._client .post (url , json = request_data )
185216
186217 if not response .is_success :
218+ response_preview = self ._truncate_response (response .text )
187219 if 500 <= response .status_code < 600 :
220+ error_details = ""
221+ try :
222+ error_json = response .json ()
223+ if isinstance (error_json , dict ) and "error" in error_json :
224+ error_details = f" { error_json ['error' ]} "
225+ except Exception :
226+ pass
227+
188228 raise RuntimeError (
189- f"Server error ({ response .status_code } ): { response .reason_phrase } . "
190- f" { response . text } . This may be due to temporary service issues."
229+ f"Server error ({ response .status_code } ): { response .reason_phrase } .{ error_details } "
230+ " This may be due to temporary service issues."
191231 )
192232 elif response .status_code == 400 :
193233 raise ValueError (
194- f"Invalid request ({ response .status_code } ): { response .reason_phrase } "
234+ f"Invalid request ({ response .status_code } ): { response .reason_phrase } . "
235+ f"Response: { response_preview } "
195236 )
196237 else :
197- raise RuntimeError (response .text )
238+ raise RuntimeError (
239+ f"Request failed ({ response .status_code } ): { response_preview } "
240+ )
198241
199- json_response = response . json ( )
242+ json_response = self . _safe_parse_json ( response )
200243
201244 # Handle streaming errors
202245 if not json_response .get ("data" ) and json_response .get ("error" ):
@@ -426,16 +469,26 @@ async def recognize_locale(self, text: str) -> str:
426469 response = await self ._client .post (url , json = {"text" : text })
427470
428471 if not response .is_success :
472+ response_preview = self ._truncate_response (response .text )
429473 if 500 <= response .status_code < 600 :
474+ error_details = ""
475+ try :
476+ error_json = response .json ()
477+ if isinstance (error_json , dict ) and "error" in error_json :
478+ error_details = f" { error_json ['error' ]} "
479+ except Exception :
480+ pass
481+
430482 raise RuntimeError (
431- f"Server error ({ response .status_code } ): { response .reason_phrase } . "
483+ f"Server error ({ response .status_code } ): { response .reason_phrase } .{ error_details } "
432484 "This may be due to temporary service issues."
433485 )
434486 raise RuntimeError (
435- f"Error recognizing locale: { response .reason_phrase } "
487+ f"Error recognizing locale ({ response .status_code } ): { response .reason_phrase } . "
488+ f"Response: { response_preview } "
436489 )
437490
438- json_response = response . json ( )
491+ json_response = self . _safe_parse_json ( response )
439492 return json_response .get ("locale" ) or ""
440493
441494 except httpx .RequestError as e :
@@ -456,13 +509,21 @@ async def whoami(self) -> Optional[Dict[str, str]]:
456509 response = await self ._client .post (url )
457510
458511 if response .is_success :
459- payload = response . json ( )
512+ payload = self . _safe_parse_json ( response )
460513 if payload .get ("email" ):
461514 return {"email" : payload ["email" ], "id" : payload ["id" ]}
462515
463516 if 500 <= response .status_code < 600 :
517+ error_details = ""
518+ try :
519+ error_json = response .json ()
520+ if isinstance (error_json , dict ) and "error" in error_json :
521+ error_details = f" { error_json ['error' ]} "
522+ except Exception :
523+ pass
524+
464525 raise RuntimeError (
465- f"Server error ({ response .status_code } ): { response .reason_phrase } . "
526+ f"Server error ({ response .status_code } ): { response .reason_phrase } .{ error_details } "
466527 "This may be due to temporary service issues."
467528 )
468529
0 commit comments