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
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""":rfc:`5545` VALARM component."""
3from __future__ import annotations
5from datetime import date, datetime, timedelta
6from typing import TYPE_CHECKING, NamedTuple
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
33if TYPE_CHECKING:
34 import uuid
36 from icalendar.compatibility import Self
37 from icalendar.prop import vBinary, vUri
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.
47 Example:
49 The following example creates an alarm which uses an audio file
50 from an FTP server.
52 .. code-block:: pycon
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 """
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")
95 REPEAT = single_int_property(
96 "REPEAT",
97 0,
98 """The number of additional times the alarm is triggered after the initial trigger.
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).
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`.
110 Example:
111 Build an alarm that fires once and then repeats twice at
112 five-minute intervals.
114 .. code-block:: pycon
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
125 Raises:
126 TypeError: If the value is not an ``int``. Booleans are rejected, too,
127 even though ``bool`` subclasses ``int``.
129 ~icalendar.error.InvalidCalendar: If the value is negative.
131 .. versionchanged:: 7.3.0
132 Negative values are no longer accepted.
133 """,
134 min_value=0,
135 )
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.
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.
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.
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.
156 Example:
157 Pair :attr:`DURATION` with :attr:`REPEAT` to produce three
158 triggers spaced ten minutes apart.
160 .. code-block:: pycon
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 )
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`.
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.
182 Returns ``None`` when no acknowledgment has been recorded.
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)``.
189 .. code-block:: pycon
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'))
199 See also:
200 :attr:`TRIGGER`, the time at which the alarm fires.
201 """,
202 )
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`.
211 The value is either a :class:`~datetime.timedelta` (relative trigger) or a
212 UTC :class:`~datetime.datetime` (absolute trigger).
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.
221 Examples:
222 Set an alarm to fire 15 minutes before the start of an event.
224 .. code-block:: pycon
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)
237 Set an absolute trigger to fire at a specific UTC time.
239 .. code-block:: pycon
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)
246 See also:
247 :attr:`TRIGGER_RELATED`, :attr:`DURATION`, :attr:`REPEAT`
248 """,
249 )
251 @property
252 def TRIGGER_RELATED(self) -> str:
253 """The RELATED parameter of the TRIGGER property.
255 Values are either "START" (default) or "END".
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.
261 In this example, we create an alarm that triggers two hours after the
262 end of its parent component.
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")
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
285 class Triggers(NamedTuple):
286 """The computed times of alarm triggers.
288 start - triggers relative to the start of the Event or Todo (timedelta)
290 end - triggers relative to the end of the Event or Todo (timedelta)
292 absolute - triggers at a datetime in UTC
293 """
295 start: tuple[timedelta]
296 end: tuple[timedelta]
297 absolute: tuple[datetime]
299 @property
300 def triggers(self):
301 """The computed triggers of an Alarm.
303 This takes the TRIGGER, DURATION and REPEAT properties into account.
305 Here, we create an alarm that triggers 3 times before the start of the
306 parent component.
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 )
343 repeat = repeat_property
345 attachments = attachments_property
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)
358 ACTION = single_string_property(
359 "ACTION",
360 """The action invoked when the alarm triggers.
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 )
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)
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
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.
405 This creates a new Alarm in accordance with :rfc:`5545`.
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.
421 Returns:
422 :class:`Alarm`
424 Raises:
425 ~error.InvalidCalendar: If the content is not valid
426 according to :rfc:`5545`.
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
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
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.
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.
476 Conforms to :rfc:`5545#section-3.6.6`.
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``.
495 Returns:
496 :class:`Alarm` with ``ACTION:DISPLAY`` set.
498 Raises:
499 ~icalendar.error.InvalidCalendar: If required fields are missing
500 or ``duration`` and ``repeat`` are not both provided together.
502 Example:
503 Create a display alarm that fires 15 minutes before the event:
505 .. code-block:: pycon
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
520 Attach the alarm to an event:
522 .. code-block:: python
524 from datetime import datetime, timedelta, timezone
525 from icalendar import Alarm, Event
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
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.
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.
573 Conforms to :rfc:`5545#section-3.6.6`.
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``.
594 Returns:
595 :class:`Alarm` with ``ACTION:AUDIO`` set.
597 Raises:
598 ~icalendar.error.InvalidCalendar: If required fields are missing
599 or ``duration`` and ``repeat`` are not both provided together.
601 Example:
602 Create an audio alarm using a custom sound file:
604 .. code-block:: pycon
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
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.
652 An EMAIL alarm sends an email to each address in ``attendees`` when
653 the alarm fires.
655 Conforms to :rfc:`5545#section-3.6.6`.
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``.
682 Returns:
683 :class:`Alarm` with ``ACTION:EMAIL`` set.
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.
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`:
695 .. code-block:: pycon
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
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))
747__all__ = ["Alarm"]