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

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

127 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 repeat_property, 

19 single_int_property, 

20 single_string_property, 

21 single_utc_property, 

22 summary_property, 

23 uid_property, 

24) 

25from icalendar.cal.component import Component 

26from icalendar.cal.examples import get_example 

27from icalendar.error import InvalidCalendar 

28from icalendar.prop.binary import vBinary 

29 

30if TYPE_CHECKING: 

31 import uuid 

32 from collections.abc import Sequence 

33 

34 from icalendar.prop import vCalAddress 

35 

36 

37class Alarm(Component): 

38 """ 

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

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

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

42 pending event or an overdue to-do. 

43 

44 Example: 

45 

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

47 from an FTP server. 

48 

49 .. code-block:: pycon 

50 

51 >>> from icalendar import Alarm 

52 >>> alarm = Alarm.example() 

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

54 BEGIN:VALARM 

55 ACTION:AUDIO 

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

57 DURATION:PT15M 

58 REPEAT:4 

59 TRIGGER;VALUE=DATE-TIME:19970317T133000Z 

60 END:VALARM 

61 """ 

62 

63 name = "VALARM" 

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

65 required = ( 

66 "ACTION", 

67 "TRIGGER", 

68 ) 

69 singletons = ( 

70 "ACTION", 

71 "DESCRIPTION", 

72 "SUMMARY", 

73 "TRIGGER", 

74 "DURATION", 

75 "REPEAT", 

76 "UID", 

77 "PROXIMITY", 

78 "ACKNOWLEDGED", 

79 ) 

80 inclusive = ( 

81 ( 

82 "DURATION", 

83 "REPEAT", 

84 ), 

85 ( 

86 "SUMMARY", 

87 "ATTENDEE", 

88 ), 

89 ) 

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

91 

92 REPEAT = single_int_property( 

93 "REPEAT", 

94 0, 

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

96 

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

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

99 sets the gap between repetitions. :attr:`REPEAT` is the count of *additional* 

100 triggers, so a :attr:`REPEAT` of ``2`` produces three alarms in total 

101 (the initial trigger plus two repeats). 

102 

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

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

105 paired with :attr:`DURATION`. 

106 

107 Example: 

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

109 five-minute intervals. 

110 

111 .. code-block:: pycon 

112 

113 >>> from datetime import timedelta 

114 >>> from icalendar import Alarm 

115 >>> alarm = Alarm() 

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

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

118 >>> alarm.REPEAT = 2 

119 >>> alarm.REPEAT 

120 2 

121 """, 

122 ) 

123 

124 DURATION = property( 

125 property_get_duration, 

126 property_set_duration, 

127 property_del_duration, 

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

129 

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

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

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

133 from the component. 

134 

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

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

137 :attr:`REPEAT` additional triggers, each spaced by :attr:`DURATION` after 

138 the initial trigger. 

139 

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

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

142 

143 Example: 

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

145 triggers spaced ten minutes apart. 

146 

147 .. code-block:: pycon 

148 

149 >>> from datetime import timedelta 

150 >>> from icalendar import Alarm 

151 >>> alarm = Alarm() 

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

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

154 >>> alarm.REPEAT = 2 

155 >>> alarm.DURATION 

156 datetime.timedelta(seconds=600) 

157 """, 

158 ) 

159 

