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

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

134 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 ATTACHMENTS_TYPE_SETTER, 

10 ATTENDEE_TYPE_SETTER, 

11 CONCEPTS_TYPE_SETTER, 

12 LINKS_TYPE_SETTER, 

13 RELATED_TO_TYPE_SETTER, 

14 _set_attachments, 

15 attachments_property, 

16 attendees_property, 

17 create_single_property, 

18 description_property, 

19 property_del_duration, 

20 property_get_duration, 

21 property_set_duration, 

22 repeat_property, 

23 single_int_property, 

24 single_string_property, 

25 single_utc_property, 

26 summary_property, 

27 uid_property, 

28) 

29from icalendar.cal.component import Component 

30from icalendar.cal.examples import get_example 

31from icalendar.error import InvalidCalendar 

32 

33if TYPE_CHECKING: 

34 import uuid 

35 

36 from icalendar.compatibility import Self 

37 from icalendar.prop import vBinary, vUri 

38 

39 

40class Alarm(Component): 

41 """ 

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

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

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

45 pending event or an overdue to-do. 

46 

47 Example: 

48 

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

50 from an FTP server. 

51 

52 .. code-block:: pycon 

53 

54 >>> from icalendar import Alarm 

55 >>> alarm = Alarm.example() 

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

57 BEGIN:VALARM 

58 ACTION:AUDIO 

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

60 DURATION:PT15M 

61 REPEAT:4 

62 TRIGGER;VALUE=DATE-TIME:19970317T133000Z 

63 END:VALARM 

64 """ 

65 

66 name = "VALARM" 

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

68 required = ( 

69 "ACTION", 

70 "TRIGGER", 

71 ) 

72 singletons = ( 

73 "ACTION", 

74 "DESCRIPTION", 

75 "SUMMARY", 

76 "TRIGGER", 

77 "DURATION", 

78 "REPEAT", 

79 "UID", 

80 "PROXIMITY", 

81 "ACKNOWLEDGED", 

82 ) 

83 inclusive = ( 

84 ( 

85 "DURATION", 

86 "REPEAT", 

87 ), 

88 ( 

89 "SUMMARY", 

90 "ATTENDEE", 

91 ), 

92 ) 

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

94 

95 REPEAT = single_int_property( 

96 "REPEAT", 

97 0, 

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

99 

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

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

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

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

104 (the initial trigger plus two repeats). 

105 

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

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

108 paired with :attr:`DURATION`. 

109 

110 Example: 

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

112 five-minute intervals. 

113 

114 .. code-block:: pycon 

115 

116 >>> from datetime import timedelta 

117 >>> from icalendar import Alarm 

118 >>> alarm = Alarm() 

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

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

121 >>> alarm.REPEAT = 2 

122 >>> alarm.REPEAT 

123 2 

124 

125 Raises: 

126 TypeError: If the value is not an ``int``. Booleans are rejected, too, 

127 even though ``bool`` subclasses ``int``. 

128 

129 ~icalendar.error.InvalidCalendar: If the value is negative. 

130 

131 .. versionchanged:: 7.3.0 

132 Negative values are no longer accepted. 

133 """, 

134 min_value=0, 

135 ) 

136 

137 DURATION = property( 

138 property_get_duration, 

139 property_set_duration, 

140 property_del_duration, 

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

142 

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

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

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

146 from the component. 

147 

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

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

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

151 the initial trigger. 

152 

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

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

155 

156 Example: 

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

158 triggers spaced ten minutes apart. 

159 

160 .. code-block:: pycon 

161 

162 >>> from datetime import timedelta 

163 >>> from icalendar import Alarm 

164 >>> alarm = Alarm() 

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

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

167 >>> alarm.REPEAT = 2 

168 >>> alarm.DURATION 

169 datetime.timedelta(seconds=600) 

170 """, 

171 ) 

172 

