diff --git a/.gitignore b/.gitignore index 9e28a75..199f0e6 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,5 @@ docs/_build/ # PyBuilder target/ -/test/ \ No newline at end of file +/test/ +.dir-locals.el \ No newline at end of file diff --git a/gapi/api.py b/gapi/api.py index 7a6e67b..a153256 100644 --- a/gapi/api.py +++ b/gapi/api.py @@ -1,20 +1,21 @@ import datetime from googleapiclient.discovery import build -from googleapiclient.errors import * from oauth2client import client from oauth2client import tools from oauth2client.file import Storage -# from oauth2client.service_account import ServiceAccountCredentials + import httplib2 import os import argparse +from gapi import config + flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() -HOUR = datetime.timedelta(seconds=60**2) +HOUR = datetime.timedelta(seconds=60 ** 2) class API: @@ -23,9 +24,9 @@ class API: service, scopes, app_name, - client_secret_file, - credentials_dir, - version='v3', + client_secret_file=os.path.join(config.state_dir, "client_secret.json"), + credentials_dir=config.state_dir, + version="v3", ): self.service_name = service self.app_name = app_name @@ -38,20 +39,18 @@ class API: def get_credentials(self): - credential_path = os.path.join(self.credentials_dir, - 'token.json') + credential_path = os.path.join(self.credentials_dir, "token.json") store = Storage(credential_path) credentials = store.get() if not credentials or credentials.invalid: - flow = client.flow_from_clientsecrets( - self.client_secret_file, self.scopes) + flow = client.flow_from_clientsecrets(self.client_secret_file, self.scopes) flow.user_agent = self.app_name if flags: credentials = tools.run_flow(flow, store, flags) else: credentials = tools.run(flow, store) - print('Storing credentials to ' + credential_path) + print("Storing credentials to " + credential_path) return credentials def build_service(self): @@ -59,8 +58,9 @@ class API: http = credentials.authorize(httplib2.Http()) - service = build(self.service_name, self.version, - http=http, cache_discovery=False) + service = build( + self.service_name, self.version, http=http, cache_discovery=False + ) return service def _needs_renewal(self): diff --git a/gapi/calendar_api.py b/gapi/calendar_api.py index 3ba44e4..7a05542 100644 --- a/gapi/calendar_api.py +++ b/gapi/calendar_api.py @@ -1,17 +1,19 @@ 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, HttpError + from api import API else: - from gapi.api import API, HttpError + from gapi.api import API flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() -APPLICATION_NAME = 'Google Calendar API Python' +APPLICATION_NAME = "Google Calendar API Python" SUN = 6 MON = 0 @@ -27,35 +29,35 @@ def to_dateTime(datetime: datetime.datetime): if not datetime.tzinfo: datetime = datetime.astimezone() zone = tzlocal.get_localzone().zone - datetime = datetime.isoformat(timespec='seconds') - return { - "timeZone": zone, - "dateTime": datetime, - } + 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']) + 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, - credentials_dir, - scopes='https://www.googleapis.com/auth/calendar', - version='v3', - ): - super().__init__('calendar', scopes, app_name, - client_secret_file, credentials_dir, version) + def __init__( + self, + app_name, + client_secret_file, + credentials_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) + self.ids = dict( + (calendar["summary"].lower(), calendar["id"]) for calendar in self.calendars + ) def create_event(self, calendar_id, body): @@ -65,9 +67,8 @@ class calendar_api(API): pass service = self.service event_service = service.events() - event = event_service.insert( - calendarId=calendar_id, body=body).execute() - return event['id'] + event = event_service.insert(calendarId=calendar_id, body=body).execute() + return event["id"] def update_event(self, calendar_id, event_id, body): try: @@ -79,13 +80,17 @@ class calendar_api(API): event_service = service.events() try: event = event_service.get( - calendarId=calendar_id, eventId=event_id).execute() + 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() + updated_event = ( + service.events() + .update(calendarId=calendar_id, eventId=event["id"], body=body) + .execute() + ) return updated_event["id"] def get_calendars(self): @@ -95,11 +100,9 @@ class calendar_api(API): 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') + 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): @@ -113,9 +116,10 @@ class calendar_api(API): 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') + calendarId=cal_id, pageToken=page_token + ).execute() + ret += events["items"] + page_token = events.get("nextPageToken") if not page_token: break return ret @@ -130,17 +134,18 @@ class calendar_api(API): 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 - ): + 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 @@ -150,43 +155,42 @@ class Event: self.recurrence = recurrence self.id = id if reminders is None: - self.reminders = {'useDefault': True} + self.reminders = {"useDefault": True} else: self.reminders = reminders - def add_reminder(self, until, method='popup'): + 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 + self.reminders["useDefault"] = False if isinstance(until, datetime.timedelta): minutes_until = until.days * 24 * 60 - minutes_until += until.seconds//60 + minutes_until += until.seconds // 60 else: minutes_until = until - self.reminders.setdefault('overrides', []).append({ - 'method': method, - 'minutes': minutes_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) + 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') + keys = ("attendees", "description", "location", "recurrence", "reminders") ret = { - 'summary': self.name, - 'start': to_dateTime(self.start), - 'end': to_dateTime(self.end), + "summary": self.name, + "start": to_dateTime(self.start), + "end": to_dateTime(self.end), } for key in keys: try: @@ -203,11 +207,10 @@ class Event: @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') + 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] @@ -234,41 +237,42 @@ class Event: 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\ + "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' + "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' + 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') + 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 new file mode 100644 index 0000000..29e16dc --- /dev/null +++ b/gapi/config.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/drive_api.py b/gapi/drive_api.py index c7a02b4..1eb7041 100644 --- a/gapi/drive_api.py +++ b/gapi/drive_api.py @@ -6,143 +6,147 @@ import re import os APPLICATION_NAME = "Google Calendar API Python" -def path_translate( - root, - want, - path, - file - ): - l = len(root)+1 + + +def path_translate(root, want, path, file): + l = len(root) + 1 rel = path[l:] - new_rel = rel.replace(os.sep,'/') - return pathjoin(want,new_rel,file) + new_rel = rel.replace(os.sep, "/") + return pathjoin(want, new_rel, file) + def is_folder(element): - return element['mimeType'] == 'application/vnd.google-apps.folder' + return element["mimeType"] == "application/vnd.google-apps.folder" -def split_path(path,rev=False): - s = re.findall(r'(?:^/|[^/\x00]+)',path) + +def split_path(path, rev=False): + s = re.findall(r"(?:^/|[^/\x00]+)", path) if rev: return s[::-1] else: return s -def without(node,*keys): + +def without(node, *keys): node_meta = set(node.keys()) nope = set(keys) ret = {} - for key in node_meta-nope: + for key in node_meta - nope: ret[key] = node[key] return ret -def remote_path_join(path,*paths): + +def remote_path_join(path, *paths): ret = path for path in paths: - if not ret.endswith('/'): - ret += '/' + if not ret.endswith("/"): + ret += "/" else: ret += path return ret + class drive_file: - def __init__(self, - api, - info, - parent - ): - self.id = info.pop('id') - self.name = info.pop('name') + def __init__(self, api, info, parent): + self.id = info.pop("id") + self.name = info.pop("name") self.api = api self.info = info self.parent = parent self._fullpath = None @classmethod - def from_id(cls,id,api,parent): - info = api.service.files().get(fileId='root').execute() - return cls(api,info,parent) + def from_id(cls, id, api, parent): + info = api.service.files().get(fileId="root").execute() + return cls(api, info, parent) @property def fullpath(self): if self._fullpath is None: if self.parent is None: - path = '/' + path = "/" else: - path = pathjoin(self.parent.fullpath,self.name) + path = pathjoin(self.parent.fullpath, self.name) self._fullpath = path return self._fullpath def __str__(self): - return '{}@{}'.format(type(self).__name__,self.fullpath) + return "{}@{}".format(type(self).__name__, self.fullpath) def __repr__(self): return str(self) + class drive_folder(drive_file): - def __init__(self, - api, - info, - parent - ): - super().__init__(api,info,parent) + def __init__(self, api, info, parent): + super().__init__(api, info, parent) self.folders = {} self.files = {} - def create_folder(self,name): + def create_folder(self, name): meta = { - 'name':name, - 'mimeType':'application/vnd.google-apps.folder', - 'parents' : [self.id] + "name": name, + "mimeType": "application/vnd.google-apps.folder", + "parents": [self.id], } - info = self.api.service.files().create(body = meta).execute() - self.folders[name] = drive_folder(self.api,info,self) + info = self.api.service.files().create(body=meta).execute() + self.folders[name] = drive_folder(self.api, info, self) def refresh(self): - '''finds all files and folders from a parent''' + """finds all files and folders from a parent""" parent_id = self.id child_list = self.__list_children__() for child in child_list: - name = child['name'] + name = child["name"] if is_folder(child): - self.folders[name] = drive_folder(self.api,child,self) + self.folders[name] = drive_folder(self.api, child, self) else: - self.files[name] = drive_file(self.api,child,self) + self.files[name] = drive_file(self.api, child, self) def __list_children__(self): page_token = None first = True while page_token or first: first = False - response = self.api.service.files().list(q = "'{}' in parents and trashed = false".format(self.id),pageToken=page_token).execute() - page_token = response.get('nextPageToken',None) - for file in response['files']: + response = ( + self.api.service.files() + .list( + q="'{}' in parents and trashed = false".format(self.id), + pageToken=page_token, + ) + .execute() + ) + page_token = response.get("nextPageToken", None) + for file in response["files"]: yield file -class drive_api(API): +class drive_api(API): def __init__( - self, - app_name, - client_secret_file, - credentials_dir, - scopes = 'https://www.googleapis.com/auth/drive', - version = 'v3', - ): - super().__init__('drive',scopes,app_name,client_secret_file,credentials_dir,version) - self.root = drive_folder.from_id('root',self,None) - - def get_file_by_path(self,path,ret_file = True): - '''gets a file or folder by remote path''' - if isinstance(path,str): + self, + app_name, + client_secret_file, + credentials_dir, + scopes="https://www.googleapis.com/auth/drive", + version="v3", + ): + super().__init__( + "drive", scopes, app_name, client_secret_file, credentials_dir, version + ) + self.root = drive_folder.from_id("root", self, None) + + def get_file_by_path(self, path, ret_file=True): + """gets a file or folder by remote path""" + if isinstance(path, str): path = split_path(path) else: path = path.copy() parent = self.root end = path.pop() - if end == '/': + if end == "/": return self.root for sub in path: - if sub != '/': + if sub != "/": try: if not parent.folders: parent.refresh() @@ -162,42 +166,42 @@ class drive_api(API): except KeyError: raise FileNotFoundError - def mkdirs(self,path): - '''makes directories if they do not exist already''' - if isinstance(path,str): + def mkdirs(self, path): + """makes directories if they do not exist already""" + if isinstance(path, str): path = split_path(path) else: path = path.copy() missing = [] - for i in range(len(path),0,-1): + for i in range(len(path), 0, -1): try: - parent = self.get_file_by_path(path[:i],False) + parent = self.get_file_by_path(path[:i], False) break except FileNotFoundError: - missing.append(path[i-1]) + missing.append(path[i - 1]) while len(missing) > 0: name = missing.pop() parent.create_folder(name) parent = parent.folders[name] return parent - def upload(self,local_path,remote_path,verbose = False): - if not isinstance(remote_path,str): + def upload(self, local_path, remote_path, verbose=False): + if not isinstance(remote_path, str): remote_path = pathjoin(*remote_path) if os.path.isdir(local_path): - for root,dirs,files in os.walk(local_path,topdown = False): + for root, dirs, files in os.walk(local_path, topdown=False): for file in files: - new_path = path_translate(local_path,remote_path,root,file) - self.__upload_file__(os.path.join(root,file),new_path) + new_path = path_translate(local_path, remote_path, root, file) + self.__upload_file__(os.path.join(root, file), new_path) if verbose: print(new_path) - return self.get_file_by_path(remote_path,False) + return self.get_file_by_path(remote_path, False) else: - return self.__upload_file__(local_path,remote_path) + return self.__upload_file__(local_path, remote_path) - def __upload_file__(self,local_path,remote_path): - '''creates file if it does not exists otherwise update the file''' - if isinstance(remote_path,str): + def __upload_file__(self, local_path, remote_path): + """creates file if it does not exists otherwise update the file""" + if isinstance(remote_path, str): remote_path = split_path(remote_path) else: remote_path = remote_path.copy() @@ -205,7 +209,9 @@ class drive_api(API): try: old_file = self.get_file_by_path(remote_path) - self.service.files().update(fileId=old_file.id,media_body=_upload).execute() + self.service.files().update( + fileId=old_file.id, media_body=_upload + ).execute() return old_file except FileNotFoundError: @@ -213,19 +219,18 @@ class drive_api(API): end = path.pop() parent = self.mkdirs(path) parent_id = parent.id - meta = { - 'name':end, - 'parents':[parent_id] - } - new_f_meta = self.service.files().create( - body = meta, - media_body = _upload - ).execute() - new_f_meta = without(new_f_meta,'kind') - new_f = drive_file(self,new_f_meta,parent) + meta = {"name": end, "parents": [parent_id]} + new_f_meta = ( + self.service.files().create(body=meta, media_body=_upload).execute() + ) + new_f_meta = without(new_f_meta, "kind") + new_f = drive_file(self, new_f_meta, parent) parent.files[end] = new_f return new_f + if __name__ == "__main__": - my_api = drive_api(APPLICATION_NAME,r'..\test\drive\client_secret.json',r'..\test\drive') + my_api = drive_api( + APPLICATION_NAME, r"..\test\drive\client_secret.json", r"..\test\drive" + ) service = my_api.service diff --git a/requirements.txt b/requirements.txt index 6540ef2..6bceffc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ google-api-python-client python-dateutil oauth2client tzlocal +appdirs diff --git a/test_drive.py b/test_drive.py index 0dcbf1d..f9850ab 100644 --- a/test_drive.py +++ b/test_drive.py @@ -1,8 +1,9 @@ import os import gapi -from gapi.drive_api import drive_api,APPLICATION_NAME -my_api = drive_api(APPLICATION_NAME,r'test\drive\client_secret.json',r'test\drive') -t1 = my_api.get_file_by_path('/Python/Lib/site-packages',False) +from gapi.drive_api import drive_api, APPLICATION_NAME + +my_api = drive_api(APPLICATION_NAME, r"test\drive\client_secret.json", r"test\drive") +t1 = my_api.get_file_by_path("/Python/Lib/site-packages", False) # t1.refresh() # t2 = my_api.mkdirs('/test/t1/this/is/a/path/why/isnt/this/working') -# t3 = my_api.upload(os.getcwd(),'/test/t3') \ No newline at end of file +# t3 = my_api.upload(os.getcwd(),'/test/t3')