""" Definition of an asyncio compatible CalDAV calendar. Caches events using `timed_alru_cache`. """ import functools import logging from datetime import UTC, datetime from typing import Annotated, Self from pydantic import AfterValidator, BaseModel, ConfigDict, StringConstraints from vobject.base import Component _logger = logging.getLogger(__name__) type StrippedStr = Annotated[str, StringConstraints(strip_whitespace=True)] def make_utc(v: datetime) -> datetime: if v.tzinfo is None: return v.replace(tzinfo=UTC) return v type UTCDateTime = Annotated[datetime, AfterValidator(make_utc)] @functools.total_ordering class CalEvent(BaseModel): """ A CalDAV calendar event. Properties are to be named as in the EVENT component of RFC5545 (iCalendar). https://icalendar.org/iCalendar-RFC-5545/3-6-1-event-component.html """ model_config = ConfigDict(frozen=True) summary: StrippedStr = "" description: StrippedStr = "" dtstart: UTCDateTime = datetime.now(UTC) dtend: UTCDateTime = datetime.now(UTC) def __lt__(self, other: Self) -> bool: """ Order Events by start time. """ return self.dtstart < other.dtstart def __eq__(self, other: Self) -> bool: """ Compare all properties. """ return self.model_dump() == other.model_dump() @classmethod def from_vevent(cls, event: Component) -> Self: """ Create a CalEvent instance from a `VObject.VEvent` object. """ data = {} keys = ("summary", "description", "dtstart", "dtend", "duration") for key in keys: try: data[key] = event.contents[key][0].value # type: ignore except KeyError: pass print(event) print(data) if "dtend" not in data: data["dtend"] = data["dtstart"] if "duration" in data: try: data["dtend"] += data["duration"] except (ValueError, TypeError, AttributeError): _logger.warn( "Could not add duration %s to %s", repr(data["duration"]), repr(data["dtstart"]), ) del data["duration"] return cls.model_validate(data)