Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/cal/alarm.py: 51%

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

72 statements  

1""":rfc:`5545` VALARM component.""" 

2 

3from __future__ import annotations 

4 

5from datetime import date, datetime, timedelta 

6from typing import TYPE_CHECKING, NamedTuple 

7 

8from icalendar.attr import ( 

9 CONCEPTS_TYPE_SETTER, 

10 LINKS_TYPE_SETTER, 

11 RELATED_TO_TYPE_SETTER, 

12 attendees_property, 

13 create_single_property, 

14 description_property, 

15 property_del_duration, 

16 property_get_duration, 

17 property_set_duration, 

18 single_int_property, 

19 single_string_property, 

20 single_utc_property, 

21 summary_property, 

22 uid_property, 

23) 

24from icalendar.cal.component import Component 

25from icalendar.cal.examples import get_example 

26 

27if TYPE_CHECKING: 

28 import uuid 

29 

30 from icalendar.prop import vCalAddress 

31 

32 

33class Alarm(Component): 

34 """ 

35 A "VALARM" calendar component is a grouping of component 

36 properties that defines an alarm or reminder for an event or a 

37 to-do. For example, it may be used to define a reminder for a 

38 pending event or an overdue to-do. 

39 

40 Example: 

41 

42 The following example creates an alarm which uses an audio file 

43 from an FTP server. 

44 

45 .. code-block:: pycon 

46 

47 >>> from icalendar import Alarm 

48 >>> alarm = Alarm.example() 

49 >>> print(alarm.to_ical().decode()) 

50 BEGIN:VALARM 

51 ACTION:AUDIO 

52 ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/sounds/bell-01.aud 

53 DURATION:PT15M 

54 REPEAT:4 

55 TRIGGER;VALUE=DATE-TIME:19970317T133000Z 

56 END:VALARM 

57 """ 

58 

59 name = "VALARM" 

60 # some properties MAY/MUST/MUST NOT appear depending on ACTION value 

61 required = ( 

62 "ACTION", 

63 "TRIGGER", 

64 ) 

65 singletons = ( 

66 "ATTACH", 

67 "ACTION", 

68 "DESCRIPTION", 

69 "SUMMARY", 

70 "TRIGGER", 

71 "DURATION", 

72 "REPEAT", 

73 "UID", 

74 "PROXIMITY", 

75 "ACKNOWLEDGED", 

76 ) 

77 inclusive = ( 

78 ( 

79 "DURATION", 

80 "REPEAT", 

81 ), 

82 ( 

83 "SUMMARY", 

84 "ATTENDEE", 

85 ), 

86 ) 

87 multiple = ("ATTENDEE", "ATTACH", "RELATED-TO") 

88 

89 REPEAT = single_int_property( 

90 "REPEAT", 

91 0, 

92 """The number of additional times the alarm is triggered after the initial trigger. 

93 

94 Defaults to ``0``, meaning the alarm fires once. To repeat the alarm, 

95 set both :attr:`REPEAT` and :attr:`DURATION`. The :attr:`DURATION` 

96 sets the gap between repetitions. ``REPEAT`` is the count of *additional* 

97 triggers, so a ``REPEAT`` of ``2`` produces three alarms in total 

98 (the initial trigger plus two repeats). 

99 

100 Conforming with :rfc:`5545#section-3.8.6.2`, this property can appear 

101 once in an :class:`~icalendar.cal.alarm.Alarm` component and must be 

102 paired with :attr:`DURATION`. 

103 

104 Example: 

105 Build an alarm that fires once and then repeats twice at 

106 five-minute intervals. 

107 

108 .. code-block:: pycon 

109 

110 >>> from datetime import timedelta 

111 >>> from icalendar import Alarm 

112 >>> alarm = Alarm() 

113 >>> alarm.TRIGGER = timedelta(minutes=-15) 

114 >>> alarm.DURATION = timedelta(minutes=5) 

115 >>> alarm.REPEAT = 2 

116 >>> alarm.REPEAT 

117 2 

118 """, 

119 ) 

120 

