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

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

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

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

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

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

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

155 class 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!"