173 ACKNOWLEDGED = single_utc_property( 

174 "ACKNOWLEDGED", 

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

176 

177 Setting this property allows calendar clients to 

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

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

180 will not refire the alarm. 

181 

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

183 

184 Example: 

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

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

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

188 

189 .. code-block:: pycon 

190 

191 >>> from datetime import timezone, datetime 

192 >>> from icalendar import Alarm 

193 >>> UTC = timezone.utc 

194 >>> alarm = Alarm() 

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

196 >>> alarm.ACKNOWLEDGED 

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

198 

199 See also: 

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

201 """, 

202 ) 

203 

204 TRIGGER = create_single_property( 

205 "TRIGGER", 

206 "dt", 

207 (datetime, timedelta), 

208 timedelta | datetime | None, 

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

210 

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

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

213 

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

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

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

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

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

219 parent component's dates. 

220 

221 Examples: 

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

223 

224 .. code-block:: pycon 

225 

226 >>> from datetime import datetime, timedelta, timezone 

227 >>> from icalendar import Alarm, Event 

228 >>> UTC = timezone.utc 

229 >>> event = Event() 

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

231 >>> alarm = Alarm() 

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

233 >>> event.add_component(alarm) 

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

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

236 

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

238 

239 .. code-block:: pycon 

240 

241 >>> absolute_alarm = Alarm() 

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

243 >>> absolute_alarm.TRIGGER 

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

245 

246 See also: 

247 :attr:`TRIGGER_RELATED`, :attr:`DURATION`, :attr:`REPEAT` 

248 """, 

249 ) 

250 

251 @property 

252 def TRIGGER_RELATED(self) -> str: 

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

254 

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

256 

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

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

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

260 

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

262 end of its parent component. 

263 

264 >>> from icalendar import Alarm 

265 >>> from datetime import timedelta 

266 >>> alarm = Alarm() 

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

268 >>> alarm.TRIGGER_RELATED = "END" 

269 """ 

270 trigger = self.get("TRIGGER") 

271 if trigger is None: 

272 return "START" 

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

274 

275 @TRIGGER_RELATED.setter 

276 def TRIGGER_RELATED(self, value: str): 

277 """Set "START" or "END".""" 

278 trigger = self.get("TRIGGER") 

279 if trigger is None: 

280 raise ValueError( 

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

282 ) 

283 trigger.params["RELATED"] = value 

284 

285 class Triggers(NamedTuple): 

286 """The computed times of alarm triggers. 

287 

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

289 

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

291 

292 absolute - triggers at a datetime in UTC 

293 """ 

294 

295 start: tuple[timedelta] 

296 end: tuple[timedelta] 

297 absolute: tuple[datetime] 

298 

299 @property 

300 def triggers(self): 

301 """The computed triggers of an Alarm. 

302 

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

304 

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

306 parent component. 

307 

308 >>> from icalendar import Alarm 

309 >>> from datetime import timedelta 

310 >>> alarm = Alarm() 

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

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

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

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

315 True 

316 >>> alarm.triggers.end 

317 () 

318 >>> alarm.triggers.absolute 

319 () 

320 """ 

321 start = [] 

322 end = [] 

323 absolute = [] 

324 trigger = self.TRIGGER 

325 if trigger is not None: 

326 if isinstance(trigger, date): 

327 absolute.append(trigger) 

328 add = absolute 

329 elif self.TRIGGER_RELATED == "START": 

330 start.append(trigger) 

331 add = start 

332 else: 

333 end.append(trigger) 

334 add = end 

335 duration = self.DURATION 

336 if duration is not None: 

337 for _ in range(self.repeat): 

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

339 return self.Triggers( 

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

341 ) 

342 

343 repeat = repeat_property 

344 

345 attachments = attachments_property 

346 

347 @attachments.setter 

348 def attachments(self, value: ATTACHMENTS_TYPE_SETTER) -> None: 

349 if value is not None and self.ACTION == "AUDIO": 

350 count = len(value) if isinstance(value, list) else 1 

351 if count > 1: 

352 raise InvalidCalendar( 

353 "An AUDIO alarm must not contain more than one attachment.\n" 

354 f"Alarm has {count} attachments." 

355 ) 

356 _set_attachments(self, value) 

357 

358 ACTION = single_string_property( 

359 "ACTION", 

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

361 

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

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

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

365 """, 

366 ) 

367 

368 @ACTION.setter 

369 def ACTION(self, value: str | None) -> None: 

370 if value == "AUDIO" and len(self.attachments) > 1: 

371 raise InvalidCalendar( 

372 "An AUDIO alarm must not contain more than one attachment.\n" 

373 f"Alarm has {len(self.attachments)} attachments." 

374 ) 

375 self.pop("ACTION", None) 

376 if value is not None: 

377 self.add("ACTION", value) 

378 

379 uid = single_string_property( 

380 "UID", 

381 uid_property.__doc__, 

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

383 ) 

