from __future__ import annotations from datetime import date, datetime, time from typing import Any, cast import pytz import requests from icalendar import Calendar, Event from data.alert import Alert from providers.alert.http_alert_provider import HTTPAlertProvider class ICALAlertProvider(HTTPAlertProvider): """Generic alert provider for iCal calendars. Defines an abstract method event_to_alert(event) that subclasses must implement, and use it to convert an iCal event to an Alert object based on whatever format their iCal events use.""" def __init__(self, name: str, provider_config: dict[str, Any], url: str, poll_interval: int) -> None: super().__init__(name, provider_config, url, poll_interval) def _http_response_to_alerts(self, http_response: requests.Response) -> list[Alert]: new_alerts = [] cal = Calendar.from_ical(http_response.content) # Iterate through events, passing each one in turn to the subclass' event_to_alert method to turn it into # a Spothole alert object for component in cal.walk(): if component.name != "VEVENT": continue event = cast(Event, component) alert = self.event_to_alert(event) new_alerts.append(alert) return new_alerts def event_to_alert(self, event: Event) -> Alert: """Convert an ICal event to an Alert object. Subclasses must implement this method.""" @staticmethod def _to_utc_timestamp(value: datetime | date) -> float: """Convert a date or datetime value from an iCal field into a UTC UNIX timestamp.""" # Datetime object so we can treat it as-is, check if it has a non-UTC tz and convert it if necessary if isinstance(value, datetime): if value.tzinfo is None: value = pytz.UTC.localize(value) return value.astimezone(pytz.UTC).timestamp() # Date object so this is an all day event return pytz.UTC.localize(datetime.combine(value, time.min)).timestamp()