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

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

74 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 CattrsError(Exception): 

10 """Base ``cattrs`` exception.""" 

11 

12 

13class StructureHandlerNotFoundError(CattrsError): 

14 """ 

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

16 :attr:`type_`. 

17 """ 

18 

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

20 super().__init__(message, type_) 

21 self.message = message 

22 self.type_ = type_ 

23 

24 def __str__(self) -> str: 

25 return self.message 

26 

27 

28class BaseValidationError(ExceptionGroup, CattrsError): 

29 cl: type 

30 

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

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

33 obj.cl = cl 

34 return obj 

35 

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

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

38 

39 

40class IterableValidationNote(str): 

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

42 

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

44 type: Any 

45 

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

47 instance = str.__new__(cls, string) 

48 instance.index = index 

49 instance.type = type 

50 return instance 

51 

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

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

54 

55 

56class IterableValidationError(BaseValidationError): 

57 """Raised when structuring an iterable.""" 

58 

59 def group_exceptions( 

60 self, 

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

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

63 excs_with_notes = [] 

64 other_excs = [] 

65 for subexc in self.exceptions: 

66 if hasattr(subexc, "__notes__"): 

67 for note in subexc.__notes__: 

68 if note.__class__ is IterableValidationNote: 

69 excs_with_notes.append((subexc, note)) 

70 break 

71 else: 

72 other_excs.append(subexc) 

73 else: 

74 other_excs.append(subexc) 

75 

76 return excs_with_notes, other_excs 

77 

78 

79class AttributeValidationNote(str): 

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

81 

82 name: str 

83 type: Any 

84 

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

86 instance = str.__new__(cls, string) 

87 instance.name = name 

88 instance.type = type 

89 return instance 

90 

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

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

93 

94 

95class ClassValidationError(BaseValidationError): 

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

97 

98 def group_exceptions( 

99 self, 

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

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

102 excs_with_notes = [] 

103 other_excs = [] 

104 for subexc in self.exceptions: 

105 if hasattr(subexc, "__notes__"): 

106 for note in subexc.__notes__: 

107 if note.__class__ is AttributeValidationNote: 

108 excs_with_notes.append((subexc, note)) 

109 break 

110 else: 

111 other_excs.append(subexc) 

112 else: 

113 other_excs.append(subexc) 

114 

115 return excs_with_notes, other_excs 

116 

117 

118class ForbiddenExtraKeysError(CattrsError): 

119 """ 

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

121 during structuring. 

122 

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

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

125 keys. 

126 """ 

127 

128 def __init__( 

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

130 ) -> None: 

131 self.message = message 

132 self.cl = cl 

133 self.extra_fields = extra_fields 

134 

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

136 

137 def __str__(self) -> str: 

138 return ( 

139 self.message 

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

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

142 )