Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/error.py: 37%
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
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
1"""Errors thrown by icalendar."""
3from __future__ import annotations
5import contextlib
8class InvalidCalendar(ValueError):
9 """The calendar given is not valid.
11 This calendar does not conform with RFC 5545 or breaks other RFCs.
12 """
15class BrokenCalendarProperty(InvalidCalendar):
16 """A property could not be parsed and its value is broken.
18 This error is raised when accessing attributes on a
19 :class:`~icalendar.prop.vBroken` property that would normally
20 be present on the expected type. The original parse error
21 is chained as ``__cause__``.
22 """
25class IncompleteComponent(ValueError):
26 """The component is missing attributes.
28 The attributes are not required, otherwise this would be
29 an InvalidCalendar. But in order to perform calculations,
30 this attribute is required.
32 This error is not raised in the UPPERCASE properties like .DTSTART,
33 only in the lowercase computations like .start.
34 """
37class IncompleteAlarmInformation(ValueError):
38 """The alarms cannot be calculated yet because information is missing."""
41class LocalTimezoneMissing(IncompleteAlarmInformation):
42 """We are missing the local timezone to compute the value.
44 Use Alarms.set_local_timezone().
45 """
48class ComponentEndMissing(IncompleteAlarmInformation):
49 """We are missing the end of a component that the alarm is for.
51 Use Alarms.set_end().
52 """
55class ComponentStartMissing(IncompleteAlarmInformation):
56 """We are missing the start of a component that the alarm is for.
58 Use Alarms.set_start().
59 """
62class FeatureWillBeRemovedInFutureVersion(DeprecationWarning):
63 """This feature will be removed in a future version."""
66def _repr_index(index: str | int) -> str:
67 """Create a JSON compatible representation for the index."""
68 if isinstance(index, str):
69 return f'"{index}"'
70 return str(index)
73class JCalParsingError(ValueError):
74 """Could not parse a part of the JCal."""
76 _default_value = object()
78 def __init__(
79 self,
80 message: str,
81 parser: str | type = "",
82 path: list[str | int] | None | str | int = None,
83 value: object = _default_value,
84 ):
85 """Create a new JCalParsingError."""
86 self.path = self._get_path(path)
87 if not isinstance(parser, str):
88 parser = parser.__name__
89 self.parser = parser
90 self.message = message
91 self.value = value
92 full_message = message
93 repr_path = ""
94 if self.path:
95 repr_path = "".join([f"[{_repr_index(index)}]" for index in self.path])
96 full_message = f"{repr_path}: {full_message}"
97 repr_path += " "
98 if parser:
99 full_message = f"{repr_path}in {parser}: {message}"
100 if value is not self._default_value:
101 full_message += f" Got value: {value!r}"
102 super().__init__(full_message)
104 @classmethod
105 @contextlib.contextmanager
106 def reraise_with_path_added(cls, *path_components: int | str):
107 """Automatically re-raise the exception with path components added.
109 Raises:
110 ~error.JCalParsingError: If there was an exception in the context.
111 """
112 try:
113 yield
114 except JCalParsingError as e:
115 raise cls(
116 path=list(path_components) + e.path,
117 parser=e.parser,
118 message=e.message,
119 value=e.value,
120 ).with_traceback(e.__traceback__) from e
122 @staticmethod
123 def _get_path(path: list[str | int] | None | str | int) -> list[str | int]:
124 """Return the path as a list."""
125 if path is None:
126 path = []
127 elif not isinstance(path, list):
128 path = [path]
129 return path
131 @classmethod
132 def validate_property(
133 cls,
134 jcal_property,
135 parser: str | type,
136 path: list[str | int] | None | str | int = None,
137 ):
138 """Validate a jCal property.
140 Raises:
141 ~error.JCalParsingError: if the property is not valid.
142 """
143 path = cls._get_path(path)
144 if not isinstance(jcal_property, list) or len(jcal_property) < 4:
145 raise JCalParsingError(
146 "The property must be a list with at least 4 items.",
147 parser,
148 path,
149 value=jcal_property,
150 )
151 if not isinstance(jcal_property[0], str):
152 raise JCalParsingError(
153 "The name must be a string.", parser, path + [0], value=jcal_property[0]
154 )
155 if not isinstance(jcal_property[1], dict):
156 raise JCalParsingError(
157 "The parameters must be a mapping.",
158 parser,
159 path + [1],
160 value=jcal_property[1],
161 )
162 if not isinstance(jcal_property[2], str):
163 raise JCalParsingError(
164 "The VALUE parameter must be a string.",
165 parser,
166 path + [2],
167 value=jcal_property[2],
168 )
170 _type_names = {
171 str: "a string",
172 int: "an integer",
173 float: "a float",
174 bool: "a boolean",
175 }
177 @classmethod
178 def validate_value_type(
179 cls,
180 jcal,
181 expected_type: type[str | int | float | bool]
182 | tuple[type[str | int | float | bool], ...],
183 parser: str | type = "",
184 path: list[str | int] | None | str | int = None,
185 ):
186 """Validate the type of a jCal value."""
187 if not isinstance(jcal, expected_type):
188 type_name = (
189 cls._type_names[expected_type]
190 if isinstance(expected_type, type)
191 else " or ".join(cls._type_names[t] for t in expected_type)
192 )
193 raise cls(
194 f"The value must be {type_name}.",
195 parser=parser,
196 value=jcal,
197 path=path,
198 )
200 @classmethod
201 def validate_list_type(
202 cls,
203 jcal,
204 expected_type: type[str | int | float | bool],
205 parser: str | type = "",
206 path: list[str | int] | None | str | int = None,
207 ):
208 """Validate the type of each item in a jCal list."""
209 path = cls._get_path(path)
210 if not isinstance(jcal, list):
211 raise cls(
212 "The value must be a list.",
213 parser=parser,
214 value=jcal,
215 path=path,
216 )
217 for index, item in enumerate(jcal):
218 if not isinstance(item, expected_type):
219 type_name = cls._type_names[expected_type]
220 raise cls(
221 f"Each item in the list must be {type_name}.",
222 parser=parser,
223 value=item,
224 path=path + [index],
225 )
228__all__ = [
229 "BrokenCalendarProperty",
230 "ComponentEndMissing",
231 "ComponentStartMissing",
232 "FeatureWillBeRemovedInFutureVersion",
233 "IncompleteAlarmInformation",
234 "IncompleteComponent",
235 "InvalidCalendar",
236 "JCalParsingError",
237 "LocalTimezoneMissing",
238]