Source code for ics.alarm.base
import copy
from datetime import datetime, timedelta
from typing import Optional, Union, Any
from abc import ABCMeta, abstractmethod
from ics.component import Component
from ics.utils import (
get_arrow,
get_lines
)
from ics.grammar.parse import Container
from ics.serializers.alarm_serializer import BaseAlarmSerializer
from ics.parsers.alarm_parser import BaseAlarmParser
[docs]class BaseAlarm(Component, metaclass=ABCMeta):
"""
A calendar event VALARM base class
"""
class Meta:
name = "VALARM"
parser = BaseAlarmParser
serializer = BaseAlarmSerializer
[docs] def __init__(
self,
trigger: Union[timedelta, datetime] = None,
repeat: int = None,
duration: timedelta = None,
) -> None:
"""
Instantiates a new :class:`ics.alarm.BaseAlarm`.
Adheres to RFC5545 VALARM standard: http://icalendar.org/iCalendar-RFC-5545/3-6-6-alarm-component.html
Args:
trigger (datetime.timedelta OR datetime.datetime) : Timespan to alert before parent action, or absolute time to alert
repeat (integer) : How many times to repeat the alarm
duration (datetime.timedelta) : Duration between repeats
Raises:
ValueError: If trigger, repeat, or duration do not match the RFC spec.
"""
# Set initial values
self._trigger: Optional[Union[timedelta, datetime]] = None
self._repeat: Optional[int] = None
self._duration: Optional[timedelta] = None
# Validate and parse
self.trigger = trigger
# XOR repeat and duration
if (repeat is not None) and (duration is None):
raise ValueError(
"A definition of an alarm with a repeating trigger MUST include both the DURATION and REPEAT properties."
)
if repeat:
self.repeat = repeat
if duration:
self.duration = duration
self.extra = Container(name="VALARM")
@classmethod
def _from_container(cls, container: Container, *args: Any, **kwargs: Any):
ret = super()._from_container(container, *args, **kwargs)
get_lines(ret.extra, "ACTION", keep=False) # Just drop the ACTION line
return ret
@property
def trigger(self) -> Optional[Union[timedelta, datetime]]:
"""The trigger condition for the alarm
| Returns either a timedelta or datetime object
"""
return self._trigger
@trigger.setter
def trigger(self, value: Optional[Union[timedelta, datetime]]) -> None:
if isinstance(value, datetime):
value = get_arrow(value)
self._trigger = value
@property
def repeat(self) -> Optional[int]:
"""Number of times to repeat alarm
| Returns an integer for number of alarm repeats
| Value must be >= 0
"""
return self._repeat
@repeat.setter
def repeat(self, value: Optional[int]) -> None:
if value is not None and value < 0:
raise ValueError("Repeat must be great than or equal to 0.")
self._repeat = value
@property
def duration(self) -> Optional[timedelta]:
"""Duration between alarm repeats
| Returns a timedelta object
| Timespan must return positive total_seconds() value
"""
return self._duration
@duration.setter
def duration(self, value: Optional[timedelta]) -> None:
if value is not None and value.total_seconds() < 0:
raise ValueError("Alarm duration timespan must be positive.")
self._duration = value
@property
@abstractmethod
def action(self):
""" VALARM action to be implemented by concrete classes
"""
raise NotImplementedError("Base class cannot be instantiated directly")
[docs] def __repr__(self):
value = "{0} trigger:{1}".format(type(self).__name__, self.trigger)
if self.repeat:
value += " repeat:{0} duration:{1}".format(self.repeat, self.duration)
return "<{0}>".format(value)
[docs] def __ne__(self, other) -> bool:
return not self.__eq__(other)
[docs] def __eq__(self, other) -> bool:
"""Two alarms are considered equal if they have the same type and base values."""
return (
type(self) is type(other)
and self.trigger == other.trigger
and self.repeat == other.repeat
and self.duration == other.duration
)
[docs] def clone(self):
"""
Returns:
Alarm: an exact copy of self"""
clone = copy.copy(self)
clone.extra = clone.extra.clone()
return clone