160 ACKNOWLEDGED = single_utc_property( 

161 "ACKNOWLEDGED", 

162 """This property is the UTC datetime at which this alarm was last sent or acknowledged as defined in :rfc:`9074`. 

163 

164 Setting this property allows calendar clients to 

165 dismiss or suppress an alarm across multiple devices. Once set to a value 

166 greater than or equal to the alarm's computed trigger time, conforming clients 

167 will not refire the alarm. 

168 

169 Returns ``None`` when no acknowledgment has been recorded. 

170 

171 Example: 

172 Mark an alarm as acknowledged. Note that the example uses an arbitrary time 

173 for the purpose of passing doctests. In actual practice, clients should 

174 use the current time in UTC, such as ``datetime.now(UTC)``. 

175 

176 .. code-block:: pycon 

177 

178 >>> from datetime import timezone, datetime 

179 >>> from icalendar import Alarm 

180 >>> UTC = timezone.utc 

181 >>> alarm = Alarm() 

182 >>> alarm.ACKNOWLEDGED = datetime(2024, 1, 15, 10, 0, tzinfo=UTC) 

183 >>> alarm.ACKNOWLEDGED 

184 datetime.datetime(2024, 1, 15, 10, 0, tzinfo=ZoneInfo(key='UTC')) 

185 

186 See also: 

187 :attr:`TRIGGER`, the time at which the alarm fires. 

188 """, 

189 ) 

190 

191 TRIGGER = create_single_property( 

192 "TRIGGER", 

193 "dt", 

194 (datetime, timedelta), 

195 timedelta | datetime | None, 

196 """The time at which this alarm fires, per :rfc:`5545#section-3.8.6.3`. 

197 

198 The value is either a :class:`~datetime.timedelta` (relative trigger) or a 

199 UTC :class:`~datetime.datetime` (absolute trigger). 

200 

201 A negative :class:`~datetime.timedelta` fires *before* the related 

202 component boundary (start or end); a positive one fires *after* it. 

203 Use :attr:`TRIGGER_RELATED` to choose whether the offset is measured from 

204 the start or the end of the parent event or to-do. 

205 An absolute trigger fires at an exact UTC point in time regardless of the 

206 parent component's dates. 

207 

208 Examples: 

209 Set an alarm to fire 15 minutes before the start of an event. 

210 

211 .. code-block:: pycon 

212 

213 >>> from datetime import datetime, timedelta, timezone 

214 >>> from icalendar import Alarm, Event 

215 >>> UTC = timezone.utc 

216 >>> event = Event() 

217 >>> event.start = datetime(2024, 1, 15, 10, 0, tzinfo=UTC) 

218 >>> alarm = Alarm() 

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

220 >>> event.add_component(alarm) 

221 >>> event.alarms.times[0].trigger 

222 datetime.datetime(2024, 1, 15, 9, 45, tzinfo=datetime.timezone.utc) 

223 

224 Set an absolute trigger to fire at a specific UTC time. 

225 

226 .. code-block:: pycon 

227 

228 >>> absolute_alarm = Alarm() 

229 >>> absolute_alarm.TRIGGER = datetime(2024, 1, 15, 9, 45, tzinfo=UTC) 

230 >>> absolute_alarm.TRIGGER 

231 datetime.datetime(2024, 1, 15, 9, 45, tzinfo=datetime.timezone.utc) 

232 

233 See also: 

234 :attr:`TRIGGER_RELATED`, :attr:`DURATION`, :attr:`REPEAT` 

235 """, 

236 ) 

237 

238 @property 

239 def TRIGGER_RELATED(self) -> str: 

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

241 

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

243 

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

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

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

247 

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

249 end of its parent component. 

250 

251 >>> from icalendar import Alarm 

252 >>> from datetime import timedelta 

253 >>> alarm = Alarm() 

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

255 >>> alarm.TRIGGER_RELATED = "END" 

256 """ 

257 trigger = self.get("TRIGGER") 

258 if trigger is None: 

259 return "START" 

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

261 

262 @TRIGGER_RELATED.setter 

263 def TRIGGER_RELATED(self, value: str): 

264 """Set "START" or "END".""" 

265 trigger = self.get("TRIGGER") 

266 if trigger is None: 

267 raise ValueError( 

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

269 ) 

270 trigger.params["RELATED"] = value 

271 

272 class Triggers(NamedTuple): 

273 """The computed times of alarm triggers. 

274 

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

276 

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

278 

