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 REPEAT property of an alarm component. 

93 

94 The alarm can be defined such that it triggers repeatedly. A 

95 definition of an alarm with a repeating trigger MUST include both 

96 the "DURATION" and "REPEAT" properties. The "DURATION" property 

97 specifies the delay period, after which the alarm will repeat. 

98 The "REPEAT" property specifies the number of additional 

99 repetitions that the alarm will be triggered. This repetition 

100 count is in addition to the initial triggering of the alarm. 

101 """, 

102 ) 

103 

104 DURATION = property( 

105 property_get_duration, 

106 property_set_duration, 

107 property_del_duration, 

108 """The DURATION property of an alarm component. 

109 

110 The alarm can be defined such that it triggers repeatedly. A 

111 definition of an alarm with a repeating trigger MUST include both 

112 the "DURATION" and "REPEAT" properties. The "DURATION" property 

113 specifies the delay period, after which the alarm will repeat. 

114 """, 

115 ) 

116 

117 ACKNOWLEDGED = single_utc_property( 

118 "ACKNOWLEDGED", 

119 """This is defined in RFC 9074: 

120 

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

122 corresponding alarm was last sent or acknowledged. 

123 

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

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

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

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

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

129 

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

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

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

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

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

135 

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

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

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

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

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

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

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

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

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

145 """, 

146 ) 

147 

148 TRIGGER = create_single_property( 

149 "TRIGGER", 

150 "dt", 

151 (datetime, timedelta), 

152 timedelta | datetime | None, 

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

154 

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

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

157 specify a UTC-formatted DATE-TIME value. 

158 

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

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

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

162 An alarm with a negative duration is triggered before the 

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

164 ) 

165 

166 @property 

167 def TRIGGER_RELATED(self) -> str: 

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

169 

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

171 

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

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

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

175 

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

177 end of its parent component: 

178 

179 >>> from icalendar import Alarm 

180 >>> from datetime import timedelta 

181 >>> alarm = Alarm() 

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

183 >>> alarm.TRIGGER_RELATED = "END" 

184 """ 

185 trigger = self.get("TRIGGER") 

186 if trigger is None: 

187 return "START" 

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

189 

190 @TRIGGER_RELATED.setter 

191 def TRIGGER_RELATED(self, value: str): 

192 """Set "START" or "END".""" 

193 trigger = self.get("TRIGGER") 

194 if trigger is None: 

195 raise ValueError( 

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

197 ) 

198 trigger.params["RELATED"] = value 

199 

200 class Triggers(NamedTuple): 

201 """The computed times of alarm triggers. 

202 

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

204 

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

206 

207 absolute - triggers at a datetime in UTC 

208 """ 

209 

210 start: tuple[timedelta] 

211 end: tuple[timedelta] 

212 absolute: tuple[datetime] 

213 

214 @property 

215 def triggers(self): 

216 """The computed triggers of an Alarm. 

217 

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

219 

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

221 parent component: 

222 

223 >>> from icalendar import Alarm 

224 >>> from datetime import timedelta 

225 >>> alarm = Alarm() 

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

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

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

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

230 True 

231 >>> alarm.triggers.end 

232 () 

233 >>> alarm.triggers.absolute 

234 () 

235 """ 

236 start = [] 

237 end = [] 

238 absolute = [] 

239 trigger = self.TRIGGER 

240 if trigger is not None: 

241 if isinstance(trigger, date): 

242 absolute.append(trigger) 

243 add = absolute 

244 elif self.TRIGGER_RELATED == "START": 

245 start.append(trigger) 

246 add = start 

247 else: 

248 end.append(trigger) 

249 add = end 

250 duration = self.DURATION 

251 if duration is not None: 

252 for _ in range(self.REPEAT): 

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

254 return self.Triggers( 

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

256 ) 

257 

258 uid = single_string_property( 

259 "UID", 

260 uid_property.__doc__, 

261 "X-ALARMUID", 

262 ) 

263 summary = summary_property 

264 description = description_property 

265 attendees = attendees_property 

266 

267 @classmethod 

268 def new( 

269 cls, 

270 /, 

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

272 concepts: CONCEPTS_TYPE_SETTER = None, 

273 description: str | None = None, 

274 links: LINKS_TYPE_SETTER = None, 

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

276 related_to: RELATED_TO_TYPE_SETTER = None, 

277 summary: str | None = None, 

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

279 ): 

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

281 

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

283 

284 Parameters: 

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

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

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

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

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

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

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

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

293 

294 Returns: 

295 :class:`Alarm` 

296 

297 Raises: 

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

299 according to :rfc:`5545`. 

300 

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

302 """ 

303 alarm: Alarm = super().new( 

304 links=links, 

305 related_to=related_to, 

306 refids=refids, 

307 concepts=concepts, 

308 ) 

309 alarm.summary = summary 

310 alarm.description = description 

311 alarm.uid = uid 

312 alarm.attendees = attendees 

313 return alarm 

314 

315 @classmethod 

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

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

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

319 

320 

321__all__ = ["Alarm"]