384 summary = summary_property 

385 description = description_property 

386 attendees = attendees_property 

387 

388 @classmethod 

389 def new( 

390 cls, 

391 /, 

392 action: str | None = None, 

393 attachments: ATTACHMENTS_TYPE_SETTER = None, 

394 attendees: ATTENDEE_TYPE_SETTER = None, 

395 concepts: CONCEPTS_TYPE_SETTER = None, 

396 description: str | None = None, 

397 links: LINKS_TYPE_SETTER = None, 

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

399 related_to: RELATED_TO_TYPE_SETTER = None, 

400 summary: str | None = None, 

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

402 ) -> Self: 

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

404 

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

406 

407 Parameters: 

408 action: The :attr:`ACTION` of the alarm. Typical values are 

409 ``"AUDIO"``, ``"DISPLAY"``, and ``"EMAIL"``. When you set 

410 ``"AUDIO"``, the alarm accepts at most one attachment. 

411 attachments: The :attr:`attachments` of the alarm. 

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

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

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

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

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

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

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

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

420 

421 Returns: 

422 :class:`Alarm` 

423 

424 Raises: 

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

426 according to :rfc:`5545`. 

427 

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

429 """ 

430 alarm: Self = super().new( 

431 links=links, 

432 related_to=related_to, 

433 refids=refids, 

434 concepts=concepts, 

435 ) 

436 if action is not None: 

437 alarm.ACTION = action 

438 alarm.attachments = attachments 

439 alarm.summary = summary 

440 alarm.description = description 

441 alarm.uid = uid 

442 alarm.attendees = attendees 

443 return alarm 

444 

445 def _apply_duration_repeat( 

446 self, 

447 duration: timedelta | None, 

448 repeat: int | None, 

449 ) -> None: 

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

451 if duration is None or repeat is None: 

452 raise InvalidCalendar( 

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

454 ) 

455 self.DURATION = duration 

456 self.repeat = repeat 

457 

458 @classmethod 

459 def new_display( 

460 cls, 

461 description: str, 

462 trigger: timedelta | datetime, 

463 duration: timedelta | None = None, 

464 repeat: int | None = None, 

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

466 links: LINKS_TYPE_SETTER = None, 

467 related_to: RELATED_TO_TYPE_SETTER = None, 

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

469 concepts: CONCEPTS_TYPE_SETTER = None, 

470 ) -> Alarm: 

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

472 

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

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

475 

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

477 

478 Parameters: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

494 

495 Returns: 

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

497 

498 Raises: 

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

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

501 

502 Example: 

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

504 

505 .. code-block:: pycon 

506 

507 >>> from datetime import timedelta 

508 >>> from icalendar import Alarm 

509 >>> alarm = Alarm.new_display( 

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

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

512 ... ) 

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

514 BEGIN:VALARM 

515 ACTION:DISPLAY 

516 DESCRIPTION:Team meeting in 15 minutes 

517 TRIGGER:-PT15M 

518 END:VALARM 

519 

520 Attach the alarm to an event: 

521 

522 .. code-block:: python 

523 

524 from datetime import datetime, timedelta, timezone 

525 from icalendar import Alarm, Event 

526 

527 event = Event.new( 

528 summary="Team meeting", 

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

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

531 ) 

532 event.add_component(Alarm.new_display( 

533 description="Team meeting in 15 minutes", 

534 trigger=timedelta(minutes=-15), 

535 )) 

536 """ 

537 if not description: 

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

539 if trigger is None: 

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

541 alarm: Alarm = cls.new( 

542 action="DISPLAY", 

543 description=description, 

544 uid=uid, 

545 links=links, 

546 related_to=related_to, 

547 refids=refids, 

548 concepts=concepts, 

549 ) 

550 alarm.TRIGGER = trigger 

551 alarm._apply_duration_repeat(duration, repeat) 

552 return alarm 

553 

554 @classmethod 

555 def new_audio( 

556 cls, 

557 trigger: timedelta | datetime, 

558 attachments: str | bytes | vUri | vBinary | None = None, 

559 duration: timedelta | None = None, 

560 repeat: int | None = None, 

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

562 links: LINKS_TYPE_SETTER = None, 

563 related_to: RELATED_TO_TYPE_SETTER = None, 

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

565 concepts: CONCEPTS_TYPE_SETTER = None, 

566 ) -> Alarm: 

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

568 

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