279 absolute - triggers at a datetime in UTC 

280 """ 

281 

282 start: tuple[timedelta] 

283 end: tuple[timedelta] 

284 absolute: tuple[datetime] 

285 

286 @property 

287 def triggers(self): 

288 """The computed triggers of an Alarm. 

289 

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

291 

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

293 parent component. 

294 

295 >>> from icalendar import Alarm 

296 >>> from datetime import timedelta 

297 >>> alarm = Alarm() 

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

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

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

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

302 True 

303 >>> alarm.triggers.end 

304 () 

305 >>> alarm.triggers.absolute 

306 () 

307 """ 

308 start = [] 

309 end = [] 

310 absolute = [] 

311 trigger = self.TRIGGER 

312 if trigger is not None: 

313 if isinstance(trigger, date): 

314 absolute.append(trigger) 

315 add = absolute 

316 elif self.TRIGGER_RELATED == "START": 

317 start.append(trigger) 

318 add = start 

319 else: 

320 end.append(trigger) 

321 add = end 

322 duration = self.DURATION 

323 if duration is not None: 

324 for _ in range(self.repeat): 

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

326 return self.Triggers( 

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

328 ) 

329 

330 repeat = repeat_property 

331 

332 ACTION = single_string_property( 

333 "ACTION", 

334 """The action invoked when the alarm triggers. 

335 

336 Typical values defined by :rfc:`5545#section-3.8.6.1` are 

337 ``AUDIO``, ``DISPLAY``, and ``EMAIL``. The empty string is 

338 returned when no ``ACTION`` property is present. 

339 """, 

340 ) 

341 uid = single_string_property( 

342 "UID", 

343 uid_property.__doc__, 

344 ["X-ALARMUID", "X-EVOLUTION-ALARM-UID"], 

345 ) 

346 summary = summary_property 

347 description = description_property 

348 attendees = attendees_property 

349 

350 @classmethod 

351 def new( 

352 cls, 

353 /, 

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

355 concepts: CONCEPTS_TYPE_SETTER = None, 

356 description: str | None = None, 

357 links: LINKS_TYPE_SETTER = None, 

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

359 related_to: RELATED_TO_TYPE_SETTER = None, 

360 summary: str | None = None, 

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

362 ): 

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

364 

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

366 

367 Parameters: 

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

369 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the alarm. 

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

371 links: The :attr:`~icalendar.cal.component.Component.links` of the alarm. 

372 refids: :attr:`~icalendar.cal.component.Component.refids` of the alarm. 

373 related_to: :attr:`~icalendar.cal.component.Component.related_to` of the alarm. 

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

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

376 

377 Returns: 

378 :class:`Alarm` 

379 

380 Raises: 

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

382 according to :rfc:`5545`. 

383 

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

385 """ 

386 alarm: Alarm = super().new( 

387 links=links, 

388 related_to=related_to, 

389 refids=refids, 

390 concepts=concepts, 

391 ) 

392 alarm.summary = summary 

393 alarm.description = description 

394 alarm.uid = uid 

395 alarm.attendees = attendees 

396 return alarm 

397 

398 def _apply_duration_repeat( 

399 self, 

400 duration: timedelta | None, 

401 repeat: int | None, 

402 ) -> None: 

403 if duration is not None or repeat is not None: 

404 if duration is None or repeat is None: 

405 raise InvalidCalendar( 

406 "DURATION and REPEAT must be set together or not at all" 

407 ) 

408 self.DURATION = duration 

409 self.REPEAT = repeat 

410 

411 @classmethod 

412 def new_display( 

413 cls, 

414 description: str, 

415 trigger: timedelta | datetime, 

416 duration: timedelta | None = None, 

417 repeat: int | None = None, 

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

419 links: LINKS_TYPE_SETTER = None, 

420 related_to: RELATED_TO_TYPE_SETTER = None, 

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

422 concepts: CONCEPTS_TYPE_SETTER = None, 

423 ) -> Alarm: 

