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

1"""Typed errors raised for any non-2xx response from hush-hush.""" 

2 

3from __future__ import annotations 

4 

5import json 

6 

7import httpx 

8 

9 

10class APIError(Exception): 

11 """Raised for any non-2xx response from hush-hush. 

12 

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 """ 

25 

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 )