Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/error.py: 36%

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

75 statements  

1"""Errors thrown by icalendar.""" 

2 

3from __future__ import annotations 

4 

5import contextlib 

6 

7 

8class InvalidCalendar(ValueError): 

9 """The calendar given is not valid. 

10 

11 This calendar does not conform with RFC 5545 or breaks other RFCs. 

12 """ 

13 

14 

15class IncompleteComponent(ValueError): 

16 """The component is missing attributes. 

17 

18 The attributes are not required, otherwise this would be 

19 an InvalidCalendar. But in order to perform calculations, 

20 this attribute is required. 

21 

22 This error is not raised in the UPPERCASE properties like .DTSTART, 

23 only in the lowercase computations like .start. 

24 """ 

25 

26 

27class IncompleteAlarmInformation(ValueError): 

28 """The alarms cannot be calculated yet because information is missing.""" 

29 

30 

31class LocalTimezoneMissing(IncompleteAlarmInformation): 

32 """We are missing the local timezone to compute the value. 

33 

34 Use Alarms.set_local_timezone(). 

35 """ 

36 

37 

38class ComponentEndMissing(IncompleteAlarmInformation): 

39 """We are missing the end of a component that the alarm is for. 

40 

41 Use Alarms.set_end(). 

42 """ 

43 

44 

45class ComponentStartMissing(IncompleteAlarmInformation): 

46 """We are missing the start of a component that the alarm is for. 

47 

48 Use Alarms.set_start(). 

49 """ 

50 

51 

52class FeatureWillBeRemovedInFutureVersion(DeprecationWarning): 

53 """This feature will be removed in a future version.""" 

54 

55 

56def _repr_index(index: str | int) -> str: 

57 """Create a JSON compatible representation for the index.""" 

58 if isinstance(index, str): 

59 return f'"{index}"' 

60 return str(index) 

61 

62 

63class JCalParsingError(ValueError): 

64 """Could not parse a part of the JCal.""" 

65 

66 _default_value = object() 

67 

68 def __init__( 

69 self, 

70 message: str, 

71 parser: str | type = "", 

72 path: list[str | int] | None | str | int = None, 

73 value: object = _default_value, 

74 ): 

75 """Create a new JCalParsingError.""" 

76 self.path = self._get_path(path) 

77 if not isinstance(parser, str): 

78 parser = parser.__name__ 

79 self.parser = parser 

80 self.message = message 

81 self.value = value 

82 full_message = message 

83 repr_path = "" 

84 if self.path: 

85 repr_path = "".join([f"[{_repr_index(index)}]" for index in self.path]) 

86 full_message = f"{repr_path}: {full_message}" 

87 repr_path += " " 

88 if parser: 

89 full_message = f"{repr_path}in {parser}: {message}" 

90 if value is not self._default_value: 

91 full_message += f" Got value: {value!r}" 

92 super().__init__(full_message) 

93 

94 @classmethod 

95 @contextlib.contextmanager 

96 def reraise_with_path_added(cls, *path_components: int | str): 

97 """Automatically re-raise the exception with path components added. 

98 

99 Raises: 

100 ~error.JCalParsingError: If there was an exception in the context. 

101 """ 

102 try: 

103 yield 

104 except JCalParsingError as e: 

105 raise cls( 

106 path=list(path_components) + e.path, 

107 parser=e.parser, 

108 message=e.message, 

109 value=e.value, 

110 ).with_traceback(e.__traceback__) from e 

111 

112 @staticmethod 

113 def _get_path(path: list[str | int] | None | str | int) -> list[str | int]: 

114 """Return the path as a list.""" 

115 if path is None: 

116 path = [] 

117 elif not isinstance(path, list): 

118 path = [path] 

119 return path 

120 

121 @classmethod 

122 def validate_property( 

123 cls, 

124 jcal_property, 

125 parser: str | type, 

126 path: list[str | int] | None | str | int = None, 

127 ): 

128 """Validate a jCal property. 

129 

130 Raises: 

131 ~error.JCalParsingError: if the property is not valid. 

132 """ 

133 path = cls._get_path(path) 

134 if not isinstance(jcal_property, list) or len(jcal_property) < 4: 

135 raise JCalParsingError( 

136 "The property must be a list with at least 4 items.", 

137 parser, 

138 path, 

139 value=jcal_property, 

140 ) 

141 if not isinstance(jcal_property[0], str): 

142 raise JCalParsingError( 

143 "The name must be a string.", parser, path + [0], value=jcal_property[0] 

144 ) 

145 if not isinstance(jcal_property[1], dict): 

146 raise JCalParsingError( 

147 "The parameters must be a mapping.", 

148 parser, 

149 path + [1], 

150 value=jcal_property[1], 

151 ) 

152 if not isinstance(jcal_property[2], str): 

153 raise JCalParsingError( 

154 "The VALUE parameter must be a string.", 

155 parser, 

156 path + [2], 

157 value=jcal_property[2], 

158 ) 

159 

160 _type_names = { 

161 str: "a string", 

162 int: "an integer", 

163 float: "a float", 

164 bool: "a boolean", 

165 } 

166 

167 @classmethod 

168 def validate_value_type( 

169 cls, 

170 jcal, 

171 expected_type: type[str | int | float | bool] 

172 | tuple[type[str | int | float | bool], ...], 

173 parser: str | type = "", 

174 path: list[str | int] | None | str | int = None, 

175 ): 

176 """Validate the type of a jCal value.""" 

177 if not isinstance(jcal, expected_type): 

178 type_name = ( 

179 cls._type_names[expected_type] 

180 if isinstance(expected_type, type) 

181 else " or ".join(cls._type_names[t] for t in expected_type) 

182 ) 

183 raise cls( 

184 f"The value must be {type_name}.", 

185 parser=parser, 

186 value=jcal, 

187 path=path, 

188 ) 

189 

190 @classmethod 

191 def validate_list_type( 

192 cls, 

193 jcal, 

194 expected_type: type[str | int | float | bool], 

195 parser: str | type = "", 

196 path: list[str | int] | None | str | int = None, 

197 ): 

198 """Validate the type of each item in a jCal list.""" 

199 path = cls._get_path(path) 

200 if not isinstance(jcal, list): 

201 raise cls( 

202 "The value must be a list.", 

203 parser=parser, 

204 value=jcal, 

205 path=path, 

206 ) 

207 for index, item in enumerate(jcal): 

208 if not isinstance(item, expected_type): 

209 type_name = cls._type_names[expected_type] 

210 raise cls( 

211 f"Each item in the list must be {type_name}.", 

212 parser=parser, 

213 value=item, 

214 path=path + [index], 

215 ) 

216 

217 

218__all__ = [ 

219 "ComponentEndMissing", 

220 "ComponentStartMissing", 

221 "FeatureWillBeRemovedInFutureVersion", 

222 "IncompleteAlarmInformation", 

223 "IncompleteComponent", 

224 "InvalidCalendar", 

225 "JCalParsingError", 

226 "LocalTimezoneMissing", 

227]