424 """Create a new DISPLAY alarm that shows a text reminder. 

425 

426 A DISPLAY alarm pops up a text notification at the trigger time. 

427 This is the most common alarm type used by calendar clients. 

428 

429 Conforms to :rfc:`5545#section-3.6.6`. 

430 

431 Parameters: 

432 description: Required. The text to display when the alarm fires. 

433 Corresponds to the :attr:`description` property. 

434 trigger: Required. When the alarm fires, as a :class:`~datetime.timedelta` 

435 relative to the event start (negative means before) or as an 

436 absolute :class:`~datetime.datetime` (recommend UTC-aware). 

437 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the alarm. 

438 duration: Gap between repeated triggers. Must be paired with 

439 ``repeat``. Corresponds to the :attr:`DURATION` property. 

440 links: The :attr:`~icalendar.cal.component.Component.links` of the alarm. 

441 refids: The :attr:`~icalendar.cal.component.Component.refids` of the alarm. 

442 related_to: The :attr:`~icalendar.cal.component.Component.related_to` of the alarm. 

443 repeat: Number of *additional* times to fire after the initial 

444 trigger. Must be paired with ``duration``. 

445 Corresponds to the :attr:`REPEAT` property. 

446 uid: Unique identifier for the alarm or ``None``. 

447 

448 Returns: 

449 :class:`Alarm` with ``ACTION:DISPLAY`` set. 

450 

451 Raises: 

452 ~icalendar.error.InvalidCalendar: If required fields are missing 

453 or ``duration`` and ``repeat`` are not both provided together. 

454 

455 Example: 

456 Create a display alarm that fires 15 minutes before the event: 

457 

458 .. code-block:: pycon 

459 

460 >>> from datetime import timedelta 

461 >>> from icalendar import Alarm 

462 >>> alarm = Alarm.new_display( 

463 ... description="Team meeting in 15 minutes", 

464 ... trigger=timedelta(minutes=-15), 

465 ... ) 

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

467 BEGIN:VALARM 

468 ACTION:DISPLAY 

469 DESCRIPTION:Team meeting in 15 minutes 

470 TRIGGER:-PT15M 

471 END:VALARM 

472 

473 Attach the alarm to an event: 

474 

475 .. code-block:: python 

476 

477 from datetime import datetime, timedelta, timezone 

478 from icalendar import Alarm, Event 

479 

480 event = Event.new( 

481 summary="Team meeting", 

482 start=datetime(2025, 6, 1, 10, 0, tzinfo=timezone.utc), 

483 end=datetime(2025, 6, 1, 11, 0, tzinfo=timezone.utc), 

484 ) 

485 event.add_component(Alarm.new_display( 

486 description="Team meeting in 15 minutes", 

487 trigger=timedelta(minutes=-15), 

488 )) 

489 """ 

490 if not description: 

491 raise InvalidCalendar("DISPLAY alarm requires a description") 

492 if trigger is None: 

493 raise InvalidCalendar("DISPLAY alarm requires a trigger") 

494 alarm: Alarm = cls.new( 

495 description=description, 

496 uid=uid, 

497 links=links, 

498 related_to=related_to, 

499 refids=refids, 

500 concepts=concepts, 

501 ) 

502 alarm.add("ACTION", "DISPLAY") 

503 alarm.TRIGGER = trigger 

504 alarm._apply_duration_repeat(duration, repeat) 

505 return alarm 

506 

507 @classmethod 

508 def new_audio( 

509 cls, 

510 trigger: timedelta | datetime, 

511 attach: str | bytes | None = None, 

512 duration: timedelta | None = None, 

513 repeat: int | None = None, 

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

515 links: LINKS_TYPE_SETTER = None, 

516 related_to: RELATED_TO_TYPE_SETTER = None, 

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

518 concepts: CONCEPTS_TYPE_SETTER = None, 

519 ) -> Alarm: 

