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

40 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-09 20:12 +0100

1""" 

2Contains module-specific custom errors. 

3""" 

4 

5import typing 

6from dataclasses import dataclass 

7 

8 

9class ConfigError(Exception): 

10 """ 

11 Base exception class for this module. 

12 """ 

13 

14 

15# class ConfigErrorGroup(ConfigError, ExceptionGroup): 

16# """ 

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

18# """ 

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

20# more = len(errors) > 1 

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

22# s = "s" if more else "" 

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

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

25# if not errors: 

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

27 

28 

29@dataclass 

30class ConfigErrorMissingKey(ConfigError): 

31 """ 

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

33 """ 

34 

35 key: str 

36 cls: type 

37 annotated_type: type 

38 

39 def __post_init__(self) -> None: 

40 """ 

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

42 """ 

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

44 self._cls = self.cls.__name__ 

45 

46 def __str__(self) -> str: 

47 """ 

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

49 """ 

50 return ( 

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

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

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

54 ) 

55 

56 

57@dataclass 

58class ConfigErrorExtraKey(ConfigError): 

59 """ 

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

61 """ 

62 

63 key: str 

64 value: str 

65 cls: type 

66 

67 def __post_init__(self) -> None: 

68 """ 

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

70 """ 

71 self._cls = self.cls.__name__ 

72 self._type = type(self.value) 

73 

74 def __str__(self) -> str: 

75 """ 

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

77 """ 

78 return ( 

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

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

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

82 ) 

83 

84 

85@dataclass 

86class ConfigErrorCouldNotConvert(ConfigError): 

87 """ 

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

89 """ 

90 

91 from_t: type 

92 to_t: type 

93 value: typing.Any 

94 

95 def __str__(self) -> str: 

96 """ 

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

98 """ 

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

100 

101 

102@dataclass 

103class ConfigErrorInvalidType(ConfigError): 

104 """ 

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

106 """ 

107 

108 key: str 

109 value: typing.Any 

110 expected_type: type 

111 

112 def __post_init__(self) -> None: 

113 """ 

114 Store the actual type of the config variable. 

115 """ 

116 self.actual_type = type(self.value) 

117 

118 max_len = 50 

119 self._value = str(self.value) 

120 if len(self._value) > max_len: 

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

122 

123 def __str__(self) -> str: 

124 """ 

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

126 """ 

127 return ( 

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

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

130 ) 

131 

132 

133@dataclass 

134class ConfigErrorImmutable(ConfigError): 

135 """ 

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

137 """ 

138 

139 cls: type 

140 

141 def __post_init__(self) -> None: 

142 """ 

143 Store the class name. 

144 """ 

145 self._cls = self.cls.__name__ 

146 

147 def __str__(self) -> str: 

148 """ 

149 Custom error message. 

150 """ 

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

152 

153 

154@dataclass 

155class IsPostponedError(ConfigError): 

156 """ 

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

158 """ 

159 

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