121 DURATION = property( 

122 property_get_duration, 

123 property_set_duration, 

124 property_del_duration, 

125 """The delay between repeated triggers of a repeating alarm. 

126 

127 Returns a :class:`datetime.timedelta` or ``None`` when the alarm 

128 has no :attr:`DURATION` set. Setting this attribute accepts a 

129 :class:`~datetime.timedelta`; deleting it removes the property 

130 from the component. 

131 

132 :attr:`DURATION` is meaningful only for repeating alarms and must 

133 be paired with :attr:`REPEAT`. The two together produce 

134 ``REPEAT`` additional triggers, each spaced by ``DURATION`` after 

135 the initial trigger. 

136 

137 Conforming with :rfc:`5545#section-3.8.2.5`, the ``DURATION`` property 

138 can appear once in an :class:`~icalendar.cal.alarm.Alarm` component. 

139 

140 Example: 

141 Pair :attr:`DURATION` with :attr:`REPEAT` to produce three 

142 triggers spaced ten minutes apart. 

143 

144 .. code-block:: pycon 

145 

146 >>> from datetime import timedelta 

147 >>> from icalendar import Alarm 

148 >>> alarm = Alarm() 

149 >>> alarm.TRIGGER = timedelta(minutes=-30) 

150 >>> alarm.DURATION = timedelta(minutes=10) 

151 >>> alarm.REPEAT = 2 

152 >>> alarm.DURATION 

153 datetime.timedelta(seconds=600) 

154 """, 

155 ) 

156 

157 ACKNOWLEDGED = single_utc_property( 

158 "ACKNOWLEDGED", 

159 """This is defined in RFC 9074: 

160 

161 Purpose: This property specifies the UTC date and time at which the 

162 corresponding alarm was last sent or acknowledged. 

163 

164 This property is used to specify when an alarm was last sent or acknowledged. 

165 This allows clients to determine when a pending alarm has been acknowledged 

166 by a calendar user so that any alerts can be dismissed across multiple devices. 

167 It also allows clients to track repeating alarms or alarms on recurring events or 

168 to-dos to ensure that the right number of missed alarms can be tracked. 

169 

170 Clients SHOULD set this property to the current date-time value in UTC 

171 when a calendar user acknowledges a pending alarm. Certain kinds of alarms, 

172 such as email-based alerts, might not provide feedback as to when the calendar user 

173 sees them. For those kinds of alarms, the client SHOULD set this property 

174 when the alarm is triggered and the action is successfully carried out. 

175 

176 When an alarm is triggered on a client, clients can check to see if an "ACKNOWLEDGED" 

177 property is present. If it is, and the value of that property is greater than or 

178 equal to the computed trigger time for the alarm, then the client SHOULD NOT trigger 

179 the alarm. Similarly, if an alarm has been triggered and 

180 an "alert" has been presented to a calendar user, clients can monitor 

181 the iCalendar data to determine whether an "ACKNOWLEDGED" property is added or 

182 changed in the alarm component. If the value of any "ACKNOWLEDGED" property 

183 in the alarm changes and is greater than or equal to the trigger time of the alarm, 

184 then clients SHOULD dismiss or cancel any "alert" presented to the calendar user. 

185 """, 

186 ) 

187 

188 TRIGGER = create_single_property( 

189 "TRIGGER", 

190 "dt", 

191 (datetime, timedelta), 

192 timedelta | datetime | None, 

193 """Purpose: This property specifies when an alarm will trigger. 

194 

195 Value Type: The default value type is DURATION. The value type can 

196 be set to a DATE-TIME value type, in which case the value MUST 

197 specify a UTC-formatted DATE-TIME value. 

198 

199 Either a positive or negative duration may be specified for the 

200 "TRIGGER" property. An alarm with a positive duration is 

201 triggered after the associated start or end of the event or to-do. 

202 An alarm with a negative duration is triggered before the 

203 associated start or end of the event or to-do.""", 

204 ) 

205 

206 @property 

207 def TRIGGER_RELATED(self) -> str: 

208 """The RELATED parameter of the TRIGGER property. 

209 

210 Values are either "START" (default) or "END". 

211 

212 A value of START will set the alarm to trigger off the 

213 start of the associated event or to-do. A value of END will set 

214 the alarm to trigger off the end of the associated event or to-do. 

215 

216 In this example, we create an alarm that triggers two hours after the 

217 end of its parent component: 

218 

219 >>> from icalendar import Alarm 

220 >>> from datetime import timedelta 

221 >>> alarm = Alarm() 

222 >>> alarm.TRIGGER = timedelta(hours=2) 

223 >>> alarm.TRIGGER_RELATED = "END" 

224 """ 

225 trigger = self.get("TRIGGER") 

226 if trigger is None: 

227 return "START" 

228 return trigger.params.get("RELATED", "START") 

229 

230 @TRIGGER_RELATED.setter 

231 def TRIGGER_RELATED(self, value: str): 

232 """Set "START" or "END".""" 

233 trigger = self.get("TRIGGER") 