520 """Create a new AUDIO alarm that plays a sound. 

521 

522 An AUDIO alarm plays a sound at the trigger time. An optional 

523 ``attach`` URI points to the audio file to play; when omitted, 

524 the client uses its default alert sound. 

525 

526 Conforms to :rfc:`5545#section-3.6.6`. 

527 

528 Parameters: 

529 trigger: Required. When the alarm fires, as a :class:`~datetime.timedelta` 

530 relative to the event start (negative means before) or as an 

531 absolute :class:`~datetime.datetime` (recommend UTC-aware). 

532 attach: Optional audio attachment. Pass a URI string such as 

533 ``"ftp://example.com/pub/sounds/bell.aud"`` for a linked 

534 sound file, or :class:`bytes` for inline binary audio data 

535 (stored as ``VALUE=BINARY``). When ``None`` the client uses 

536 its default sound. 

537 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the alarm. 

538 duration: Gap between repeated triggers. Must be paired with 

539 ``repeat``. Corresponds to the :attr:`DURATION` property. 

540 links: The :attr:`~icalendar.cal.component.Component.links` of the alarm. 

541 refids: The :attr:`~icalendar.cal.component.Component.refids` of the alarm. 

542 related_to: The :attr:`~icalendar.cal.component.Component.related_to` of the alarm. 

543 repeat: Number of *additional* times to fire after the initial 

544 trigger. Must be paired with ``duration``. 

545 Corresponds to the :attr:`REPEAT` property. 

546 uid: Unique identifier for the alarm or ``None``. 

547 

548 Returns: 

549 :class:`Alarm` with ``ACTION:AUDIO`` set. 

550 

551 Raises: 

552 ~icalendar.error.InvalidCalendar: If required fields are missing 

553 or ``duration`` and ``repeat`` are not both provided together. 

554 

555 Example: 

556 Create an audio alarm using a custom sound file: 

557 

558 .. code-block:: pycon 

559 

560 >>> from datetime import timedelta 

561 >>> from icalendar import Alarm 

562 >>> alarm = Alarm.new_audio( 

563 ... trigger=timedelta(minutes=-5), 

564 ... attach="ftp://example.com/pub/sounds/bell-01.aud", 

565 ... ) 

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

567 BEGIN:VALARM 

568 ACTION:AUDIO 

569 ATTACH:ftp://example.com/pub/sounds/bell-01.aud 

570 TRIGGER:-PT5M 

571 END:VALARM 

572 """ 

573 if trigger is None: 

574 raise InvalidCalendar("AUDIO alarm requires a trigger") 

575 alarm: Alarm = cls.new( 

576 uid=uid, 

577 links=links, 

578 related_to=related_to, 

579 refids=refids, 

580 concepts=concepts, 

581 ) 

582 alarm.add("ACTION", "AUDIO") 

583 alarm.TRIGGER = trigger 

584 if attach: 

585 alarm.add( 

586 "ATTACH", vBinary(attach) if isinstance(attach, bytes) else attach 

587 ) 

588 alarm._apply_duration_repeat(duration, repeat) 

589 return alarm 

590 

591 @classmethod 

592 def new_email( 

593 cls, 

594 summary: str, 

595 description: str, 

596 trigger: timedelta | datetime, 

597 attendees: Sequence[vCalAddress] | vCalAddress, 

598 attachments: Sequence[str] | str | None = None, 

599 duration: timedelta | None = None, 

600 repeat: int | None = None, 

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

602 links: LINKS_TYPE_SETTER = None, 

603 related_to: RELATED_TO_TYPE_SETTER = None, 

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

605 concepts: CONCEPTS_TYPE_SETTER = None, 

606 ) -> Alarm: 

