1
0
mirror of https://github.com/b4tman/sync_ics2gcal synced 2025-09-08 19:17:31 +00:00

Format with black.

This commit is contained in:
Glenn Waters
2022-03-08 10:46:21 -05:00
parent 2372103807
commit 70278c1542
10 changed files with 507 additions and 328 deletions

View File

@@ -1,18 +1,11 @@
from .ical import (
CalendarConverter,
EventConverter,
DateDateTime
)
from .ical import CalendarConverter, EventConverter, DateDateTime
from .gcal import (
GoogleCalendarService,
GoogleCalendar,
EventData,
EventList,
EventTuple
EventTuple,
)
from .sync import (
CalendarSync
)
from .sync import CalendarSync

View File

@@ -7,7 +7,7 @@ from google.oauth2 import service_account
from googleapiclient import discovery
from pytz import utc
EventData = Dict[str, Union[str, 'EventData', None]]
EventData = Dict[str, Union[str, "EventData", None]]
EventList = List[EventData]
EventTuple = Tuple[EventData, EventData]
@@ -26,24 +26,25 @@ class GoogleCalendarService:
( https://googleapis.dev/python/google-auth/latest/reference/google.auth.html#google.auth.default )
"""
scopes = ['https://www.googleapis.com/auth/calendar']
scopes = ["https://www.googleapis.com/auth/calendar"]
credentials, _ = google.auth.default(scopes=scopes)
service = discovery.build(
'calendar', 'v3', credentials=credentials, cache_discovery=False)
"calendar", "v3", credentials=credentials, cache_discovery=False
)
return service
@staticmethod
def from_srv_acc_file(service_account_file: str):
"""make service Resource from service account filename (authorize)
"""
"""make service Resource from service account filename (authorize)"""
scopes = ['https://www.googleapis.com/auth/calendar']
scopes = ["https://www.googleapis.com/auth/calendar"]
credentials = service_account.Credentials.from_service_account_file(
service_account_file)
service_account_file
)
scoped_credentials = credentials.with_scopes(scopes)
service = discovery.build(
'calendar', 'v3', credentials=scoped_credentials,
cache_discovery=False)
"calendar", "v3", credentials=scoped_credentials, cache_discovery=False
)
return service
@staticmethod
@@ -58,9 +59,8 @@ class GoogleCalendarService:
-- None: default credentials will be used
"""
if config is not None and 'service_account' in config:
service = GoogleCalendarService.from_srv_acc_file(
config['service_account'])
if config is not None and "service_account" in config:
service = GoogleCalendarService.from_srv_acc_file(config["service_account"])
else:
service = GoogleCalendarService.default()
return service
@@ -77,18 +77,17 @@ def select_event_key(event: EventData) -> Optional[str]:
"""
key = None
if 'iCalUID' in event:
key = 'iCalUID'
elif 'id' in event:
key = 'id'
if "iCalUID" in event:
key = "iCalUID"
elif "id" in event:
key = "id"
return key
class GoogleCalendar:
"""class to interact with calendar on Google
"""
"""class to interact with calendar on Google"""
logger = logging.getLogger('GoogleCalendar')
logger = logging.getLogger("GoogleCalendar")
def __init__(self, service: discovery.Resource, calendar_id: Optional[str]):
self.service: discovery.Resource = service
@@ -111,43 +110,51 @@ class GoogleCalendar:
if exception is not None:
self.logger.error(
'failed to %s event with %s: %s, exception: %s',
action, key, event.get(key), str(exception)
"failed to %s event with %s: %s, exception: %s",
action,
key,
event.get(key),
str(exception),
)
else:
resp_key = select_event_key(response)
if resp_key is not None:
event = response
key = resp_key
self.logger.info('event %s ok, %s: %s',
action, key, event.get(key))
self.logger.info("event %s ok, %s: %s", action, key, event.get(key))
return callback
def list_events_from(self, start: datetime) -> EventList:
""" list events from calendar, where start date >= 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
time_min = utc.normalize(start.astimezone(utc)).replace(
tzinfo=None).isoformat() + 'Z'
time_min = (
utc.normalize(start.astimezone(utc)).replace(tzinfo=None).isoformat() + "Z"
)
while True:
response = self.service.events().list(calendarId=self.calendar_id,
pageToken=page_token,
singleEvents=True,
timeMin=time_min,
fields=fields).execute()
if 'items' in response:
events.extend(response['items'])
page_token = response.get('nextPageToken')
response = (
self.service.events()
.list(
calendarId=self.calendar_id,
pageToken=page_token,
singleEvents=True,
timeMin=time_min,
fields=fields,
)
.execute()
)
if "items" in response:
events.extend(response["items"])
page_token = response.get("nextPageToken")
if not page_token:
break
self.logger.info('%d events listed', len(events))
self.logger.info("%d events listed", len(events))
return events
def find_exists(self, events: List) -> Tuple[List[EventTuple], EventList]:
""" find existing events from list, by 'iCalUID' field
"""find existing events from list, by 'iCalUID' field
Arguments:
events {list} -- list of events
@@ -157,7 +164,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 = []
@@ -166,14 +173,15 @@ class GoogleCalendar:
found = False
cur_event = events_by_req[int(request_id)]
if exception is None:
found = ([] != response['items'])
found = [] != response["items"]
else:
self.logger.error(
'exception %s, while listing event with UID: %s',
str(exception), cur_event['iCalUID'])
"exception %s, while listing event with UID: %s",
str(exception),
cur_event["iCalUID"],
)
if found:
exists.append(
(cur_event, response['items'][0]))
exists.append((cur_event, response["items"][0]))
else:
not_found.append(events_by_req[int(request_id)])
@@ -181,89 +189,102 @@ class GoogleCalendar:
i = 0
for event in events:
events_by_req.append(event)
batch.add(self.service.events().list(calendarId=self.calendar_id,
iCalUID=event['iCalUID'],
showDeleted=True,
fields=fields
),
request_id=str(i)
)
batch.add(
self.service.events().list(
calendarId=self.calendar_id,
iCalUID=event["iCalUID"],
showDeleted=True,
fields=fields,
),
request_id=str(i),
)
i += 1
batch.execute()
self.logger.info('%d events exists, %d not found',
len(exists), len(not_found))
self.logger.info("%d events exists, %d not found", len(exists), len(not_found))
return exists, not_found
def insert_events(self, events: EventList):
""" insert list of events
"""insert list of events
Arguments:
events - events list
"""
fields = 'id'
fields = "id"
events_by_req = []
insert_callback = self._make_request_callback('insert', events_by_req)
insert_callback = self._make_request_callback("insert", events_by_req)
batch = self.service.new_batch_http_request(callback=insert_callback)
i = 0
for event in events:
events_by_req.append(event)
batch.add(self.service.events().insert(
calendarId=self.calendar_id, body=event, fields=fields),
request_id=str(i)
batch.add(
self.service.events().insert(
calendarId=self.calendar_id, body=event, fields=fields
),
request_id=str(i),
)
i += 1
batch.execute()
def patch_events(self, event_tuples: List[EventTuple]):
""" patch (update) events
"""patch (update) events
Arguments:
event_tuples -- list of tuples: (new_event, exists_event)
"""
fields = 'id'
fields = "id"
events_by_req = []
patch_callback = self._make_request_callback('patch', events_by_req)
patch_callback = self._make_request_callback("patch", events_by_req)
batch = self.service.new_batch_http_request(callback=patch_callback)
i = 0
for event_new, event_old in event_tuples:
if 'id' not in event_old:
if "id" not in event_old:
continue
events_by_req.append(event_new)
batch.add(self.service.events().patch(
calendarId=self.calendar_id, eventId=event_old['id'],
body=event_new), fields=fields, request_id=str(i))
batch.add(
self.service.events().patch(
calendarId=self.calendar_id, eventId=event_old["id"], body=event_new
),
fields=fields,
request_id=str(i),
)
i += 1
batch.execute()
def update_events(self, event_tuples: List[EventTuple]):
""" update events
"""update events
Arguments:
event_tuples -- list of tuples: (new_event, exists_event)
"""
fields = 'id'
fields = "id"
events_by_req = []
update_callback = self._make_request_callback('update', events_by_req)
update_callback = self._make_request_callback("update", events_by_req)
batch = self.service.new_batch_http_request(callback=update_callback)
i = 0
for event_new, event_old in event_tuples:
if 'id' not in event_old:
if "id" not in event_old:
continue
events_by_req.append(event_new)
batch.add(self.service.events().update(
calendarId=self.calendar_id, eventId=event_old['id'],
body=event_new, fields=fields), request_id=str(i))
batch.add(
self.service.events().update(
calendarId=self.calendar_id,
eventId=event_old["id"],
body=event_new,
fields=fields,
),
request_id=str(i),
)
i += 1
batch.execute()
def delete_events(self, events: EventList):
""" delete events
"""delete events
Arguments:
events -- list of events
@@ -271,14 +292,17 @@ class GoogleCalendar:
events_by_req = []
delete_callback = self._make_request_callback('delete', events_by_req)
delete_callback = self._make_request_callback("delete", events_by_req)
batch = self.service.new_batch_http_request(callback=delete_callback)
i = 0
for event in events:
events_by_req.append(event)
batch.add(self.service.events().delete(
calendarId=self.calendar_id,
eventId=event['id']), request_id=str(i))
batch.add(
self.service.events().delete(
calendarId=self.calendar_id, eventId=event["id"]
),
request_id=str(i),
)
i += 1
batch.execute()
@@ -295,36 +319,33 @@ class GoogleCalendar:
calendar Resource
"""
calendar = {'summary': summary}
calendar = {"summary": summary}
if time_zone is not None:
calendar['timeZone'] = time_zone
calendar["timeZone"] = time_zone
created_calendar = self.service.calendars().insert(
body=calendar
).execute()
self.calendar_id = created_calendar['id']
created_calendar = self.service.calendars().insert(body=calendar).execute()
self.calendar_id = created_calendar["id"]
return created_calendar
def delete(self):
"""delete calendar
"""
"""delete calendar"""
self.service.calendars().delete(calendarId=self.calendar_id).execute()
def make_public(self):
"""make calendar public
"""
"""make calendar public"""
rule_public = {
'scope': {
'type': 'default',
"scope": {
"type": "default",
},
'role': 'reader'
"role": "reader",
}
return self.service.acl().insert(
calendarId=self.calendar_id,
body=rule_public
).execute()
return (
self.service.acl()
.insert(calendarId=self.calendar_id, body=rule_public)
.execute()
)
def add_owner(self, email: str):
"""add calendar owner by email
@@ -334,13 +355,14 @@ class GoogleCalendar:
"""
rule_owner = {
'scope': {
'type': 'user',
'value': email,
"scope": {
"type": "user",
"value": email,
},
'role': 'owner'
"role": "owner",
}
return self.service.acl().insert(
calendarId=self.calendar_id,
body=rule_owner
).execute()
return (
self.service.acl()
.insert(calendarId=self.calendar_id, body=rule_owner)
.execute()
)

View File

@@ -20,18 +20,15 @@ def format_datetime_utc(value: DateDateTime) -> str:
utc datetime value as string in iso format
"""
if not isinstance(value, datetime.datetime):
value = datetime.datetime(
value.year, value.month, value.day, tzinfo=utc)
value = datetime.datetime(value.year, value.month, value.day, tzinfo=utc)
value = value.replace(microsecond=1)
return utc.normalize(
value.astimezone(utc)
).replace(tzinfo=None).isoformat() + 'Z'
return utc.normalize(value.astimezone(utc)).replace(tzinfo=None).isoformat() + "Z"
def gcal_date_or_datetime(value: DateDateTime,
check_value: Optional[DateDateTime] = None) \
-> Dict[str, str]:
def gcal_date_or_datetime(
value: DateDateTime, check_value: Optional[DateDateTime] = None
) -> Dict[str, str]:
"""date or datetime to gcal (start or end dict)
Arguments:
@@ -47,12 +44,12 @@ def gcal_date_or_datetime(value: DateDateTime,
result: Dict[str, str] = {}
if isinstance(check_value, datetime.datetime):
result['dateTime'] = format_datetime_utc(value)
result["dateTime"] = format_datetime_utc(value)
else:
if isinstance(check_value, datetime.date):
if isinstance(value, datetime.datetime):
value = datetime.date(value.year, value.month, value.day)
result['date'] = value.isoformat()
result["date"] = value.isoformat()
return result
@@ -71,7 +68,7 @@ class EventConverter(Event):
string value
"""
return self.decoded(prop).decode(encoding='utf-8')
return self.decoded(prop).decode(encoding="utf-8")
def _datetime_str_prop(self, prop: str) -> str:
"""utc datetime as string from property
@@ -86,7 +83,7 @@ class EventConverter(Event):
return format_datetime_utc(self.decoded(prop))
def _gcal_start(self) -> Dict[str, str]:
""" event start dict from icalendar event
"""event start dict from icalendar event
Raises:
ValueError -- if DTSTART not date or datetime
@@ -95,7 +92,7 @@ class EventConverter(Event):
dict
"""
value = self.decoded('DTSTART')
value = self.decoded("DTSTART")
return gcal_date_or_datetime(value)
def _gcal_end(self) -> Dict[str, str]:
@@ -108,22 +105,26 @@ class EventConverter(Event):
"""
result: Dict[str, str]
if 'DTEND' in self:
value = self.decoded('DTEND')
if "DTEND" in self:
value = self.decoded("DTEND")
result = gcal_date_or_datetime(value)
elif 'DURATION' in self:
start_val = self.decoded('DTSTART')
duration = self.decoded('DURATION')
elif "DURATION" in self:
start_val = self.decoded("DTSTART")
duration = self.decoded("DURATION")
end_val = start_val + duration
result = gcal_date_or_datetime(end_val, check_value=start_val)
else:
raise ValueError('no DTEND or DURATION')
raise ValueError("no DTEND or DURATION")
return result
def _put_to_gcal(self, gcal_event: EventData,
prop: str, func: Callable[[str], str],
ics_prop: Optional[str] = None):
def _put_to_gcal(
self,
gcal_event: EventData,
prop: str,
func: Callable[[str], str],
ics_prop: Optional[str] = None,
):
"""get property from ical event if existed, and put to gcal event
Arguments:
@@ -146,54 +147,47 @@ class EventConverter(Event):
"""
event = {
'iCalUID': self._str_prop('UID'),
'start': self._gcal_start(),
'end': self._gcal_end()
"iCalUID": self._str_prop("UID"),
"start": self._gcal_start(),
"end": self._gcal_end(),
}
self._put_to_gcal(event, 'summary', self._str_prop)
self._put_to_gcal(event, 'description', self._str_prop)
self._put_to_gcal(event, 'location', self._str_prop)
self._put_to_gcal(event, 'created', self._datetime_str_prop)
self._put_to_gcal(event, "summary", self._str_prop)
self._put_to_gcal(event, "description", self._str_prop)
self._put_to_gcal(event, "location", self._str_prop)
self._put_to_gcal(event, "created", self._datetime_str_prop)
self._put_to_gcal(event, "updated", self._datetime_str_prop, "LAST-MODIFIED")
self._put_to_gcal(
event, 'updated', self._datetime_str_prop, 'LAST-MODIFIED')
self._put_to_gcal(
event,
'transparency',
lambda prop: self._str_prop(prop).lower(), 'TRANSP')
event, "transparency", lambda prop: self._str_prop(prop).lower(), "TRANSP"
)
return event
class CalendarConverter:
"""Convert icalendar events to google calendar resources
"""
"""Convert icalendar events to google calendar resources"""
logger = logging.getLogger('CalendarConverter')
logger = logging.getLogger("CalendarConverter")
def __init__(self, calendar: Optional[Calendar] = None):
self.calendar: Optional[Calendar] = calendar
def load(self, filename: str):
""" load calendar from ics file
"""
with open(filename, 'r', encoding='utf-8') as f:
"""load calendar from ics file"""
with open(filename, "r", encoding="utf-8") as f:
self.calendar = Calendar.from_ical(f.read())
self.logger.info('%s loaded', filename)
self.logger.info("%s loaded", filename)
def loads(self, string: str):
""" load calendar from ics string
"""
"""load calendar from ics string"""
self.calendar = Calendar.from_ical(string)
def events_to_gcal(self) -> EventList:
"""Convert events to google calendar resources
"""
"""Convert events to google calendar resources"""
ics_events = self.calendar.walk(name='VEVENT')
self.logger.info('%d events read', len(ics_events))
ics_events = self.calendar.walk(name="VEVENT")
self.logger.info("%d events read", len(ics_events))
result = list(
map(lambda event: EventConverter(event).to_gcal(), ics_events))
self.logger.info('%d events converted', len(result))
result = list(map(lambda event: EventConverter(event).to_gcal(), ics_events))
self.logger.info("%d events converted", len(result))
return result

View File

@@ -10,7 +10,7 @@ from . import GoogleCalendar, GoogleCalendarService
def load_config(filename: str) -> Optional[Dict[str, Any]]:
result = None
try:
with open(filename, 'r', encoding='utf-8') as f:
with open(filename, "r", encoding="utf-8") as f:
result = yaml.safe_load(f)
except FileNotFoundError:
pass
@@ -19,24 +19,27 @@ def load_config(filename: str) -> Optional[Dict[str, Any]]:
class PropertyCommands:
""" get/set google calendar properties """
"""get/set google calendar properties"""
def __init__(self, _service):
self._service = _service
def get(self, calendar_id: str, property_name: str) -> None:
""" get calendar property
"""get calendar property
Args:
calendar_id: calendar id
property_name: property key
"""
response = self._service.calendarList().get(calendarId=calendar_id,
fields=property_name).execute()
response = (
self._service.calendarList()
.get(calendarId=calendar_id, fields=property_name)
.execute()
)
print(response.get(property_name))
def set(self, calendar_id: str, property_name: str, property_value: str) -> None:
""" set calendar property
"""set calendar property
Args:
calendar_id: calendar id
@@ -44,53 +47,60 @@ class PropertyCommands:
property_value: property value
"""
body = {property_name: property_value}
response = self._service.calendarList().patch(body=body, calendarId=calendar_id).execute()
response = (
self._service.calendarList()
.patch(body=body, calendarId=calendar_id)
.execute()
)
print(response)
class Commands:
""" manage google calendars in service account """
"""manage google calendars in service account"""
def __init__(self, config: str = 'config.yml'):
def __init__(self, config: str = "config.yml"):
"""
Args:
config(str): config filename
"""
self._config: Optional[Dict[str, Any]] = load_config(config)
if self._config is not None and 'logging' in self._config:
logging.config.dictConfig(self._config['logging'])
if self._config is not None and "logging" in self._config:
logging.config.dictConfig(self._config["logging"])
self._service = GoogleCalendarService.from_config(self._config)
self.property = PropertyCommands(self._service)
def list(self, show_hidden: bool = False, show_deleted: bool = False) -> None:
""" list calendars
"""list calendars
Args:
show_hidden: show hidden calendars
show_deleted: show deleted calendars
"""
fields: str = 'nextPageToken,items(id,summary)'
fields: str = "nextPageToken,items(id,summary)"
calendars: List[Dict[str, Any]] = []
page_token: Optional[str] = None
while True:
calendars_api = self._service.calendarList()
response = calendars_api.list(fields=fields,
pageToken=page_token,
showHidden=show_hidden,
showDeleted=show_deleted
).execute()
if 'items' in response:
calendars.extend(response['items'])
page_token = response.get('nextPageToken')
response = calendars_api.list(
fields=fields,
pageToken=page_token,
showHidden=show_hidden,
showDeleted=show_deleted,
).execute()
if "items" in response:
calendars.extend(response["items"])
page_token = response.get("nextPageToken")
if page_token is None:
break
for calendar in calendars:
print('{summary}: {id}'.format_map(calendar))
print("{summary}: {id}".format_map(calendar))
def create(self, summary: str, timezone: Optional[str] = None, public: bool = False) -> None:
""" create calendar
def create(
self, summary: str, timezone: Optional[str] = None, public: bool = False
) -> None:
"""create calendar
Args:
summary: new calendar summary
@@ -101,10 +111,10 @@ class Commands:
calendar.create(summary, timezone)
if public:
calendar.make_public()
print('{}: {}'.format(summary, calendar.calendar_id))
print("{}: {}".format(summary, calendar.calendar_id))
def add_owner(self, calendar_id: str, email: str) -> None:
""" add owner to calendar
"""add owner to calendar
Args:
calendar_id: calendar id
@@ -112,33 +122,33 @@ class Commands:
"""
calendar = GoogleCalendar(self._service, calendar_id)
calendar.add_owner(email)
print('to {} added owner: {}'.format(calendar_id, email))
print("to {} added owner: {}".format(calendar_id, email))
def remove(self, calendar_id: str) -> None:
""" remove calendar
"""remove calendar
Args:
calendar_id: calendar id
"""
calendar = GoogleCalendar(self._service, calendar_id)
calendar.delete()
print('removed: {}'.format(calendar_id))
print("removed: {}".format(calendar_id))
def rename(self, calendar_id: str, summary: str) -> None:
""" rename calendar
"""rename calendar
Args:
calendar_id: calendar id
summary:
"""
calendar = {'summary': summary}
calendar = {"summary": summary}
self._service.calendars().patch(body=calendar, calendarId=calendar_id).execute()
print('{}: {}'.format(summary, calendar_id))
print("{}: {}".format(summary, calendar_id))
def main():
fire.Fire(Commands, name='manage-ics2gcal')
fire.Fire(Commands, name="manage-ics2gcal")
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -11,10 +11,9 @@ from .ical import CalendarConverter, DateDateTime
class CalendarSync:
"""class for synchronize calendar with Google
"""
"""class for synchronize calendar with Google"""
logger = logging.getLogger('CalendarSync')
logger = logging.getLogger("CalendarSync")
def __init__(self, gcalendar: GoogleCalendar, converter: CalendarConverter):
self.gcalendar: GoogleCalendar = gcalendar
@@ -24,11 +23,10 @@ class CalendarSync:
self.to_delete: EventList = []
@staticmethod
def _events_list_compare(items_src: EventList,
items_dst: EventList,
key: str = 'iCalUID') \
-> Tuple[EventList, List[EventTuple], EventList]:
""" compare list of events by key
def _events_list_compare(
items_src: EventList, items_dst: EventList, key: str = "iCalUID"
) -> Tuple[EventList, List[EventTuple], EventList]:
"""compare list of events by key
Arguments:
items_src {list of dict} -- source events
@@ -41,7 +39,8 @@ class CalendarSync:
items_to_delete)
"""
def get_key(item: EventData) -> str: return item[key]
def get_key(item: EventData) -> str:
return item[key]
keys_src: Set[str] = set(map(get_key, items_src))
keys_dst: Set[str] = set(map(get_key, items_dst))
@@ -50,9 +49,7 @@ class CalendarSync:
keys_to_update = keys_src & keys_dst
keys_to_delete = keys_dst - keys_src
def items_by_keys(items: EventList,
key_name: str,
keys: Set[str]) -> EventList:
def items_by_keys(items: EventList, key_name: str, keys: Set[str]) -> EventList:
return list(filter(lambda item: item[key_name] in keys, items))
items_to_insert = items_by_keys(items_src, key, keys_to_insert)
@@ -67,25 +64,25 @@ class CalendarSync:
return items_to_insert, items_to_update, items_to_delete
def _filter_events_to_update(self):
""" filter 'to_update' events by 'updated' datetime
"""
"""filter 'to_update' events by 'updated' datetime"""
def filter_updated(event_tuple: EventTuple) -> bool:
new, old = event_tuple
if 'updated' not in new or 'updated' not in old:
if "updated" not in new or "updated" not in old:
return True
new_date = dateutil.parser.parse(new['updated'])
old_date = dateutil.parser.parse(old['updated'])
new_date = dateutil.parser.parse(new["updated"])
old_date = dateutil.parser.parse(old["updated"])
return new_date > old_date
self.to_update = list(filter(filter_updated, self.to_update))
@staticmethod
def _filter_events_by_date(events: EventList,
date: DateDateTime,
op: Callable[[DateDateTime,
DateDateTime], bool]) -> EventList:
""" filter events by start datetime
def _filter_events_by_date(
events: EventList,
date: DateDateTime,
op: Callable[[DateDateTime, DateDateTime], bool],
) -> EventList:
"""filter events by start datetime
Arguments:
events -- events list
@@ -98,21 +95,22 @@ class CalendarSync:
def filter_by_date(event: EventData) -> bool:
date_cmp = date
event_start: Dict[str, str] = event['start']
event_start: Dict[str, str] = event["start"]
event_date: Union[DateDateTime, str, None] = None
compare_dates = False
if 'date' in event_start:
event_date = event_start['date']
if "date" in event_start:
event_date = event_start["date"]
compare_dates = True
elif 'dateTime' in event_start:
event_date = event_start['dateTime']
elif "dateTime" in event_start:
event_date = event_start["dateTime"]
event_date = dateutil.parser.parse(event_date)
if compare_dates:
date_cmp = datetime.date(date.year, date.month, date.day)
event_date = datetime.date(
event_date.year, event_date.month, event_date.day)
event_date.year, event_date.month, event_date.day
)
return op(event_date, date_cmp)
@@ -149,44 +147,47 @@ class CalendarSync:
# divide source events by start datetime
events_src_pending = CalendarSync._filter_events_by_date(
events_src, start_date, operator.ge)
events_src, start_date, operator.ge
)
events_src_past = CalendarSync._filter_events_by_date(
events_src, start_date, operator.lt)
events_src, start_date, operator.lt
)
# first events comparison
self.to_insert, self.to_update, self.to_delete = CalendarSync._events_list_compare(
events_src_pending, events_dst)
(
self.to_insert,
self.to_update,
self.to_delete,
) = CalendarSync._events_list_compare(events_src_pending, events_dst)
# 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)
events_src_past, self.to_delete
)
self.to_update.extend(add_to_update)
# find if events 'to_insert' exists in gcalendar, for update them
add_to_update, self.to_insert = self.gcalendar.find_exists(
self.to_insert)
add_to_update, self.to_insert = self.gcalendar.find_exists(self.to_insert)
self.to_update.extend(add_to_update)
# 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 )',
"prepared to sync: ( insert: %d, update: %d, delete: %d )",
len(self.to_insert),
len(self.to_update),
len(self.to_delete)
len(self.to_delete),
)
def clear(self) -> None:
""" clear prepared sync lists (insert, update, delete)
"""
"""clear prepared sync lists (insert, update, delete)"""
self.to_insert.clear()
self.to_update.clear()
self.to_delete.clear()
def apply(self) -> None:
""" apply sync (insert, update, delete), using prepared lists of events
"""
"""apply sync (insert, update, delete), using prepared lists of events"""
self.gcalendar.insert_events(self.to_insert)
self.gcalendar.update_events(self.to_update)
@@ -194,4 +195,4 @@ class CalendarSync:
self.clear()
self.logger.info('sync done')
self.logger.info("sync done")

View File

@@ -6,18 +6,13 @@ import dateutil.parser
import datetime
import logging
import logging.config
from . import (
CalendarConverter,
GoogleCalendarService,
GoogleCalendar,
CalendarSync
)
from . import CalendarConverter, GoogleCalendarService, GoogleCalendar, CalendarSync
ConfigDate = Union[str, datetime.datetime]
def load_config() -> Dict[str, Any]:
with open('config.yml', 'r', encoding='utf-8') as f:
with open("config.yml", "r", encoding="utf-8") as f:
result = yaml.safe_load(f)
return result
@@ -25,7 +20,7 @@ def load_config() -> Dict[str, Any]:
def get_start_date(date: ConfigDate) -> datetime.datetime:
if isinstance(date, datetime.datetime):
return date
if 'now' == date:
if "now" == date:
result = datetime.datetime.utcnow()
else:
result = dateutil.parser.parse(date)
@@ -35,13 +30,13 @@ def get_start_date(date: ConfigDate) -> datetime.datetime:
def main():
config = load_config()
if 'logging' in config:
logging.config.dictConfig(config['logging'])
if "logging" in config:
logging.config.dictConfig(config["logging"])
calendar_id: str = config['calendar']['google_id']
ics_filepath: str = config['calendar']['source']
calendar_id: str = config["calendar"]["google_id"]
ics_filepath: str = config["calendar"]["source"]
start = get_start_date(config['start_from'])
start = get_start_date(config["start_from"])
converter = CalendarConverter()
converter.load(ics_filepath)
@@ -54,5 +49,5 @@ def main():
sync.apply()
if __name__ == '__main__':
if __name__ == "__main__":
main()