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

39 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-19 17:39 +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 ConfigErrorInvalidType(ConfigError): 

86 """ 

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

88 """ 

89 

90 key: str 

91 value: typing.Any 

92 expected_type: type 

93 

94 def __post_init__(self) -> None: 

95 """ 

96 Store the actual type of the config variable. 

97 """ 

98 self.actual_type = type(self.value) 

99 

100 max_len = 50 

101 self._value = str(self.value) 

102 if len(self._value) > max_len: 

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

104 

105 def __str__(self) -> str: 

106 """ 

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

108 """ 

109 return ( 

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

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

112 ) 

113 

114 

115@dataclass 

116class IsPostponedError(ConfigError): 

117 """ 

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

119 """ 

120 

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