607 """Create a new EMAIL alarm that sends an email notification. 

608 

609 An EMAIL alarm sends an email to each address in ``attendees`` when 

610 the alarm fires. 

611 

612 Conforms to :rfc:`5545#section-3.6.6`. 

613 

614 Parameters: 

615 attendees: Required. One or more recipient addresses as 

616 :class:`~icalendar.prop.cal_address.vCalAddress` instances. A 

617 single address or a sequence of addresses. At least one is 

618 required. 

619 description: Required. Body of the email. 

620 Corresponds to the :attr:`description` property. 

621 summary: Required. Subject line of the email. 

622 Corresponds to the :attr:`summary` property. 

623 trigger: Required. When the alarm fires, as a :class:`~datetime.timedelta` 

624 relative to the event start (negative means before) or as an 

625 absolute :class:`~datetime.datetime` (recommend UTC-aware). 

626 attachments: Optional URI or sequence of URIs to attach to the 

627 email. 

628 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the alarm. 

629 duration: Gap between repeated triggers. Must be paired with 

630 ``repeat``. Corresponds to the :attr:`DURATION` property. 

631 links: The :attr:`~icalendar.cal.component.Component.links` of the alarm. 

632 refids: The :attr:`~icalendar.cal.component.Component.refids` of the alarm. 

633 related_to: The :attr:`~icalendar.cal.component.Component.related_to` of the alarm. 

634 repeat: Number of *additional* times to fire after the initial 

635 trigger. Must be paired with ``duration``. 

636 Corresponds to the :attr:`REPEAT` property. 

637 uid: Unique identifier for the alarm or ``None``. 

638 

639 Returns: 

640 :class:`Alarm` with ``ACTION:EMAIL`` set. 

641 

642 Raises: 

643 ~icalendar.error.InvalidCalendar: If required fields are missing, 

644 ``attendees`` is empty, or ``duration`` and ``repeat`` are not 

645 both provided together. 

646 

647 Example: 

648 Create an email alarm sent to two recipients: 

649 

650 .. code-block:: pycon 

651 

652 >>> from datetime import timedelta 

653 >>> from icalendar import Alarm, vCalAddress 

654 >>> alarm = Alarm.new_email( 

655 ... summary="Meeting reminder", 

656 ... description="Your meeting starts in 30 minutes.", 

657 ... trigger=timedelta(minutes=-30), 

658 ... attendees=[vCalAddress("mailto:user@example.com")], 

659 ... ) 

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

661 BEGIN:VALARM 

662 ACTION:EMAIL 

663 ATTENDEE:mailto:user@example.com 

664 DESCRIPTION:Your meeting starts in 30 minutes. 

665 SUMMARY:Meeting reminder 

666 TRIGGER:-PT30M 

667 END:VALARM 

668 """ 

669 if isinstance(attendees, str): 

670 attendees = [attendees] 

671 if isinstance(attachments, str): 

672 attachments = [attachments] 

673 if not summary: 

674 raise InvalidCalendar("EMAIL alarm requires a summary") 

675 if not description: 

676 raise InvalidCalendar("EMAIL alarm requires a description") 

677 if trigger is None: 

678 raise InvalidCalendar("EMAIL alarm requires a trigger") 

679 if not attendees: 

680 raise InvalidCalendar("EMAIL alarm requires at least one attendee") 

681 alarm: Alarm = cls.new( 

682 summary=summary, 

683 description=description, 

684 uid=uid, 

685 attendees=attendees, 

686 links=links, 

687 related_to=related_to, 

688 refids=refids, 

689 concepts=concepts, 

690 ) 

691 alarm.add("ACTION", "EMAIL") 

692 alarm.TRIGGER = trigger 

693 if attachments: 

694 for attachment in attachments: 

695 alarm.add("ATTACH", attachment) 

696 alarm._apply_duration_repeat(duration, repeat) 

697 return alarm 

698 

699 @classmethod 

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

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

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

703 

704 

705__all__ = ["Alarm"]