Coverage for src/lanraragi_api/error.py: 56%

27 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-22 23:20 +0000

1from typing import Any 

2 

3 

4class APIError(Exception): 

5 """Base exception for all API client errors.""" 

6 

7 

8class APIRequestError(APIError): 

9 """Raised when a request cannot be sent to the server. 

10 

11 It covers transport level failures such as a refused connection or a 

12 timeout. HTTP error statuses are reported through ``APIHttpError`` instead. 

13 The message names the class of the underlying ``requests`` exception when 

14 one is available. 

15 

16 Attributes: 

17 url: Absolute URL the request was sent to. 

18 """ 

19 

20 def __init__(self, url: str, error_type: str | None = None): 

21 suffix = f" ({error_type})" if error_type else "" 

22 super().__init__(f"Request to {url} failed{suffix}") 

23 self.url: str = url 

24 

25 

26class APIHttpError(APIError): 

27 """Raised when the server answers with an unexpected status code. 

28 

29 Attributes: 

30 status_code: HTTP status code returned by the server. 

31 url: Absolute URL the request was sent to. 

32 """ 

33 

34 def __init__(self, status_code: int, url: str): 

35 super().__init__(f"HTTP {status_code} for {url}") 

36 self.status_code: int = status_code 

37 self.url: str = url 

38 

39 

40class APIResponseDecodeError(APIError): 

41 """Raised when a response body cannot be turned into the expected model. 

42 

43 It is raised for a body that is not valid JSON for a JSON endpoint, and for 

44 a JSON payload that does not match the pydantic model of the endpoint. 

45 

46 Attributes: 

47 url: Absolute URL the request was sent to. 

48 """ 

49 

50 def __init__(self, url: str, message: str): 

51 super().__init__(f"Failed to parse response from {url}: {message}") 

52 self.url: str = url 

53 

54 

55class APIOperationError(APIError): 

56 """Raised when an operation endpoint reports a failure. 

57 

58 Only raised when raising is enabled, either through the client-level 

59 ``raise_on_operation_error`` setting or through the ``raise_on_failure`` 

60 argument of a single call. Otherwise the failed operation is returned to the 

61 caller like any other result. 

62 

63 Attributes: 

64 operation: Name of the operation reported by the server. 

65 message: Error message reported by the server, if it was kept. 

66 status_code: HTTP status code of the response, if one was returned. 

67 payload: Raw response payload, if the client was configured to keep it. 

68 """ 

69 

70 def __init__( 

71 self, 

72 operation: str, 

73 message: str | None, 

74 status_code: int | None = None, 

75 payload: dict[str, Any] | None = None, 

76 ): 

77 error_message = message or "operation failed without an error message" 

78 prefix = f"Operation '{operation}' failed" 

79 if status_code is not None: 

80 prefix = f"{prefix} with HTTP {status_code}" 

81 super().__init__(f"{prefix}: {error_message}") 

82 self.operation: str = operation 

83 self.message: str | None = message 

84 self.status_code: int | None = status_code 

85 self.payload: dict[str, Any] | None = payload