from enum import IntEnum import datetime import re from dateutil import rrule from .calendar_api import calendar_api from .utils import to_dateTime, from_dateTime class weekdays(IntEnum): SUN = 6 MON = 0 TUE = 1 WED = 2 THU = 3 FRI = 4 SAT = 5 class Event: """Model for a calendar event that can be uploaded""" def __init__( self, start, end, name, description=None, recurrences=None, reminders=None, attendees=None, location=None, id=None, ): self.start = start self.attendees = attendees self.end = end self.name = name self.description = description self.location = location if recurrences is None: self.recurrences = [] else: self.recurrences = recurrences self.id = id if reminders is None: self.reminders = {"useDefault": True} else: self.reminders = reminders def add_reminder(self, until, method="popup"): """Add a reminder minutes before an event. Use either a notification (popup) or email""" assert method in ("email", "popup") self.reminders["useDefault"] = False if isinstance(until, datetime.timedelta): minutes_until = until.days * 24 * 60 minutes_until += until.seconds // 60 else: minutes_until = until self.reminders.setdefault("overrides", []).append( {"method": method, "minutes": minutes_until} ) def add_weekly_recurrence(self, until: datetime.datetime, *days): if not until.tzinfo: until = until.astimezone() self.recurrences.append( rrule.rrule( freq=rrule.WEEKLY, dtstart=self.start, wkst=weekdays.SUN, until=until, byweekday=days, ) ) def serialize_recurrences(self): for _rrule in self.recurrences: ret_str = str(_rrule).split("\n")[-1] ret_str = re.sub(r"(UNTIL=[^;]+)", r"\1Z", ret_str) yield ret_str def to_json(self): keys = ("attendees", "description", "location", "reminders") ret = { "summary": self.name, "start": to_dateTime(self.start), "end": to_dateTime(self.end), } for _rrule in self.serialize_recurrences(): ret.setdefault("recurrence", []).append(_rrule) for key in keys: try: value = self.__getattribute__(key) if value: ret[key] = value except AttributeError: pass return ret def add_attendees(self): pass @classmethod def from_json(cls, body): args = {} args["name"] = body.get("summary", "unnamed") args["start"] = from_dateTime(body["start"]) args["end"] = from_dateTime(body["end"]) keys = ( "attendees", "description", "location", "recurrences", "reminders", "id", ) for key in keys: try: args[key] = body[key] except KeyError: pass instance = cls(**args) return instance @classmethod def from_id(cls, api: calendar_api, calendar_id, event_id): """creates an event model from specified calendar and event_id""" event = api.get_event_by_id(calendar_id, event_id) return cls.from_json(event) def upload(self, api: calendar_api, calendar_id): """Upload an event to calendar. Either modifies an event in place or creates a new event.""" if self.id is not None: event_id = api.update_event(calendar_id, self.id, self.to_json()) else: event_id = api.create_event(calendar_id, self.to_json()) self.id = event_id return event_id class Calendar: """Model for representing a Google calendar""" def __init__(self, api: calendar_api, calendar_id): self.api = api if calendar_id in api.ids.keys(): self.name = calendar_id self.id = self.api.ids[self.name] elif calendar_id in api.ids.values(): self.name = self.api.calendars[calendar_id]["summary"] self.id = calendar_id else: raise ValueError("Non-existent calendar specified") self.calendar_id = calendar_id def update_or_add_event(self, event: Event): event.upload(self.api, self.calendar_id) def get_events( self, start: datetime.datetime = None, end: datetime.datetime = None ): return ( Event(event_representation) for event_representation in self.api.get_events_in_range( self.id, start, end ) ) def delete_event(self, event: Event): if event.id is not None: return self.api.delete_event(self.calendar_id, event.id) def search_events( self, event_name_regex=None, event_description_regex=None, start=None, end=None ): for event in self.get_events(start, end): will_yield = False if event_name_regex is not None: will_yield = re.search(event_name_regex, Event.name) is not None if event_description_regex is not None: will_yield = ( re.search(event_description_regex, Event.description) is not None or will_yield # noqa ) if will_yield: yield event def calendar_dict(api: calendar_api): return dict( (calendar_name, Calendar(api, calendar_name)) for calendar_name in api.ids.keys() )