From 0b63b6bd44ecf671ff1da6ef1768c74b8589c854 Mon Sep 17 00:00:00 2001 From: Raphael Roberts Date: Sun, 29 Dec 2019 18:28:25 -0600 Subject: [PATCH] Redid some of this module's structure. drive_api still needs work --- gapi/__init__.py | 14 ++ gapi/apis/__init__.py | 0 gapi/{api.py => apis/base_api.py} | 6 +- gapi/apis/calendar_api/__init__.py | 5 + gapi/apis/calendar_api/calendar_api.py | 98 +++++++++ gapi/apis/calendar_api/models.py | 136 ++++++++++++ gapi/apis/calendar_api/utils.py | 22 ++ gapi/apis/drive_api/__init__.py | 0 gapi/{ => apis/drive_api}/drive_api.py | 0 gapi/calendar_api.py | 279 ------------------------- gapi/config.py | 14 -- tests/event_class.py | 46 ++++ 12 files changed, 324 insertions(+), 296 deletions(-) create mode 100644 gapi/apis/__init__.py rename gapi/{api.py => apis/base_api.py} (93%) create mode 100644 gapi/apis/calendar_api/__init__.py create mode 100644 gapi/apis/calendar_api/calendar_api.py create mode 100644 gapi/apis/calendar_api/models.py create mode 100644 gapi/apis/calendar_api/utils.py create mode 100644 gapi/apis/drive_api/__init__.py rename gapi/{ => apis/drive_api}/drive_api.py (100%) delete mode 100644 gapi/calendar_api.py delete mode 100644 gapi/config.py create mode 100644 tests/event_class.py diff --git a/gapi/__init__.py b/gapi/__init__.py index e69de29..29e16dc 100644 --- a/gapi/__init__.py +++ b/gapi/__init__.py @@ -0,0 +1,14 @@ +import os + +import appdirs + +app_dirs = appdirs.AppDirs("gapi") +config_dir = app_dirs.user_config_dir +cache_dir = app_dirs.user_cache_dir +log_dir = app_dirs.user_log_dir +state_dir = app_dirs.user_state_dir + + +for dir in (config_dir, cache_dir, log_dir, state_dir): + if not os.path.exists(dir): + os.makedirs(dir) diff --git a/gapi/apis/__init__.py b/gapi/apis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gapi/api.py b/gapi/apis/base_api.py similarity index 93% rename from gapi/api.py rename to gapi/apis/base_api.py index a153256..5f66e55 100644 --- a/gapi/api.py +++ b/gapi/apis/base_api.py @@ -11,7 +11,7 @@ import os import argparse -from gapi import config +import gapi flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() @@ -24,8 +24,8 @@ class API: service, scopes, app_name, - client_secret_file=os.path.join(config.state_dir, "client_secret.json"), - credentials_dir=config.state_dir, + client_secret_file=os.path.join(gapi.state_dir, "client_secret.json"), + credentials_dir=gapi.state_dir, version="v3", ): self.service_name = service diff --git a/gapi/apis/calendar_api/__init__.py b/gapi/apis/calendar_api/__init__.py new file mode 100644 index 0000000..54ddecc --- /dev/null +++ b/gapi/apis/calendar_api/__init__.py @@ -0,0 +1,5 @@ +import os + + +from .models import Event +from .calendar_api import calendar_api diff --git a/gapi/apis/calendar_api/calendar_api.py b/gapi/apis/calendar_api/calendar_api.py new file mode 100644 index 0000000..0b49a76 --- /dev/null +++ b/gapi/apis/calendar_api/calendar_api.py @@ -0,0 +1,98 @@ +import os + +from googleapiclient.errors import HttpError + +import gapi +from gapi.apis.base_api import API + +APPLICATION_NAME = "Google Calendar API Python" + + +class calendar_api(API): + def __init__( + self, + app_name, + client_secret_file=os.path.join(gapi.state_dir, "client_secret.json"), + credentials_dir=gapi.state_dir, + scopes="https://www.googleapis.com/auth/calendar", + version="v3", + ): + super().__init__( + "calendar", scopes, app_name, client_secret_file, credentials_dir, version + ) + + self.calendars = self.get_calendars() + self.ids = dict( + (calendar["summary"].lower(), calendar["id"]) for calendar in self.calendars + ) + + def create_event(self, calendar_id, body): + + try: + calendar_id = self.ids[calendar_id] + except KeyError: + pass + service = self.service + event_service = service.events() + event = event_service.insert(calendarId=calendar_id, body=body).execute() + return event["id"] + + def update_event(self, calendar_id, event_id, body): + try: + calendar_id = self.ids[calendar_id] + except KeyError: + pass + + service = self.service + event_service = service.events() + try: + event = event_service.get( + calendarId=calendar_id, eventId=event_id + ).execute() + except HttpError as e: + if e.resp.status == 404: + return self.create_event(calendar_id, body) + + updated_event = ( + service.events() + .update(calendarId=calendar_id, eventId=event["id"], body=body) + .execute() + ) + return updated_event["id"] + + def get_calendars(self): + page_token = None + cl = [] + first = True + while page_token or first: + first = False + calendar_list_service = self.service.calendarList() + calendar_list = calendar_list_service.list(pageToken=page_token).execute() + cl += list(calendar_entry for calendar_entry in calendar_list["items"]) + page_token = calendar_list.get("nextPageToken") + return cl + + def get_events(self, cal_id): + service = self.service + try: + cal_id = self.ids[cal_id] + except KeyError: + pass + page_token = None + ret = [] + while True: + event_service = service.events() + events = event_service.list( + calendarId=cal_id, pageToken=page_token + ).execute() + ret += events["items"] + page_token = events.get("nextPageToken") + if not page_token: + break + return ret + + def get_event_by_id(self, cal_id, event_id): + """Retrieves event from cal_id and event_id""" + service = self.service + event_service = service.events() + return event_service.get(calendarId=cal_id, eventId=event_id).execute() diff --git a/gapi/apis/calendar_api/models.py b/gapi/apis/calendar_api/models.py new file mode 100644 index 0000000..274a159 --- /dev/null +++ b/gapi/apis/calendar_api/models.py @@ -0,0 +1,136 @@ +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 Calendar: + """Model for representing a Google calendar""" + + pass + + +class Event: + """Model for a calendar event that can be uploaded""" + + def __init__( + self, + start, + end, + name, + description=None, + recurrence=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 + self.recurrence = recurrence + 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() + ret = rrule.rrule( + freq=rrule.WEEKLY, + dtstart=self.start, + wkst=weekdays.SUN, + until=until, + byweekday=days, + ) + ret_str = str(ret).split("\n")[-1] + ret_str = re.sub(r"(UNTIL=[^;]+)", r"\1Z", ret_str) + if self.recurrence is None: + self.recurrence = [] + self.recurrence.append(ret_str) + + def to_json(self): + keys = ("attendees", "description", "location", "recurrence", "reminders") + ret = { + "summary": self.name, + "start": to_dateTime(self.start), + "end": to_dateTime(self.end), + } + 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", "recurrence", "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 + + def delete(self, api: calendar_api): + pass diff --git a/gapi/apis/calendar_api/utils.py b/gapi/apis/calendar_api/utils.py new file mode 100644 index 0000000..208e315 --- /dev/null +++ b/gapi/apis/calendar_api/utils.py @@ -0,0 +1,22 @@ +import datetime + +import tzlocal +from dateutil import tz +from dateutil.parser import parse as date_parse + + +def to_dateTime(datetime: datetime.datetime): + """converts a datetime into json format for rest api""" + if not datetime.tzinfo: + datetime = datetime.astimezone() + zone = tzlocal.get_localzone().zone + datetime = datetime.isoformat(timespec="seconds") + return {"timeZone": zone, "dateTime": datetime} + + +def from_dateTime(dateTime): + """converts to a datetime from json format returned by rest api""" + timezone = tz.gettz(dateTime["timeZone"]) + datetime = date_parse(dateTime["dateTime"]) + datetime.replace(tzinfo=timezone) + return datetime diff --git a/gapi/apis/drive_api/__init__.py b/gapi/apis/drive_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gapi/drive_api.py b/gapi/apis/drive_api/drive_api.py similarity index 100% rename from gapi/drive_api.py rename to gapi/apis/drive_api/drive_api.py diff --git a/gapi/calendar_api.py b/gapi/calendar_api.py deleted file mode 100644 index f1722a9..0000000 --- a/gapi/calendar_api.py +++ /dev/null @@ -1,279 +0,0 @@ -import os -from dateutil import rrule, tz -from oauth2client import tools -from googleapiclient.errors import HttpError -from dateutil.parser import parse as date_parse -import re -import tzlocal -import argparse -import datetime - -if __name__ == "__main__": - from api import API -else: - from gapi.api import API, config - -flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() -APPLICATION_NAME = "Google Calendar API Python" - -SUN = 6 -MON = 0 -TUE = 1 -WED = 2 -THU = 3 -FRI = 4 -SAT = 5 - - -def to_dateTime(datetime: datetime.datetime): - """converts a datetime into json format for rest api""" - if not datetime.tzinfo: - datetime = datetime.astimezone() - zone = tzlocal.get_localzone().zone - datetime = datetime.isoformat(timespec="seconds") - return {"timeZone": zone, "dateTime": datetime} - - -def from_dateTime(dateTime): - """converts to a datetime from json format returned by rest api""" - timezone = tz.gettz(dateTime["timeZone"]) - datetime = date_parse(dateTime["dateTime"]) - datetime.replace(tzinfo=timezone) - return datetime - - -class calendar_api(API): - def __init__( - self, - app_name, - client_secret_file=os.path.join(config.state_dir, "client_secret.json"), - credentials_dir=config.state_dir, - scopes="https://www.googleapis.com/auth/calendar", - version="v3", - ): - super().__init__( - "calendar", scopes, app_name, client_secret_file, credentials_dir, version - ) - - self.calendars = self.get_calendars() - self.ids = dict( - (calendar["summary"].lower(), calendar["id"]) for calendar in self.calendars - ) - - def create_event(self, calendar_id, body): - - try: - calendar_id = self.ids[calendar_id] - except KeyError: - pass - service = self.service - event_service = service.events() - event = event_service.insert(calendarId=calendar_id, body=body).execute() - return event["id"] - - def update_event(self, calendar_id, event_id, body): - try: - calendar_id = self.ids[calendar_id] - except KeyError: - pass - - service = self.service - event_service = service.events() - try: - event = event_service.get( - calendarId=calendar_id, eventId=event_id - ).execute() - except HttpError as e: - if e.resp.status == 404: - return self.create_event(calendar_id, body) - - updated_event = ( - service.events() - .update(calendarId=calendar_id, eventId=event["id"], body=body) - .execute() - ) - return updated_event["id"] - - def get_calendars(self): - page_token = None - cl = [] - first = True - while page_token or first: - first = False - calendar_list_service = self.service.calendarList() - calendar_list = calendar_list_service.list(pageToken=page_token).execute() - cl += list(calendar_entry for calendar_entry in calendar_list["items"]) - page_token = calendar_list.get("nextPageToken") - return cl - - def get_events(self, cal_id): - service = self.service - try: - cal_id = self.ids[cal_id] - except KeyError: - pass - page_token = None - ret = [] - while True: - event_service = service.events() - events = event_service.list( - calendarId=cal_id, pageToken=page_token - ).execute() - ret += events["items"] - page_token = events.get("nextPageToken") - if not page_token: - break - return ret - - def get_event_by_id(self, cal_id, event_id): - """Retrieves event from cal_id and event_id""" - service = self.service - event_service = service.events() - return event_service.get(calendarId=cal_id, eventId=event_id).execute() - - -class Event: - """Model for a calendar event that can be uploaded""" - - def __init__( - self, - start, - end, - name, - description=None, - recurrence=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 - self.recurrence = recurrence - 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() - ret = rrule.rrule( - freq=rrule.WEEKLY, dtstart=self.start, wkst=SUN, until=until, byweekday=days - ) - ret_str = str(ret).split("\n")[-1] - ret_str = re.sub(r"(UNTIL=[^;]+)", r"\1Z", ret_str) - if self.recurrence is None: - self.recurrence = [] - self.recurrence.append(ret_str) - - def to_json(self): - keys = ("attendees", "description", "location", "recurrence", "reminders") - ret = { - "summary": self.name, - "start": to_dateTime(self.start), - "end": to_dateTime(self.end), - } - 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", "recurrence", "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()) - return event_id - - -if __name__ == "__main__": - import os - - # ~~BODY EXAMPLE~~##~~BODY EXAMPLE~~ - example = { - "attendees": [{"email": "lpage@example.com"}, {"email": "sbrin@example.com"}], - "description": "A chance to hear more about Google's\ - developer products.", - "end": { - "dateTime": "2015-05-28T17:00:00-07:00", - "timeZone": "America/Los_Angeles", - }, - "location": "800 Howard St., San Francisco, CA 94103", - "recurrence": ["RRULE:FREQ=DAILY;COUNT=2"], - "reminders": { - "overrides": [ - {"method": "email", "minutes": 1440}, - {"method": "popup", "minutes": 10}, - ], - "useDefault": False, - }, - "start": { - "dateTime": "2015-05-28T09:00:00-07:00", - "timeZone": "America/Los_Angeles", - }, - "summary": "Google I/O 2015", - } - e = Event( - date_parse("march 16, 2019 10:00 am"), - date_parse("march 16, 2019 3:30 pm"), - "Hang out with Matt", - ) - path = r"C:\Users\Raphael\Documents\local_repo\cred" - my_api = my_api = calendar_api( - "python", os.path.join(path, "client_secret.json", path), path - ) - cal_id = "raphael.roberts48@gmail.com" - e2 = Event.from_id(my_api, cal_id, "qmrsd88ma8ko67ri98d8pbhd7s") - until = datetime.datetime.today() + datetime.timedelta(days=20) - e2.add_weekly_recurrence(until, MON, TUE) - # e2.upload(my_api, cal_id) diff --git a/gapi/config.py b/gapi/config.py deleted file mode 100644 index 29e16dc..0000000 --- a/gapi/config.py +++ /dev/null @@ -1,14 +0,0 @@ -import os - -import appdirs - -app_dirs = appdirs.AppDirs("gapi") -config_dir = app_dirs.user_config_dir -cache_dir = app_dirs.user_cache_dir -log_dir = app_dirs.user_log_dir -state_dir = app_dirs.user_state_dir - - -for dir in (config_dir, cache_dir, log_dir, state_dir): - if not os.path.exists(dir): - os.makedirs(dir) diff --git a/tests/event_class.py b/tests/event_class.py new file mode 100644 index 0000000..41c9061 --- /dev/null +++ b/tests/event_class.py @@ -0,0 +1,46 @@ +from dateutil.parser import parse as date_parse + +from gapi.apis.calendar_api.models import weekdays, Event +from gapi.apis.calendar_api import calendar_api +import datetime + +if __name__ == "__main__": + + MY_API = calendar_api("fuck google") + # ~~BODY EXAMPLE~~##~~BODY EXAMPLE~~ + example = { + "attendees": [{"email": "lpage@example.com"}, {"email": "sbrin@example.com"}], + "description": "A chance to hear more about Google's\ + developer products.", + "end": { + "dateTime": "2015-05-28T17:00:00-07:00", + "timeZone": "America/Los_Angeles", + }, + "location": "800 Howard St., San Francisco, CA 94103", + "recurrence": ["RRULE:FREQ=DAILY;COUNT=2"], + "reminders": { + "overrides": [ + {"method": "email", "minutes": 1440}, + {"method": "popup", "minutes": 10}, + ], + "useDefault": False, + }, + "start": { + "dateTime": "2015-05-28T09:00:00-07:00", + "timeZone": "America/Los_Angeles", + }, + "summary": "Google I/O 2015", + } + e = Event( + date_parse("march 16, 2019 10:00 am"), + date_parse("march 16, 2019 3:30 pm"), + "Hang out with Matt", + ) + CAL_ID = "raphael.roberts48@gmail.com" + event = Event.from_json(example) + id = event.upload(MY_API, CAL_ID) + print(id) + e2 = Event.from_id(MY_API, CAL_ID, id) + until = datetime.datetime.today() + datetime.timedelta(days=20) + e2.add_weekly_recurrence(until, weekdays.MON, weekdays.TUE) + e2.upload(MY_API, CAL_ID)