234 if trigger is None: 

235 raise ValueError( 

236 "You must set a TRIGGER before setting the RELATED parameter." 

237 ) 

238 trigger.params["RELATED"] = value 

239 

240 class Triggers(NamedTuple): 

241 """The computed times of alarm triggers. 

242 

243 start - triggers relative to the start of the Event or Todo (timedelta) 

244 

245 end - triggers relative to the end of the Event or Todo (timedelta) 

246 

247 absolute - triggers at a datetime in UTC 

248 """ 

249 

250 start: tuple[timedelta] 

251 end: tuple[timedelta] 

252 absolute: tuple[datetime] 

253 

254 @property 

255 def triggers(self): 

256 """The computed triggers of an Alarm. 

257 

258 This takes the TRIGGER, DURATION and REPEAT properties into account. 

259 

260 Here, we create an alarm that triggers 3 times before the start of the 

261 parent component: 

262 

263 >>> from icalendar import Alarm 

264 >>> from datetime import timedelta 

265 >>> alarm = Alarm() 

266 >>> alarm.TRIGGER = timedelta(hours=-4) # trigger 4 hours before START 

267 >>> alarm.DURATION = timedelta(hours=1) # after 1 hour trigger again 

268 >>> alarm.REPEAT = 2 # trigger 2 more times 

269 >>> alarm.triggers.start == (timedelta(hours=-4), timedelta(hours=-3), timedelta(hours=-2)) 

270 True 

271 >>> alarm.triggers.end 

272 () 

273 >>> alarm.triggers.absolute 

274 () 

275 """ 

276 start = [] 

277 end = [] 

278 absolute = [] 

279 trigger = self.TRIGGER 

280 if trigger is not None: 

281 if isinstance(trigger, date): 

282 absolute.append(trigger) 

283 add = absolute 

284 elif self.TRIGGER_RELATED == "START": 

285 start.append(trigger) 

286 add = start 

287 else: 

288 end.append(trigger) 

289 add = end 

290 duration = self.DURATION 

291 if duration is not None: 

292 for _ in range(self.REPEAT): 

293 add.append(add[-1] + duration) 

294 return self.Triggers( 

295 start=tuple(start), end=tuple(end), absolute=tuple(absolute) 

296 ) 

297 

298 uid = single_string_property( 

299 "UID", 

300 uid_property.__doc__, 

301 "X-ALARMUID", 

302 ) 

303 summary = summary_property 

304 description = description_property 

305 attendees = attendees_property 

306 

307 @classmethod 

308 def new( 

309 cls, 

310 /, 

311 attendees: list[vCalAddress] | None = None, 

312 concepts: CONCEPTS_TYPE_SETTER = None, 

313 description: str | None = None, 

314 links: LINKS_TYPE_SETTER = None, 

315 refids: list[str] | str | None = None, 

316 related_to: RELATED_TO_TYPE_SETTER = None, 

317 summary: str | None = None, 

318 uid: str | uuid.UUID | None = None, 

319 ): 

320 """Create a new alarm with all required properties. 

321 

322 This creates a new Alarm in accordance with :rfc:`5545`. 

323 

324 Parameters: 

325 attendees: The :attr:`attendees` of the alarm. 

326 concepts: The :attr:`~icalendar.Component.concepts` of the alarm. 

327 description: The :attr:`description` of the alarm. 

328 links: The :attr:`~icalendar.Component.links` of the alarm. 

329 refids: :attr:`~icalendar.Component.refids` of the alarm. 

330 related_to: :attr:`~icalendar.Component.related_to` of the alarm. 

331 summary: The :attr:`summary` of the alarm. 

332 uid: The :attr:`uid` of the alarm. 

333 

334 Returns: 

335 :class:`Alarm` 

336 

337 Raises: 

338 ~error.InvalidCalendar: If the content is not valid 

339 according to :rfc:`5545`. 

340 

341 .. warning:: As time progresses, we will be stricter with the validation. 

342 """ 

343 alarm: Alarm = super().new( 

344 links=links, 

345 related_to=related_to, 

346 refids=refids, 

347 concepts=concepts, 

348 ) 

349 alarm.summary = summary 

350 alarm.description = description 

351 alarm.uid = uid 

352 alarm.attendees = attendees 

353 return alarm 

354 

355 @classmethod 

356 def example(cls, name: str = "example") -> Alarm: 

357 """Return the alarm example with the given name.""" 

358 return cls.from_ical(get_example("alarms", name)) 

359 

360 

361__all__ = ["Alarm"]