Coverage for src/configuraptor/errors.py: 100%

56 statements  

« prev     ^ index     » next       coverage.py v7.8.1, created at 2025-05-22 10:38 +0200

1""" 

2Contains module-specific custom errors. 

3""" 

4 

5import typing 

6from dataclasses import dataclass 

7 

8from .abs import T_data 

9 

10 

11class ConfigError(Exception): 

12 """ 

13 Base exception class for this module. 

14 """ 

15 

16 

17# class ConfigErrorGroup(ConfigError, ExceptionGroup): 

18# """ 

19# Base Exception class for this module, but for exception groups (3.11+) 

20# """ 

21# def __init__(self, _type: str, errors: list[Exception]): 

22# more = len(errors) > 1 

23# cnt = "Multiple" if more else "One" 

24# s = "s" if more else "" 

25# message = f"{cnt} {_type}{s} in config!" 

26# super().__init__(message, errors) 

27# if not errors: 

28# raise ValueError("Error group raised without any errors?") 

29 

30 

31@dataclass 

32class ConfigErrorMissingKey(ConfigError): 

33 """ 

34 Exception for when the config file is missing a required key. 

35 """ 

36 

37 key: str 

38 cls: type 

39 annotated_type: type 

40 

41 def __post_init__(self) -> None: 

42 """ 

43 Automatically filles in the names of annotated type and cls for printing from __str__. 

44 """ 

45 self._annotated_type = getattr(self.annotated_type, "__name__", str(self.annotated_type)) 

46 self._cls = self.cls.__name__ 

47 

48 def __str__(self) -> str: 

49 """ 

50 Custom error message based on dataclass values and calculated actual type. 

51 """ 

52 return ( 

53 f"Config key '{self.key}' (type `{self._annotated_type}`) " 

54 f"of class `{self._cls}` was not found in the config, " 

55 f"but is required as a default value is not specified." 

56 ) 

57 

58 

59@dataclass 

60class ConfigErrorExtraKey(ConfigError): 

61 """ 

62 Exception for when the config file is missing a required key. 

63 """ 

64 

65 key: str 

66 value: str 

67 cls: type 

68 

69 def __post_init__(self) -> None: 

70 """ 

71 Automatically filles in the names of annotated type and cls for printing from __str__. 

72 """ 

73 self._cls = self.cls.__name__ 

74 self._type = type(self.value) 

75 

76 def __str__(self) -> str: 

77 """ 

78 Custom error message based on dataclass values and calculated actual type. 

79 """ 

80 return ( 

81 f"Config key '{self.key}' (value: `{self.value}` type `{self._type}`) " 

82 f"does not exist on class `{self._cls}`, but was attempted to be updated. " 

83 f"Use strict = False to allow this behavior." 

84 ) 

85 

86 

87@dataclass 

88class ConfigErrorCouldNotConvert(ConfigError): 

89 """ 

90 Raised by `convert_between` if something funky is going on (incompatible types etc.). 

91 """ 

92 

93 from_t: type 

94 to_t: type 

95 value: typing.Any 

96 

97 def __str__(self) -> str: 

98 """ 

99 Custom error message based on dataclass values and calculated actual type. 

100 """ 

101 return f"Could not convert `{self.value}` from `{self.from_t}` to `{self.to_t}`" 

102 

103 

104@dataclass 

105class ConfigErrorInvalidType(ConfigError): 

106 """ 

107 Exception for when the config file contains a key with an unexpected type. 

108 """ 

109 

110 key: str 

111 value: typing.Any 

112 expected_type: type 

113 

114 def __post_init__(self) -> None: 

115 """ 

116 Store the actual type of the config variable. 

117 """ 

118 self.actual_type = type(self.value) 

119 

120 max_len = 50 

121 self._value = str(self.value) 

122 if len(self._value) > max_len: 

123 self._value = f"{self._value[:max_len]}..." 

124 

125 def __str__(self) -> str: 

126 """ 

127 Custom error message based on dataclass values and calculated actual type. 

128 """ 

129 return ( 

130 f"Config key '{self.key}' had a value (`{self._value}`) with a type (`{self.actual_type}`) " 

131 f"that was not expected: `{self.expected_type}` is the required type." 

132 ) 

133 

134 

135@dataclass 

136class ConfigErrorImmutable(ConfigError): 

137 """ 

138 Raised when an immutable Mapping is attempted to be updated. 

139 """ 

140 

141 cls: type 

142 

143 def __post_init__(self) -> None: 

144 """ 

145 Store the class name. 

146 """ 

147 self._cls = self.cls.__name__ 

148 

149 def __str__(self) -> str: 

150 """ 

151 Custom error message. 

152 """ 

153 return f"{self._cls} is Immutable!" 

154 

155 

156@dataclass 

157class IsPostponedError(ConfigError): 

158 """ 

159 Error thrown when you try to access a 'postponed' property without filling its value first. 

160 """ 

161 

162 message: str = "This postponed property has not been filled yet!" 

163 

164 

165class FailedToLoad(ConfigError): 

166 """ 

167 Exception raised when a configuration fails to load. 

168 

169 E.g. when `load_data` is called with `strict=True` 

170 """ 

171 

172 data: T_data