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

1"""

2 Contains module-specific custom errors.

3"""

4

5 import typing

6 from dataclasses import dataclass

7

8 from .abs import T_data

9

10

11 class 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

32 class 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

60 class 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

88 class 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

105 class 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

136 class 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

157 class 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

165 class 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