mirror of
https://github.com/b4tman/sync_ics2gcal
synced 2025-01-21 07:28:24 +00:00
fix docs
This commit is contained in:
parent
cc75f522d4
commit
26e35c1b68
@ -7,8 +7,20 @@ import sys
|
||||
|
||||
|
||||
class GoogleCalendarService():
|
||||
"""class for make google calendar service Resource
|
||||
|
||||
Returns:
|
||||
service Resource
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def from_srv_acc_file(service_account_file):
|
||||
"""make service Resource from service account filename (authorize)
|
||||
|
||||
Returns:
|
||||
service Resource
|
||||
"""
|
||||
|
||||
scopes = 'https://www.googleapis.com/auth/calendar'
|
||||
credentials = service_account.ServiceAccountCredentials.from_json_keyfile_name(
|
||||
service_account_file, scopes=scopes)
|
||||
@ -18,6 +30,9 @@ class GoogleCalendarService():
|
||||
|
||||
|
||||
class GoogleCalendar():
|
||||
"""class to interact with calendar on google
|
||||
"""
|
||||
|
||||
logger = logging.getLogger('GoogleCalendar')
|
||||
|
||||
def __init__(self, service, calendarId):
|
||||
@ -25,9 +40,9 @@ class GoogleCalendar():
|
||||
self.calendarId = calendarId
|
||||
|
||||
def list_events_from(self, start):
|
||||
''' Получение списка событий из GCAL начиная с даты start
|
||||
'''
|
||||
fields='nextPageToken,items(id,iCalUID,updated)'
|
||||
""" list events from calendar, where start date >= start
|
||||
"""
|
||||
fields = 'nextPageToken,items(id,iCalUID,updated)'
|
||||
events = []
|
||||
page_token = None
|
||||
timeMin = utc.normalize(start.astimezone(utc)).replace(
|
||||
@ -44,7 +59,7 @@ class GoogleCalendar():
|
||||
return events
|
||||
|
||||
def find_exists(self, events):
|
||||
""" Поиск уже существующих в GCAL событий, из списка событий к вставке
|
||||
""" find existing events from list, by 'iCalUID' field
|
||||
|
||||
Arguments:
|
||||
events {list} -- list of events
|
||||
@ -54,7 +69,7 @@ class GoogleCalendar():
|
||||
events_exist - list of tuples: (new_event, exists_event)
|
||||
"""
|
||||
|
||||
fields='items(id,iCalUID,updated)'
|
||||
fields = 'items(id,iCalUID,updated)'
|
||||
events_by_req = []
|
||||
exists = []
|
||||
not_found = []
|
||||
@ -86,13 +101,13 @@ class GoogleCalendar():
|
||||
return exists, not_found
|
||||
|
||||
def insert_events(self, events):
|
||||
""" Вставка событий в GCAL
|
||||
""" insert list of events
|
||||
|
||||
Arguments:
|
||||
events -- список событий
|
||||
events - events list
|
||||
"""
|
||||
|
||||
fields='id'
|
||||
fields = 'id'
|
||||
events_by_req = []
|
||||
|
||||
def insert_callback(request_id, response, exception):
|
||||
@ -114,14 +129,13 @@ class GoogleCalendar():
|
||||
batch.execute()
|
||||
|
||||
def patch_events(self, event_tuples):
|
||||
""" Обновление (патч) событий в GCAL
|
||||
""" patch (update) events
|
||||
|
||||
Arguments:
|
||||
calendarId -- ИД календаря
|
||||
event_tuples -- список кортежей событий (новое, старое)
|
||||
event_tuples -- list of tuples: (new_event, exists_event)
|
||||
"""
|
||||
|
||||
fields='id'
|
||||
fields = 'id'
|
||||
events_by_req = []
|
||||
|
||||
def patch_callback(request_id, response, exception):
|
||||
@ -145,13 +159,13 @@ class GoogleCalendar():
|
||||
batch.execute()
|
||||
|
||||
def update_events(self, event_tuples):
|
||||
""" Обновление событий в GCAL
|
||||
""" update events
|
||||
|
||||
Arguments:
|
||||
event_tuples -- список кортежей событий (новое, старое)
|
||||
event_tuples -- list of tuples: (new_event, exists_event)
|
||||
"""
|
||||
|
||||
fields='id'
|
||||
fields = 'id'
|
||||
events_by_req = []
|
||||
|
||||
def update_callback(request_id, response, exception):
|
||||
@ -175,10 +189,10 @@ class GoogleCalendar():
|
||||
batch.execute()
|
||||
|
||||
def delete_events(self, events):
|
||||
""" Удаление событий в GCAL
|
||||
""" delete events
|
||||
|
||||
Arguments:
|
||||
events -- список событий
|
||||
events -- list of events
|
||||
"""
|
||||
|
||||
events_by_req = []
|
||||
@ -201,6 +215,9 @@ class GoogleCalendar():
|
||||
batch.execute()
|
||||
|
||||
def make_public(self):
|
||||
"""make calendar puplic
|
||||
"""
|
||||
|
||||
rule_public = {
|
||||
'scope': {
|
||||
'type': 'default',
|
||||
@ -210,6 +227,12 @@ class GoogleCalendar():
|
||||
return self.service.acl().insert(calendarId=self.calendarId, body=rule_public).execute()
|
||||
|
||||
def add_owner(self, email):
|
||||
"""add calendar owner by email
|
||||
|
||||
Arguments:
|
||||
email -- email to add
|
||||
"""
|
||||
|
||||
rule_owner = {
|
||||
'scope': {
|
||||
'type': 'user',
|
||||
|
@ -11,9 +11,27 @@ class EventConverter(Event):
|
||||
"""
|
||||
|
||||
def _str_prop(self, prop):
|
||||
"""decoded string property
|
||||
|
||||
Arguments:
|
||||
prop - propperty name
|
||||
|
||||
Returns:
|
||||
string value
|
||||
"""
|
||||
|
||||
return self.decoded(prop).decode(encoding='utf-8')
|
||||
|
||||
def _datetime_str_prop(self, prop):
|
||||
"""utc datetime as string from property
|
||||
|
||||
Arguments:
|
||||
prop -- property name
|
||||
|
||||
Returns:
|
||||
utc datetime value as string in iso format
|
||||
"""
|
||||
|
||||
date = self.decoded(prop)
|
||||
if not isinstance(date, datetime.datetime):
|
||||
date = datetime.datetime(
|
||||
@ -22,6 +40,15 @@ class EventConverter(Event):
|
||||
return utc.normalize(date.astimezone(utc)).replace(tzinfo=None).isoformat() + 'Z'
|
||||
|
||||
def _gcal_start(self):
|
||||
""" event start dict from icalendar event
|
||||
|
||||
Raises:
|
||||
ValueError -- if DTSTART not date or datetime
|
||||
|
||||
Returns:
|
||||
dict
|
||||
"""
|
||||
|
||||
start_date = self.decoded('DTSTART')
|
||||
if isinstance(start_date, datetime.datetime):
|
||||
return {
|
||||
@ -35,6 +62,16 @@ class EventConverter(Event):
|
||||
raise ValueError('DTSTART must be date or datetime')
|
||||
|
||||
def _gcal_end(self):
|
||||
"""event end dict from icalendar event
|
||||
|
||||
Raises:
|
||||
ValueError -- if DTEND not date or datetime
|
||||
ValueError -- if no DTEND or DURATION
|
||||
ValueError -- if end date/datetime not found
|
||||
Returns:
|
||||
dict
|
||||
"""
|
||||
|
||||
if 'DTEND' in self:
|
||||
end_date = self.decoded('DTEND')
|
||||
if isinstance(end_date, datetime.datetime):
|
||||
@ -66,6 +103,15 @@ class EventConverter(Event):
|
||||
raise ValueError('end date/time not found')
|
||||
|
||||
def _put_to_gcal(self, gcal_event, prop, func, ics_prop=None):
|
||||
"""get property from ical event if exist, and put to gcal event
|
||||
|
||||
Arguments:
|
||||
gcal_event -- dest event
|
||||
prop -- property name
|
||||
func -- function to convert
|
||||
ics_prop -- ical property name (default: {None})
|
||||
"""
|
||||
|
||||
if not ics_prop:
|
||||
ics_prop = prop
|
||||
if ics_prop in self:
|
||||
@ -113,6 +159,11 @@ class CalendarConverter():
|
||||
self.calendar = Calendar.from_ical(f.read())
|
||||
self.logger.info('%s loaded', filename)
|
||||
|
||||
def loads(self, string):
|
||||
""" load calendar from ics string
|
||||
"""
|
||||
self.calendar = Calendar.from_ical(string)
|
||||
|
||||
def events_to_gcal(self):
|
||||
"""Convert events to google calendar resources
|
||||
"""
|
||||
|
@ -1,16 +1,22 @@
|
||||
import datetime
|
||||
import dateutil.parser
|
||||
import logging
|
||||
import operator
|
||||
from pytz import utc
|
||||
|
||||
|
||||
class CalendarSync():
|
||||
"""class for syncronize calendar with google
|
||||
"""
|
||||
|
||||
logger = logging.getLogger('CalendarSync')
|
||||
|
||||
def __init__(self, gcalendar, converter):
|
||||
self.gcalendar = gcalendar
|
||||
self.converter = converter
|
||||
|
||||
def _events_list_compare(self, items_src, items_dst, key='iCalUID'):
|
||||
@staticmethod
|
||||
def _events_list_compare(items_src, items_dst, key='iCalUID'):
|
||||
""" compare list of events by key
|
||||
|
||||
Arguments:
|
||||
@ -44,13 +50,7 @@ class CalendarSync():
|
||||
return items_to_insert, items_to_update, items_to_delete
|
||||
|
||||
def _filter_events_to_update(self):
|
||||
""" Отбор событий к обновлению, по дате обновления
|
||||
|
||||
Arguments:
|
||||
events -- список кортежей к обновлению (новое, старое)
|
||||
|
||||
Returns:
|
||||
список кортежей к обновлению (новое, старое)
|
||||
""" filter 'to_update' events by 'updated' datetime
|
||||
"""
|
||||
|
||||
def filter_updated(event_tuple):
|
||||
@ -59,25 +59,41 @@ class CalendarSync():
|
||||
|
||||
self.to_update = list(filter(filter_updated, self.to_update))
|
||||
|
||||
def _filter_events_by_date(self, events, date, op):
|
||||
""" Отбор событий по дате обновления
|
||||
@staticmethod
|
||||
def _filter_events_by_date(events, date, op):
|
||||
""" filter events by start datetime
|
||||
|
||||
Arguments:
|
||||
events -- список событий к обновлению
|
||||
date {datetime} -- дата для сравнения
|
||||
op {operator} -- оператор сравнения
|
||||
events -- events list
|
||||
date {datetime} -- datetime to compare
|
||||
op {operator} -- comparsion operator
|
||||
|
||||
Returns:
|
||||
список событий
|
||||
list of filtred events
|
||||
"""
|
||||
|
||||
def filter_by_date(event):
|
||||
return op(dateutil.parser.parse(event['updated']), date)
|
||||
event_start = event['start']
|
||||
event_date = None
|
||||
if 'date' in event_start:
|
||||
event_date = event_start['date']
|
||||
if 'dateTime' in event_start:
|
||||
event_date = event_start['dateTime']
|
||||
return op(dateutil.parser.parse(event_date), date)
|
||||
|
||||
return list(filter(filter_by_date, events))
|
||||
|
||||
@staticmethod
|
||||
def _tz_aware_datetime(date):
|
||||
"""make tz aware datetime from datetime/date (utc if no tzinfo)
|
||||
|
||||
Arguments:
|
||||
date - date or datetime / with or without tzinfo
|
||||
|
||||
Returns:
|
||||
datetime with tzinfo
|
||||
"""
|
||||
|
||||
if not isinstance(date, datetime.datetime):
|
||||
date = datetime.datetime(date.year, date.month, date.day)
|
||||
if date.tzinfo is None:
|
||||
@ -85,46 +101,55 @@ class CalendarSync():
|
||||
return date
|
||||
|
||||
def prepare_sync(self, start_date):
|
||||
start_date = _tz_aware_datetime(start_date)
|
||||
"""prepare sync lists by comparsion of events
|
||||
|
||||
Arguments:
|
||||
start_date -- date/datetime to start sync
|
||||
"""
|
||||
|
||||
start_date = CalendarSync._tz_aware_datetime(start_date)
|
||||
|
||||
events_src = self.converter.events_to_gcal()
|
||||
events_dst = self.gcalendar.list_events_from(start_date)
|
||||
|
||||
# разбитие тестовых событий на будующие и прошлые
|
||||
events_src_pending = self._filter_events_by_date(
|
||||
# divide source events by start datetime
|
||||
events_src_pending = CalendarSync._filter_events_by_date(
|
||||
events_src, start_date, operator.ge)
|
||||
events_src_past = self._filter_events_by_date(
|
||||
events_src_past = CalendarSync._filter_events_by_date(
|
||||
events_src, start_date, operator.lt)
|
||||
|
||||
events_src = None
|
||||
|
||||
# первоначальное сравнение списков
|
||||
self.to_insert, self.to_update, self.to_delete = self._events_list_compare(
|
||||
# first events comparsion
|
||||
self.to_insert, self.to_update, self.to_delete = CalendarSync._events_list_compare(
|
||||
events_src_pending, events_dst)
|
||||
|
||||
events_src_pending, events_dst = None, None
|
||||
|
||||
# сравнение списка на удаление со списком прошлых событий, для определения доп событий к обновлению
|
||||
_, add_to_update, self.to_delete = self._events_list_compare(
|
||||
# find in events 'to_delete' past events from source, for update (move to past)
|
||||
_, add_to_update, self.to_delete = CalendarSync._events_list_compare(
|
||||
events_src_past, self.to_delete)
|
||||
self.to_update.extend(add_to_update)
|
||||
|
||||
events_src_past = None
|
||||
|
||||
# проверка списка к вставке и перемещение доп. элементов в список к обновлению
|
||||
# find if events 'to_insert' exists in gcalendar, for update them
|
||||
add_to_update, self.to_insert = self.gcalendar.find_exists(
|
||||
self.to_insert)
|
||||
self.to_update.extend(add_to_update)
|
||||
|
||||
add_to_update = None
|
||||
|
||||
# отбор событий требующих обновления (по полю 'updated')
|
||||
# exclude outdated events from 'to_update' list, by 'updated' field
|
||||
self._filter_events_to_update()
|
||||
|
||||
self.logger.info('prepared to sync: ( insert: %d, update: %d, delete: %d )',
|
||||
len(self.to_insert), len(self.to_update), len(self.to_delete))
|
||||
|
||||
def apply(self):
|
||||
"""apply sync (insert, update, delete), using prepared lists of events
|
||||
"""
|
||||
|
||||
self.gcalendar.insert_events(self.to_insert)
|
||||
self.gcalendar.update_events(self.to_update)
|
||||
self.gcalendar.delete_events(self.to_delete)
|
||||
|
Loading…
x
Reference in New Issue
Block a user