Coverage for src/paperap/exceptions.py: 91%

43 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-18 12:26 -0400

1""" 

2---------------------------------------------------------------------------- 

3 

4 METADATA: 

5 

6 File: exceptions.py 

7 Project: paperap 

8 Created: 2025-03-04 

9 Version: 0.0.7 

10 Author: Jess Mann 

11 Email: jess@jmann.me 

12 Copyright (c) 2025 Jess Mann 

13 

14---------------------------------------------------------------------------- 

15 

16 LAST MODIFIED: 

17 

18 2025-03-04 By Jess Mann 

19 

20""" 

21 

22from __future__ import annotations 

23 

24from string import Template 

25 

26import pydantic 

27 

28 

29class PaperlessError(Exception): 

30 """Base exception for all paperless client errors.""" 

31 

32 

33class ModelValidationError(PaperlessError, ValueError): 

34 """Raised when a model fails validation.""" 

35 

36 def __init__(self, message: str | None = None, model: pydantic.BaseModel | None = None) -> None: 

37 if not message: 

38 message = f"Model failed validation for {model.__class__.__name__}." 

39 super().__init__(message) 

40 

41 

42class ConfigurationError(PaperlessError): 

43 """Raised when the configuration is invalid.""" 

44 

45 

46class APIError(PaperlessError): 

47 """Raised when the API returns an error.""" 

48 

49 status_code: int | None = None 

50 

51 def __init__(self, message: str | None = None, status_code: int | None = None) -> None: 

52 self.status_code = status_code 

53 if not message: 

54 message = "An error occurred." 

55 message = f"API Error {status_code}: {message}" 

56 message = Template(message).safe_substitute(status_code=status_code) 

57 super().__init__(message) 

58 

59 

60class AuthenticationError(APIError): 

61 """Raised when authentication fails.""" 

62 

63 

64class InsufficientPermissionError(APIError): 

65 """Raised when a user does not have permission to perform an action.""" 

66 

67 

68class FeatureNotAvailableError(APIError): 

69 """Raised when a feature is not available.""" 

70 

71 

72class FilterDisabledError(FeatureNotAvailableError): 

73 """Raised when a filter is not available.""" 

74 

75 

76class RequestError(APIError): 

77 """Raised when an error occurs while making a request.""" 

78 

79 

80class BadResponseError(APIError): 

81 """Raised when a response is returned, but the status code is not 200.""" 

82 

83 

84class ResponseParsingError(APIError): 

85 """Raised when the response can't be parsed.""" 

86 

87 

88class ResourceNotFoundError(APIError): 

89 """Raised when a requested resource is not found.""" 

90 

91 resource_name: str | None = None 

92 

93 def __init__(self, message: str | None = None, resource_name: str | None = None) -> None: 

94 self.resource_name = resource_name 

95 if not message: 

96 message = "Resource ${resource} not found." 

97 message = Template(message).safe_substitute(resource=resource_name) 

98 super().__init__(message, 404) 

99 

100 

101class ObjectNotFoundError(ResourceNotFoundError): 

102 """Raised when a requested object is not found.""" 

103 

104 model_id: int | None = None 

105 

106 def __init__( 

107 self, message: str | None = None, resource_name: str | None = None, model_id: int | None = None 

108 ) -> None: 

109 self.model_id = model_id 

110 if not message: 

111 message = "Resource ${resource} (#${pk}) not found." 

112 message = Template(message).safe_substitute(resource=resource_name, pk=model_id) 

113 super().__init__(message, resource_name) 

114 

115 

116class MultipleObjectsFoundError(APIError): 

117 """Raised when multiple objects are found when only one was expected."""