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

53 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-03 15:35 +0200

1""" 

2Contains module-specific custom errors. 

3""" 

4import typing 

5from dataclasses import dataclass 

6 

7 

8class ConfigError(Exception): 

9 """ 

10 Base exception class for this module. 

11 """ 

12 

13 

14# class ConfigErrorGroup(ConfigError, ExceptionGroup): 

15# """ 

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

17# """ 

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

19# more = len(errors) > 1 

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

21# s = "s" if more else "" 

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

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

24# if not errors: 

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

26 

27 

28@dataclass 

29class ConfigErrorMissingKey(ConfigError): 

30 """ 

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

32 """ 

33 

34 key: str 

35 cls: type 

36 annotated_type: type 

37 

38 def __post_init__(self) -> None: 

39 """ 

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

41 """ 

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

43 self._cls = self.cls.__name__ 

44 

45 def __str__(self) -> str: 

46 """ 

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

48 """ 

49 return ( 

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

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

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

53 ) 

54 

55 

56@dataclass 

57class ConfigErrorExtraKey(ConfigError): 

58 """ 

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

60 """ 

61 

62 key: str 

63 value: str 

64 cls: type 

65 

66 def __post_init__(self) -> None: 

67 """ 

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

69 """ 

70 self._cls = self.cls.__name__ 

71 self._type = type(self.value) 

72 

73 def __str__(self) -> str: 

74 """ 

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

76 """ 

77 return ( 

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

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

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

81 ) 

82 

83 

84@dataclass 

85class ConfigErrorCouldNotConvert(ConfigError): 

86 """ 

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

88 """ 

89 

90 from_t: type 

91 to_t: type 

92 value: typing.Any 

93 

94 def __str__(self) -> str: 

95 """ 

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

97 """ 

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

99 

100 

101@dataclass 

102class ConfigErrorInvalidType(ConfigError): 

103 """ 

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

105 """ 

106 

107 key: str 

108 value: typing.Any 

109 expected_type: type 

110 

111 def __post_init__(self) -> None: 

112 """ 

113 Store the actual type of the config variable. 

114 """ 

115 self.actual_type = type(self.value) 

116 

117 max_len = 50 

118 self._value = str(self.value) 

119 if len(self._value) > max_len: 

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

121 

122 def __str__(self) -> str: 

123 """ 

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

125 """ 

126 return ( 

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

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

129 ) 

130 

131 

132@dataclass 

133class ConfigErrorImmutable(ConfigError): 

134 """ 

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

136 """ 

137 

138 cls: type 

139 

140 def __post_init__(self) -> None: 

141 """ 

142 Store the class name. 

143 """ 

144 self._cls = self.cls.__name__ 

145 

146 def __str__(self) -> str: 

147 """ 

148 Custom error message. 

149 """ 

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

151 

152 

153@dataclass 

154class IsPostponedError(ConfigError): 

155 """ 

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

157 """ 

158 

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