Coverage for src/hush_hush/errors.py: 88%
16 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-10-16 16:01 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-10-16 16:01 +0000
1"""Typed errors raised for any non-2xx response from hush-hush."""
3from __future__ import annotations
5import json
7import httpx
10class APIError(Exception):
11 """Raised for any non-2xx response from hush-hush.
13 Attributes:
14 status_code: The HTTP status hush-hush responded with.
15 request_id: Populated when the response carries a documented
16 request-ID header; hush-hush's spec doesn't currently document
17 one, so this is usually ``None``. Kept as an attribute rather
18 than omitted so a future spec addition doesn't change this
19 type's shape.
20 message: The parsed ``error`` field from hush-hush's error body, or
21 ``None`` if the body wasn't the expected shape.
22 body: The raw, unparsed response body, for a caller that needs more
23 than ``message``.
24 """
26 def __init__(self, response: httpx.Response) -> None:
27 self.status_code = response.status_code
28 self.request_id: str | None = response.headers.get("X-Request-Id")
29 self.body: bytes = response.content
30 self.message: str | None = None
31 try:
32 parsed = json.loads(response.content)
33 if isinstance(parsed, dict):
34 self.message = parsed.get("error")
35 except (json.JSONDecodeError, UnicodeDecodeError):
36 pass
37 super().__init__(
38 f"hushhush: {self.status_code}: {self.message}"
39 if self.message
40 else f"hushhush: unexpected status {self.status_code}"
41 )