Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cattrs/errors.py: 38%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

73 statements  

1from collections.abc import Sequence 

2from typing import Any, Optional, Union 

3 

4from typing_extensions import Self 

5 

6from cattrs._compat import ExceptionGroup 

7 

8 

9class StructureHandlerNotFoundError(Exception): 

10 """ 

11 Error raised when structuring cannot find a handler for converting inputs into 

12 :attr:`type_`. 

13 """ 

14 

15 def __init__(self, message: str, type_: type) -> None: 

16 super().__init__(message, type_) 

17 self.message = message 

18 self.type_ = type_ 

19 

20 def __str__(self) -> str: 

21 return self.message 

22 

23 

24class BaseValidationError(ExceptionGroup): 

25 cl: type 

26 

27 def __new__(cls, message: str, excs: Sequence[Exception], cl: type) -> Self: 

28 obj = super().__new__(cls, message, excs) 

29 obj.cl = cl 

30 return obj 

31 

32 def derive(self, excs: Sequence[Exception]) -> Self: 

33 return self.__class__(self.message, excs, self.cl) 

34 

35 

36class IterableValidationNote(str): 

37 """Attached as a note to an exception when an iterable element fails structuring.""" 

38 

39 index: Union[int, str] # Ints for list indices, strs for dict keys 

40 type: Any 

41 

42 def __new__(cls, string: str, index: Union[int, str], type: Any) -> Self: 

43 instance = str.__new__(cls, string) 

44 instance.index = index 

45 instance.type = type 

46 return instance 

47 

48 def __getnewargs__(self) -> tuple[str, Union[int, str], Any]: 

49 return (str(self), self.index, self.type) 

50 

51 

52class IterableValidationError(BaseValidationError): 

53 """Raised when structuring an iterable.""" 

54 

55 def group_exceptions( 

56 self, 

57 ) -> tuple[list[tuple[Exception, IterableValidationNote]], list[Exception]]: 

58 """Split the exceptions into two groups: with and without validation notes.""" 

59 excs_with_notes = [] 

60 other_excs = [] 

61 for subexc in self.exceptions: 

62 if hasattr(subexc, "__notes__"): 

63 for note in subexc.__notes__: 

64 if note.__class__ is IterableValidationNote: 

65 excs_with_notes.append((subexc, note)) 

66 break 

67 else: 

68 other_excs.append(subexc) 

69 else: 

70 other_excs.append(subexc) 

71 

72 return excs_with_notes, other_excs 

73 

74 

75class AttributeValidationNote(str): 

76 """Attached as a note to an exception when an attribute fails structuring.""" 

77 

78 name: str 

79 type: Any 

80 

81 def __new__(cls, string: str, name: str, type: Any) -> Self: 

82 instance = str.__new__(cls, string) 

83 instance.name = name 

84 instance.type = type 

85 return instance 

86 

87 def __getnewargs__(self) -> tuple[str, str, Any]: 

88 return (str(self), self.name, self.type) 

89 

90 

91class ClassValidationError(BaseValidationError): 

92 """Raised when validating a class if any attributes are invalid.""" 

93 

94 def group_exceptions( 

95 self, 

96 ) -> tuple[list[tuple[Exception, AttributeValidationNote]], list[Exception]]: 

97 """Split the exceptions into two groups: with and without validation notes.""" 

98 excs_with_notes = [] 

99 other_excs = [] 

100 for subexc in self.exceptions: 

101 if hasattr(subexc, "__notes__"): 

102 for note in subexc.__notes__: 

103 if note.__class__ is AttributeValidationNote: 

104 excs_with_notes.append((subexc, note)) 

105 break 

106 else: 

107 other_excs.append(subexc) 

108 else: 

109 other_excs.append(subexc) 

110 

111 return excs_with_notes, other_excs 

112 

113 

114class ForbiddenExtraKeysError(Exception): 

115 """ 

116 Raised when `forbid_extra_keys` is activated and such extra keys are detected 

117 during structuring. 

118 

119 The attribute `extra_fields` is a sequence of those extra keys, which were the 

120 cause of this error, and `cl` is the class which was structured with those extra 

121 keys. 

122 """ 

123 

124 def __init__( 

125 self, message: Optional[str], cl: type, extra_fields: set[str] 

126 ) -> None: 

127 self.message = message 

128 self.cl = cl 

129 self.extra_fields = extra_fields 

130 

131 super().__init__(message, cl, extra_fields) 

132 

133 def __str__(self) -> str: 

134 return ( 

135 self.message 

136 or f"Extra fields in constructor for {self.cl.__name__}: " 

137 f"{', '.join(sorted(self.extra_fields))}" 

138 )