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
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
32from icalendar.prop import vCalAddress
34if TYPE_CHECKING:
35 import uuid
37 from icalendar.compatibility import Self
38 from icalendar.prop import vBinary, vUri
41class Alarm(Component):
42 """
43 A "VALARM" calendar component is a grouping of component
44 properties that defines an alarm or reminder for an event or a
45 to-do. For example, it may be used to define a reminder for a
46 pending event or an overdue to-do.
48 Example:
50 The following example creates an alarm which uses an audio file
51 from an FTP server.
53 .. code-block:: pycon
55 >>> from icalendar import Alarm
56 >>> alarm = Alarm.example()
57 >>> print(alarm.to_ical().decode())
58 BEGIN:VALARM
59 ACTION:AUDIO
60 ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/sounds/bell-01.aud
61 DURATION:PT15M
62 REPEAT:4
63 TRIGGER;VALUE=DATE-TIME:19970317T133000Z
64 END:VALARM
65 """
67 name = "VALARM"
68 # some properties MAY/MUST/MUST NOT appear depending on ACTION value
69 required = (
70 "ACTION",
71 "TRIGGER",
72 )
73 singletons = (
74 "ACTION",
75 "DESCRIPTION",
76 "SUMMARY",
77 "TRIGGER",
78 "DURATION",
79 "REPEAT",
80 "UID",
81 "PROXIMITY",
82 "ACKNOWLEDGED",
83 )
84 inclusive = (
85 (
86 "DURATION",
87 "REPEAT",
88 ),
89 (
90 "SUMMARY",
91 "ATTENDEE",
92 ),
93 )
94 multiple = ("ATTENDEE", "ATTACH", "RELATED-TO")
96 REPEAT = single_int_property(
97 "REPEAT",
98 0,
99 """The number of additional times the alarm is triggered after the initial trigger.
101 Defaults to ``0``, meaning the alarm fires once. To repeat the alarm,
102 set both :attr:`REPEAT` and :attr:`DURATION`. The :attr:`DURATION`
103 sets the gap between repetitions. :attr:`REPEAT` is the count of *additional*
104 triggers, so a :attr:`REPEAT` of ``2`` produces three alarms in total
105 (the initial trigger plus two repeats).
107 Conforming with :rfc:`5545#section-3.8.6.2`, this property can appear
108 once in an :class:`~icalendar.cal.alarm.Alarm` component and must be
109 paired with :attr:`DURATION`.
111 Example:
112 Build an alarm that fires once and then repeats twice at
113 five-minute intervals.
115 .. code-block:: pycon
117 >>> from datetime import timedelta
118 >>> from icalendar import Alarm
119 >>> alarm = Alarm()
120 >>> alarm.TRIGGER = timedelta(minutes=-15)
121 >>> alarm.DURATION = timedelta(minutes=5)
122 >>> alarm.REPEAT = 2
123 >>> alarm.REPEAT
124 2
126 Raises:
127 TypeError: If the value is not an ``int``. Booleans are rejected, too,
128 even though ``bool`` subclasses ``int``.
130 ~icalendar.error.InvalidCalendar: If the value is negative.
132 .. versionchanged:: 7.3.0
133 Negative values are no longer accepted.
134 """,
135 min_value=0,
136 )
138 DURATION = property(
139 property_get_duration,
140 property_set_duration,
141 property_del_duration,
142 """The delay between repeated triggers of a repeating alarm.
144 Returns a :class:`datetime.timedelta` or ``None`` when the alarm
145 has no :attr:`DURATION` set. Setting this attribute accepts a
146 :class:`~datetime.timedelta`; deleting it removes the property
147 from the component.
149 :attr:`DURATION` is meaningful only for repeating alarms and must
150 be paired with :attr:`REPEAT`. The two together produce
151 :attr:`REPEAT` additional triggers, each spaced by :attr:`DURATION` after
152 the initial trigger.
154 Conforming with :rfc:`5545#section-3.8.2.5`, the :attr:`DURATION` property
155 can appear once in an :class:`~icalendar.cal.alarm.Alarm` component.
157 Example:
158 Pair :attr:`DURATION` with :attr:`REPEAT` to produce three
159 triggers spaced ten minutes apart.
161 .. code-block:: pycon
163 >>> from datetime import timedelta
164 >>> from icalendar import Alarm
165 >>> alarm = Alarm()
166 >>> alarm.TRIGGER = timedelta(minutes=-30)
167 >>> alarm.DURATION = timedelta(minutes=10)
168 >>> alarm.REPEAT = 2
169 >>> alarm.DURATION
170 datetime.timedelta(seconds=600)
171 """,
172 )
174 ACKNOWLEDGED = single_utc_property(
175 "ACKNOWLEDGED",
176 """This property is the UTC datetime at which this alarm was last sent or acknowledged as defined in :rfc:`9074`.
178 Setting this property allows calendar clients to
179 dismiss or suppress an alarm across multiple devices. Once set to a value
180 greater than or equal to the alarm's computed trigger time, conforming clients
181 will not refire the alarm.
183 Returns ``None`` when no acknowledgment has been recorded.
185 Example:
186 Mark an alarm as acknowledged. Note that the example uses an arbitrary time
187 for the purpose of passing doctests. In actual practice, clients should
188 use the current time in UTC, such as ``datetime.now(UTC)``.
190 .. code-block:: pycon
192 >>> from datetime import timezone, datetime
193 >>> from icalendar import Alarm
194 >>> UTC = timezone.utc
195 >>> alarm = Alarm()
196 >>> alarm.ACKNOWLEDGED = datetime(2024, 1, 15, 10, 0, tzinfo=UTC)
197 >>> alarm.ACKNOWLEDGED
198 datetime.datetime(2024, 1, 15, 10, 0, tzinfo=ZoneInfo(key='UTC'))
200 See also:
201 :attr:`TRIGGER`, the time at which the alarm fires.
202 """,
203 )
205 TRIGGER = create_single_property(
206 "TRIGGER",
207 "dt",
208 (datetime, timedelta),
209 timedelta | datetime | None,
210 """The time at which this alarm fires, per :rfc:`5545#section-3.8.6.3`.
212 The value is either a :class:`~datetime.timedelta` (relative trigger) or a
213 UTC :class:`~datetime.datetime` (absolute trigger).
215 A negative :class:`~datetime.timedelta` fires *before* the related
216 component boundary (start or end); a positive one fires *after* it.
217 Use :attr:`TRIGGER_RELATED` to choose whether the offset is measured from
218 the start or the end of the parent event or to-do.
219 An absolute trigger fires at an exact UTC point in time regardless of the
220 parent component's dates.
222 Examples:
223 Set an alarm to fire 15 minutes before the start of an event.
225 .. code-block:: pycon
227 >>> from datetime import datetime, timedelta, timezone
228 >>> from icalendar import Alarm, Event
229 >>> UTC = timezone.utc
230 >>> event = Event()
231 >>> event.start = datetime(2024, 1, 15, 10, 0, tzinfo=UTC)
232 >>> alarm = Alarm()
233 >>> alarm.TRIGGER = timedelta(minutes=-15)
234 >>> event.add_component(alarm)
235 >>> event.alarms.times[0].trigger
236 datetime.datetime(2024, 1, 15, 9, 45, tzinfo=datetime.timezone.utc)
238 Set an absolute trigger to fire at a specific UTC time.
240 .. code-block:: pycon
242 >>> absolute_alarm = Alarm()
243 >>> absolute_alarm.TRIGGER = datetime(2024, 1, 15, 9, 45, tzinfo=UTC)
244 >>> absolute_alarm.TRIGGER
245 datetime.datetime(2024, 1, 15, 9, 45, tzinfo=datetime.timezone.utc)
247 See also:
248 :attr:`TRIGGER_RELATED`, :attr:`DURATION`, :attr:`REPEAT`
249 """,
250 )
252 @property
253 def TRIGGER_RELATED(self) -> str:
254 """The RELATED parameter of the TRIGGER property.
256 Values are either "START" (default) or "END".
258 A value of START will set the alarm to trigger off the
259 start of the associated event or to-do. A value of END will set
260 the alarm to trigger off the end of the associated event or to-do.
262 In this example, we create an alarm that triggers two hours after the
263 end of its parent component.
265 >>> from icalendar import Alarm
266 >>> from datetime import timedelta
267 >>> alarm = Alarm()
268 >>> alarm.TRIGGER = timedelta(hours=2)
269 >>> alarm.TRIGGER_RELATED = "END"
270 """
271 trigger = self.get("TRIGGER")
272 if trigger is None:
273 return "START"
274 return trigger.params.get("RELATED", "START")
276 @TRIGGER_RELATED.setter
277 def TRIGGER_RELATED(self, value: str):
278 """Set "START" or "END"."""
279 trigger = self.get("TRIGGER")
280 if trigger is None:
281 raise ValueError(
282 "You must set a TRIGGER before setting the RELATED parameter."
283 )
284 trigger.params["RELATED"] = value
286 class Triggers(NamedTuple):
287 """The computed times of alarm triggers.
289 start - triggers relative to the start of the Event or Todo (timedelta)
291 end - triggers relative to the end of the Event or Todo (timedelta)
293 absolute - triggers at a datetime in UTC
294 """
296 start: tuple[timedelta]
297 end: tuple[timedelta]
298 absolute: tuple[datetime]
300 @property
301 def triggers(self):
302 """The computed triggers of an Alarm.
304 This takes the TRIGGER, DURATION and REPEAT properties into account.
306 Here, we create an alarm that triggers 3 times before the start of the
307 parent component.
309 >>> from icalendar import Alarm
310 >>> from datetime import timedelta
311 >>> alarm = Alarm()
312 >>> alarm.TRIGGER = timedelta(hours=-4) # trigger 4 hours before START
313 >>> alarm.DURATION = timedelta(hours=1) # after 1 hour trigger again
314 >>> alarm.REPEAT = 2 # trigger 2 more times
315 >>> alarm.triggers.start == (timedelta(hours=-4), timedelta(hours=-3), timedelta(hours=-2))
316 True
317 >>> alarm.triggers.end
318 ()
319 >>> alarm.triggers.absolute
320 ()
321 """
322 start = []
323 end = []
324 absolute = []
325 trigger = self.TRIGGER
326 if trigger is not None:
327 if isinstance(trigger, date):
328 absolute.append(trigger)
329 add = absolute
330 elif self.TRIGGER_RELATED == "START":
331 start.append(trigger)
332 add = start
333 else:
334 end.append(trigger)
335 add = end
336 duration = self.DURATION
337 if duration is not None:
338 for _ in range(self.repeat):
339 add.append(add[-1] + duration)
340 return self.Triggers(
341 start=tuple(start), end=tuple(end), absolute=tuple(absolute)
342 )
344 repeat = repeat_property
346 attachments = attachments_property
348 @attachments.setter
349 def attachments(self, value: ATTACHMENTS_TYPE_SETTER) -> None:
350 if value is not None and self.ACTION == "AUDIO":
351 count = len(value) if isinstance(value, list) else 1
352 if count > 1:
353 raise InvalidCalendar(
354 "An AUDIO alarm must not contain more than one attachment.\n"
355 f"Alarm has {count} attachments."
356 )
357 _set_attachments(self, value)
359 ACTION = single_string_property(
360 "ACTION",
361 """The action invoked when the alarm triggers.
363 Typical values defined by :rfc:`5545#section-3.8.6.1` are
364 ``AUDIO``, ``DISPLAY``, and ``EMAIL``. The empty string is
365 returned when no ``ACTION`` property is present.
366 """,
367 )
369 @ACTION.setter
370 def ACTION(self, value: str | None) -> None:
371 if value == "AUDIO" and len(self.attachments) > 1:
372 raise InvalidCalendar(
373 "An AUDIO alarm must not contain more than one attachment.\n"
374 f"Alarm has {len(self.attachments)} attachments."
375 )
376 self.pop("ACTION", None)
377 if value is not None:
378 self.add("ACTION", value)
380 uid = single_string_property(
381 "UID",
382 uid_property.__doc__,
383 ["X-ALARMUID", "X-EVOLUTION-ALARM-UID"],
384 )
385 summary = summary_property
386 description = description_property
387 attendees = attendees_property
389 @classmethod
390 def new(
391 cls,
392 /,
393 action: str | None = None,
394 attachments: ATTACHMENTS_TYPE_SETTER = None,
395 attendees: ATTENDEE_TYPE_SETTER = None,
396 concepts: CONCEPTS_TYPE_SETTER = None,
397 description: str | None = None,
398 links: LINKS_TYPE_SETTER = None,
399 refids: list[str] | str | None = None,
400 related_to: RELATED_TO_TYPE_SETTER = None,
401 summary: str | None = None,
402 uid: str | uuid.UUID | None = None,
403 ) -> Self:
404 """Create a new alarm with all required properties.
406 This creates a new Alarm in accordance with :rfc:`5545`.
408 Parameters:
409 action: The :attr:`ACTION` of the alarm. Typical values are
410 ``"AUDIO"``, ``"DISPLAY"``, and ``"EMAIL"``. When you set
411 ``"AUDIO"``, the alarm accepts at most one attachment.
412 attachments: The :attr:`attachments` of the alarm.
413 attendees: The :attr:`attendees` of the alarm.
414 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the alarm.
415 description: The :attr:`description` of the alarm.
416 links: The :attr:`~icalendar.cal.component.Component.links` of the alarm.
417 refids: :attr:`~icalendar.cal.component.Component.refids` of the alarm.
418 related_to: :attr:`~icalendar.cal.component.Component.related_to` of the alarm.
419 summary: The :attr:`summary` of the alarm.
420 uid: The :attr:`uid` of the alarm.
422 Returns:
423 :class:`Alarm`
425 Raises:
426 ~error.InvalidCalendar: If the content is not valid
427 according to :rfc:`5545`.
429 .. warning:: As time progresses, we will be stricter with the validation.
430 """
431 alarm: Self = super().new(
432 links=links,
433 related_to=related_to,
434 refids=refids,
435 concepts=concepts,
436 )
437 if action is not None:
438 alarm.ACTION = action
439 alarm.attachments = attachments
440 alarm.summary = summary
441 alarm.description = description
442 alarm.uid = uid
443 alarm.attendees = attendees
444 return alarm
446 def _apply_duration_repeat(
447 self,
448 duration: timedelta | None,
449 repeat: int | None,
450 ) -> None:
451 if duration is not None or repeat is not None:
452 if duration is None or repeat is None:
453 raise InvalidCalendar(
454 "DURATION and REPEAT must be set together or not at all"
455 )
456 self.DURATION = duration
457 self.repeat = repeat
459 @classmethod
460 def new_display(
461 cls,
462 description: str,
463 trigger: timedelta | datetime,
464 duration: timedelta | None = None,
465 repeat: int | None = None,
466 uid: str | uuid.UUID | None = None,
467 links: LINKS_TYPE_SETTER = None,
468 related_to: RELATED_TO_TYPE_SETTER = None,
469 refids: list[str] | str | None = None,
470 concepts: CONCEPTS_TYPE_SETTER = None,
471 ) -> Alarm:
472 """Create a new DISPLAY alarm that shows a text reminder.
474 A DISPLAY alarm pops up a text notification at the trigger time.
475 This is the most common alarm type used by calendar clients.
477 Conforms to :rfc:`5545#section-3.6.6`.
479 Parameters:
480 description: Required. The text to display when the alarm fires.
481 Corresponds to the :attr:`description` property.
482 trigger: Required. When the alarm fires, as a :class:`~datetime.timedelta`
483 relative to the event start (negative means before) or as an
484 absolute :class:`~datetime.datetime` (recommend UTC-aware).
485 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the alarm.
486 duration: Gap between repeated triggers. Must be paired with
487 ``repeat``. Corresponds to the :attr:`DURATION` property.
488 links: The :attr:`~icalendar.cal.component.Component.links` of the alarm.
489 refids: The :attr:`~icalendar.cal.component.Component.refids` of the alarm.
490 related_to: The :attr:`~icalendar.cal.component.Component.related_to` of the alarm.
491 repeat: Number of *additional* times to fire after the initial
492 trigger. Must be paired with ``duration``.
493 Corresponds to the :attr:`REPEAT` property.
494 uid: Unique identifier for the alarm or ``None``.
496 Returns:
497 :class:`Alarm` with ``ACTION:DISPLAY`` set.
499 Raises:
500 ~icalendar.error.InvalidCalendar: If required fields are missing
501 or ``duration`` and ``repeat`` are not both provided together.
503 Example:
504 Create a display alarm that fires 15 minutes before the event:
506 .. code-block:: pycon
508 >>> from datetime import timedelta
509 >>> from icalendar import Alarm
510 >>> alarm = Alarm.new_display(
511 ... description="Team meeting in 15 minutes",
512 ... trigger=timedelta(minutes=-15),
513 ... )
514 >>> print(alarm.to_ical().decode())
515 BEGIN:VALARM
516 ACTION:DISPLAY
517 DESCRIPTION:Team meeting in 15 minutes
518 TRIGGER:-PT15M
519 END:VALARM
521 Attach the alarm to an event:
523 .. code-block:: python
525 from datetime import datetime, timedelta, timezone
526 from icalendar import Alarm, Event
528 event = Event.new(
529 summary="Team meeting",
530 start=datetime(2025, 6, 1, 10, 0, tzinfo=timezone.utc),
531 end=datetime(2025, 6, 1, 11, 0, tzinfo=timezone.utc),
532 )
533 event.add_component(Alarm.new_display(
534 description="Team meeting in 15 minutes",
535 trigger=timedelta(minutes=-15),
536 ))
537 """
538 if not description:
539 raise InvalidCalendar("DISPLAY alarm requires a description")
540 if trigger is None:
541 raise InvalidCalendar("DISPLAY alarm requires a trigger")
542 alarm: Alarm = cls.new(
543 action="DISPLAY",
544 description=description,
545 uid=uid,
546 links=links,
547 related_to=related_to,
548 refids=refids,
549 concepts=concepts,
550 )
551 alarm.TRIGGER = trigger
552 alarm._apply_duration_repeat(duration, repeat)
553 return alarm
555 @classmethod
556 def new_audio(
557 cls,
558 trigger: timedelta | datetime,
559 attachments: str | bytes | vUri | vBinary | None = None,
560 duration: timedelta | None = None,
561 repeat: int | None = None,
562 uid: str | uuid.UUID | None = None,
563 links: LINKS_TYPE_SETTER = None,
564 related_to: RELATED_TO_TYPE_SETTER = None,
565 refids: list[str] | str | None = None,
566 concepts: CONCEPTS_TYPE_SETTER = None,
567 ) -> Alarm:
568 """Create a new AUDIO alarm that plays a sound.
570 An AUDIO alarm plays a sound at the trigger time. An optional
571 ``attachments`` URI points to the audio file to play; when omitted,
572 the client uses its default alert sound.
574 Conforms to :rfc:`5545#section-3.6.6`.
576 Parameters:
577 trigger: Required. When the alarm fires, as a :class:`~datetime.timedelta`
578 relative to the event start (negative means before) or as an
579 absolute :class:`~datetime.datetime` (recommend UTC-aware).
580 attachments: Optional audio attachment. Accepts a URI as a
581 :class:`str` or :class:`~icalendar.prop.uri.vUri`, or
582 inline binary audio as :class:`bytes` or
583 :class:`~icalendar.prop.binary.vBinary`. When ``None``,
584 the client uses its default sound.
585 duration: Gap between repeated triggers. Must be paired with
586 ``repeat``. Corresponds to the :attr:`DURATION` property.
587 links: The :attr:`~icalendar.cal.component.Component.links` of the alarm.
588 refids: The :attr:`~icalendar.cal.component.Component.refids` of the alarm.
589 related_to: The :attr:`~icalendar.cal.component.Component.related_to` of the alarm.
590 repeat: Number of *additional* times to fire after the initial
591 trigger. Must be paired with ``duration``.
592 Corresponds to the :attr:`REPEAT` property.
593 uid: Unique identifier for the alarm or ``None``.
595 Returns:
596 :class:`Alarm` with ``ACTION:AUDIO`` set.
598 Raises:
599 ~icalendar.error.InvalidCalendar: If required fields are missing
600 or ``duration`` and ``repeat`` are not both provided together.
602 Example:
603 Create an audio alarm using a custom sound file:
605 .. code-block:: pycon
607 >>> from datetime import timedelta
608 >>> from icalendar import Alarm
609 >>> alarm = Alarm.new_audio(
610 ... trigger=timedelta(minutes=-5),
611 ... attachments="ftp://example.com/pub/sounds/bell-01.aud",
612 ... )
613 >>> print(alarm.to_ical().decode())
614 BEGIN:VALARM
615 ACTION:AUDIO
616 ATTACH:ftp://example.com/pub/sounds/bell-01.aud
617 TRIGGER:-PT5M
618 END:VALARM
619 """
620 if trigger is None:
621 raise InvalidCalendar("AUDIO alarm requires a trigger")
622 alarm: Alarm = cls.new(
623 action="AUDIO",
624 attachments=attachments,
625 uid=uid,
626 links=links,
627 related_to=related_to,
628 refids=refids,
629 concepts=concepts,
630 )
631 alarm.TRIGGER = trigger
632 alarm._apply_duration_repeat(duration, repeat)
633 return alarm
635 @classmethod
636 def new_email(
637 cls,
638 summary: str,
639 description: str,
640 trigger: timedelta | datetime,
641 attendees: ATTENDEE_TYPE_SETTER,
642 attachments: ATTACHMENTS_TYPE_SETTER = None,
643 duration: timedelta | None = None,
644 repeat: int | None = None,
645 uid: str | uuid.UUID | None = None,
646 links: LINKS_TYPE_SETTER = None,
647 related_to: RELATED_TO_TYPE_SETTER = None,
648 refids: list[str] | str | None = None,
649 concepts: CONCEPTS_TYPE_SETTER = None,
650 ) -> Alarm:
651 """Create a new EMAIL alarm that sends an email notification.
653 An EMAIL alarm sends an email to each address in ``attendees`` when
654 the alarm fires.
656 Conforms to :rfc:`5545#section-3.6.6`.
658 Parameters:
659 attendees: Required. One or more recipient addresses as email strings or
660 :class:`~icalendar.prop.cal_address.vCalAddress` instances. A
661 single address or a sequence of addresses. At least one is
662 required.
663 description: Required. Body of the email.
664 Corresponds to the :attr:`description` property.
665 summary: Required. Subject line of the email.
666 Corresponds to the :attr:`summary` property.
667 trigger: Required. When the alarm fires, as a :class:`~datetime.timedelta`
668 relative to the event start (negative means before) or as an
669 absolute :class:`~datetime.datetime` (recommend UTC-aware).
670 attachments: The :attr:`attachments` of the alarm. A single value
671 or a sequence of them. Both URIs and binary data are accepted.
672 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the alarm.
673 duration: Gap between repeated triggers. Must be paired with
674 ``repeat``. Corresponds to the :attr:`DURATION` property.
675 links: The :attr:`~icalendar.cal.component.Component.links` of the alarm.
676 refids: The :attr:`~icalendar.cal.component.Component.refids` of the alarm.
677 related_to: The :attr:`~icalendar.cal.component.Component.related_to` of the alarm.
678 repeat: Number of *additional* times to fire after the initial
679 trigger. Must be paired with ``duration``.
680 Corresponds to the :attr:`REPEAT` property.
681 uid: Unique identifier for the alarm or ``None``.
683 Returns:
684 :class:`Alarm` with ``ACTION:EMAIL`` set.
686 Raises:
687 ~icalendar.error.InvalidCalendar: If required fields are missing,
688 ``attendees`` is empty, or ``duration`` and ``repeat`` are not
689 both provided together.
691 Example:
692 Create an email alarm sent to two recipients. Plain email strings
693 and ``mailto:``-prefixed strings are both accepted and normalized
694 to :class:`~icalendar.prop.cal_address.vCalAddress`:
696 .. code-block:: pycon
698 >>> from datetime import timedelta
699 >>> from icalendar import Alarm
700 >>> alarm = Alarm.new_email(
701 ... summary="Meeting reminder",
702 ... description="Your meeting starts in 30 minutes.",
703 ... trigger=timedelta(minutes=-30),
704 ... attendees=["user@example.com", "mailto:boss@example.com"],
705 ... )
706 >>> print(alarm.to_ical().decode())
707 BEGIN:VALARM
708 ACTION:EMAIL
709 ATTENDEE:mailto:user@example.com
710 ATTENDEE:mailto:boss@example.com
711 DESCRIPTION:Your meeting starts in 30 minutes.
712 SUMMARY:Meeting reminder
713 TRIGGER:-PT30M
714 END:VALARM
715 """
716 if isinstance(attendees, (str, vCalAddress)):
717 attendees = [attendees]
718 elif attendees is None:
719 attendees = []
720 cleaned_attendees = [
721 a
722 for a in attendees
723 if a is not None and (not isinstance(a, str) or a.strip())
724 ]
725 if not summary:
726 raise InvalidCalendar("EMAIL alarm requires a summary")
727 if not description:
728 raise InvalidCalendar("EMAIL alarm requires a description")
729 if trigger is None:
730 raise InvalidCalendar("EMAIL alarm requires a trigger")
731 if not cleaned_attendees:
732 raise InvalidCalendar("EMAIL alarm requires at least one valid attendee")
733 alarm: Alarm = cls.new(
734 action="EMAIL",
735 attachments=attachments,
736 summary=summary,
737 description=description,
738 uid=uid,
739 attendees=cleaned_attendees,
740 links=links,
741 related_to=related_to,
742 refids=refids,
743 concepts=concepts,
744 )
745 alarm.TRIGGER = trigger
746 alarm._apply_duration_repeat(duration, repeat)
747 return alarm
749 @classmethod
750 def example(cls, name: str = "example") -> Alarm:
751 """Return the alarm example with the given name."""
752 return cls.from_ical(get_example("alarms", name))
755__all__ = ["Alarm"]