570 ``attachments`` URI points to the audio file to play; when omitted, 

571 the client uses its default alert sound. 

572 

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

574 

575 Parameters: 

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

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

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

579 attachments: Optional audio attachment. Accepts a URI as a 

580 :class:`str` or :class:`~icalendar.prop.uri.vUri`, or 

581 inline binary audio as :class:`bytes` or 

582 :class:`~icalendar.prop.binary.vBinary`. When ``None``, 

583 the client uses its default sound. 

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

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

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

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

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

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

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

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

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

593 

594 Returns: 

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

596 

597 Raises: 

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

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

600 

601 Example: 

602 Create an audio alarm using a custom sound file: 

603 

604 .. code-block:: pycon 

605 

606 >>> from datetime import timedelta 

607 >>> from icalendar import Alarm 

608 >>> alarm = Alarm.new_audio( 

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

610 ... attachments="ftp://example.com/pub/sounds/bell-01.aud", 

611 ... ) 

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

613 BEGIN:VALARM 

614 ACTION:AUDIO 

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

616 TRIGGER:-PT5M 

617 END:VALARM 

618 """ 

619 if trigger is None: 

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

621 alarm: Alarm = cls.new( 

622 action="AUDIO", 

623 attachments=attachments, 

624 uid=uid, 

625 links=links, 

626 related_to=related_to, 

627 refids=refids, 

628 concepts=concepts, 

629 ) 

630 alarm.TRIGGER = trigger 

631 alarm._apply_duration_repeat(duration, repeat) 

632 return alarm 

633 

634 @classmethod 

635 def new_email( 

636 cls, 

637 summary: str, 

638 description: str, 

639 trigger: timedelta | datetime, 

640 attendees: ATTENDEE_TYPE_SETTER, 

641 attachments: ATTACHMENTS_TYPE_SETTER = None, 

642 duration: timedelta | None = None, 

643 repeat: int | None = None, 

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

645 links: LINKS_TYPE_SETTER = None, 

646 related_to: RELATED_TO_TYPE_SETTER = None, 

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

648 concepts: CONCEPTS_TYPE_SETTER = None, 

649 ) -> Alarm: 

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

651 

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

653 the alarm fires. 

654 

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

656 

657 Parameters: 

658 attendees: Required. One or more recipient addresses as email strings or 

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

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

661 required. 

662 description: Required. Body of the email. 

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

664 summary: Required. Subject line of the email. 

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

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

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

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

669 attachments: The :attr:`attachments` of the alarm. A single value 

670 or a sequence of them. Both URIs and binary data are accepted. 

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

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

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

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

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

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

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

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

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

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

681 

682 Returns: 

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

684 

685 Raises: 

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

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

688 both provided together. 

689 

690 Example: 

691 Create an email alarm sent to two recipients. Plain email strings 

692 and ``mailto:``-prefixed strings are both accepted and normalized 

693 to :class:`~icalendar.prop.cal_address.vCalAddress`: 

694 

695 .. code-block:: pycon 

696 

697 >>> from datetime import timedelta 

698 >>> from icalendar import Alarm 

699 >>> alarm = Alarm.new_email( 

700 ... summary="Meeting reminder", 

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

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

703 ... attendees=["user@example.com", "mailto:boss@example.com"], 

704 ... ) 

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

706 BEGIN:VALARM 

707 ACTION:EMAIL 

708 ATTENDEE:mailto:user@example.com 

709 ATTENDEE:mailto:boss@example.com 

710 DESCRIPTION:Your meeting starts in 30 minutes. 

711 SUMMARY:Meeting reminder 

712 TRIGGER:-PT30M 

713 END:VALARM 

714 """ 

715 if isinstance(attendees, str): 

716 attendees = [attendees] 

717 if not summary: 

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

719 if not description: 

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

721 if trigger is None: 

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

723 if not attendees: 

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

725 alarm: Alarm = cls.new( 

726 action="EMAIL", 

727 attachments=attachments, 

728 summary=summary, 

729 description=description, 

730 uid=uid, 

731 attendees=attendees, 

732 links=links, 

733 related_to=related_to, 

734 refids=refids, 

735 concepts=concepts, 

736 ) 

737 alarm.TRIGGER = trigger 

738 alarm._apply_duration_repeat(duration, repeat) 

739 return alarm 

740 

741 @classmethod 

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

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

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

745 

746 

747__all__ = ["Alarm"]