Skip to content

Commit 9d62bdb

Browse files
committed
enhancing error parsing
1 parent 40a6e85 commit 9d62bdb

1 file changed

Lines changed: 59 additions & 23 deletions

File tree

subconscious/errors.py

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,27 +76,63 @@ def _status_to_code(status: int) -> ErrorCode:
7676

7777

7878
def raise_for_status(response) -> None:
79-
"""Raise appropriate exception based on response status."""
80-
if response.status_code >= 400:
81-
try:
82-
body = response.json()
83-
error = body.get("error", {})
84-
code = error.get("code", _status_to_code(response.status_code))
85-
message = error.get("message", response.text or f"HTTP {response.status_code}")
86-
details = error.get("details")
87-
except Exception:
88-
code = _status_to_code(response.status_code)
89-
message = response.text or f"HTTP {response.status_code}"
90-
details = None
91-
92-
if code == "authentication_failed":
93-
raise AuthenticationError(message)
94-
elif code == "rate_limited":
95-
raise RateLimitError(message)
96-
elif code == "not_found":
97-
raise NotFoundError(message)
98-
elif code == "invalid_request":
99-
raise ValidationError(message, details)
100-
else:
101-
raise SubconsciousError(code, message, response.status_code, details)
79+
"""Raise appropriate exception based on response status.
80+
81+
Extracts error info from three wire shapes the server may return:
82+
83+
- Canonical SDK shape: ``{"error": {"code", "message", "details"}}``
84+
- Express default: ``{"error": "Internal server error"}``
85+
- Plain text/unknown: falls back to ``response.text``
86+
87+
Enriches the message with method + URL so users get enough context
88+
to correlate with server logs (especially for 5xx responses where
89+
the body is generic).
90+
"""
91+
if response.status_code < 400:
92+
return
93+
94+
try:
95+
body = response.json()
96+
except Exception:
97+
body = None
98+
99+
error_field = body.get("error") if isinstance(body, dict) else None
100+
if isinstance(error_field, dict):
101+
code = error_field.get("code", _status_to_code(response.status_code))
102+
message = error_field.get("message") or str(error_field)
103+
details = error_field.get("details")
104+
elif isinstance(error_field, str):
105+
code = _status_to_code(response.status_code)
106+
message = error_field
107+
details = None
108+
else:
109+
code = _status_to_code(response.status_code)
110+
message = response.text or f"HTTP {response.status_code}"
111+
details = None
112+
113+
# Context suffix: "[GET /v1/runs/<id>]" and a request id when the
114+
# server surfaces one. Skipped quietly if the request object isn't
115+
# reachable (e.g., requests.Response without a bound PreparedRequest).
116+
request = getattr(response, "request", None)
117+
method = getattr(request, "method", None)
118+
url = getattr(request, "url", None)
119+
request_id = response.headers.get("x-request-id") if response.headers else None
120+
suffix_parts = []
121+
if method and url:
122+
suffix_parts.append(f"{method} {url}")
123+
if request_id:
124+
suffix_parts.append(f"request_id={request_id}")
125+
if suffix_parts:
126+
message = f"{message} [{' '.join(suffix_parts)}]"
127+
128+
if code == "authentication_failed":
129+
raise AuthenticationError(message)
130+
elif code == "rate_limited":
131+
raise RateLimitError(message)
132+
elif code == "not_found":
133+
raise NotFoundError(message)
134+
elif code == "invalid_request":
135+
raise ValidationError(message, details)
136+
else:
137+
raise SubconsciousError(code, message, response.status_code, details)
102138

0 commit comments

Comments
 (0)