1
0
mirror of https://github.com/b4tman/sync_ics2gcal synced 2025-01-21 07:28:24 +00:00

type annotations in ical, gcal

This commit is contained in:
Dmitry Belyaev 2022-06-03 22:06:17 +03:00
parent 4a85424215
commit dc23acb7d2
Signed by: b4tman
GPG Key ID: 41A00BF15EA7E5F3
2 changed files with 63 additions and 48 deletions

View File

@ -1,15 +1,28 @@
import logging import logging
from datetime import datetime from datetime import datetime
from typing import List, Dict, Any, Callable, Tuple, Optional, Union from typing import List, Dict, Any, Callable, Tuple, Optional, Union, TypedDict, TypeAlias
import google.auth import google.auth
from google.oauth2 import service_account from google.oauth2 import service_account
from googleapiclient import discovery from googleapiclient import discovery
from pytz import utc from pytz import utc
EventData = Dict[str, Union[str, "EventData", None]]
EventList = List[EventData] class EventDate(TypedDict, total=False):
EventTuple = Tuple[EventData, EventData] date: str
timeZone: str
class EventDateTime(TypedDict, total=False):
dateTime: str
timeZone: str
EventDateOrDateTime: TypeAlias = Union[EventDate, EventDateTime]
EventData: TypeAlias = Dict[str, Union[str, EventDateOrDateTime, None]]
EventList: TypeAlias = List[EventData]
EventTuple: TypeAlias = Tuple[EventData, EventData]
class GoogleCalendarService: class GoogleCalendarService:
@ -60,7 +73,8 @@ class GoogleCalendarService:
""" """
if config is not None and "service_account" in config: if config is not None and "service_account" in config:
service = GoogleCalendarService.from_srv_acc_file(config["service_account"]) service_account_filename: str = str(config["service_account"])
service = GoogleCalendarService.from_srv_acc_file(service_account_filename)
else: else:
service = GoogleCalendarService.default() service = GoogleCalendarService.default()
return service return service
@ -91,7 +105,7 @@ class GoogleCalendar:
def __init__(self, service: discovery.Resource, calendar_id: Optional[str]): def __init__(self, service: discovery.Resource, calendar_id: Optional[str]):
self.service: discovery.Resource = service self.service: discovery.Resource = service
self.calendar_id: str = calendar_id self.calendar_id: str = str(calendar_id)
def _make_request_callback(self, action: str, events_by_req: EventList) -> Callable: def _make_request_callback(self, action: str, events_by_req: EventList) -> Callable:
"""make callback for log result of batch request """make callback for log result of batch request
@ -104,9 +118,9 @@ class GoogleCalendar:
callback function callback function
""" """
def callback(request_id, response, exception): def callback(request_id: str, response: Any, exception: Exception):
event = events_by_req[int(request_id)] event: EventData = events_by_req[int(request_id)]
key = select_event_key(event) key: str = str(select_event_key(event))
if exception is not None: if exception is not None:
self.logger.error( self.logger.error(
@ -117,7 +131,7 @@ class GoogleCalendar:
str(exception), str(exception),
) )
else: else:
resp_key = select_event_key(response) resp_key: str = select_event_key(response)
if resp_key is not None: if resp_key is not None:
event = response event = response
key = resp_key key = resp_key
@ -127,10 +141,10 @@ class GoogleCalendar:
def list_events_from(self, start: datetime) -> EventList: def list_events_from(self, start: datetime) -> EventList:
"""list events from calendar, where start date >= start""" """list events from calendar, where start date >= start"""
fields = "nextPageToken,items(id,iCalUID,updated)" fields: str = "nextPageToken,items(id,iCalUID,updated)"
events = [] events: EventList = []
page_token = None page_token: Optional[str] = None
time_min = ( time_min: str = (
utc.normalize(start.astimezone(utc)).replace(tzinfo=None).isoformat() + "Z" utc.normalize(start.astimezone(utc)).replace(tzinfo=None).isoformat() + "Z"
) )
while True: while True:
@ -164,14 +178,14 @@ class GoogleCalendar:
events_exist - list of tuples: (new_event, exists_event) events_exist - list of tuples: (new_event, exists_event)
""" """
fields = "items(id,iCalUID,updated)" fields: str = "items(id,iCalUID,updated)"
events_by_req = [] events_by_req: EventList = []
exists = [] exists: List[EventTuple] = []
not_found = [] not_found: EventList = []
def list_callback(request_id, response, exception): def list_callback(request_id: str, response: Any, exception: Exception):
found = False found: bool = False
cur_event = events_by_req[int(request_id)] cur_event: EventData = events_by_req[int(request_id)]
if exception is None: if exception is None:
found = [] != response["items"] found = [] != response["items"]
else: else:
@ -186,7 +200,7 @@ class GoogleCalendar:
not_found.append(events_by_req[int(request_id)]) not_found.append(events_by_req[int(request_id)])
batch = self.service.new_batch_http_request(callback=list_callback) batch = self.service.new_batch_http_request(callback=list_callback)
i = 0 i: int = 0
for event in events: for event in events:
events_by_req.append(event) events_by_req.append(event)
batch.add( batch.add(
@ -210,12 +224,12 @@ class GoogleCalendar:
events - events list events - events list
""" """
fields = "id" fields: str = "id"
events_by_req = [] events_by_req: EventList = []
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) batch = self.service.new_batch_http_request(callback=insert_callback)
i = 0 i: int = 0
for event in events: for event in events:
events_by_req.append(event) events_by_req.append(event)
batch.add( batch.add(
@ -234,12 +248,12 @@ class GoogleCalendar:
event_tuples -- list of tuples: (new_event, exists_event) event_tuples -- list of tuples: (new_event, exists_event)
""" """
fields = "id" fields: str = "id"
events_by_req = [] events_by_req: EventList = []
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) batch = self.service.new_batch_http_request(callback=patch_callback)
i = 0 i: int = 0
for event_new, event_old in event_tuples: for event_new, event_old in event_tuples:
if "id" not in event_old: if "id" not in event_old:
continue continue
@ -261,12 +275,12 @@ class GoogleCalendar:
event_tuples -- list of tuples: (new_event, exists_event) event_tuples -- list of tuples: (new_event, exists_event)
""" """
fields = "id" fields: str = "id"
events_by_req = [] events_by_req: EventList = []
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) batch = self.service.new_batch_http_request(callback=update_callback)
i = 0 i: int = 0
for event_new, event_old in event_tuples: for event_new, event_old in event_tuples:
if "id" not in event_old: if "id" not in event_old:
continue continue
@ -290,11 +304,11 @@ class GoogleCalendar:
events -- list of events events -- list of events
""" """
events_by_req = [] events_by_req: EventList = []
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) batch = self.service.new_batch_http_request(callback=delete_callback)
i = 0 i: int = 0
for event in events: for event in events:
events_by_req.append(event) events_by_req.append(event)
batch.add( batch.add(
@ -319,7 +333,7 @@ class GoogleCalendar:
calendar Resource calendar Resource
""" """
calendar = {"summary": summary} calendar: Dict[str, str] = {"summary": summary}
if time_zone is not None: if time_zone is not None:
calendar["timeZone"] = time_zone calendar["timeZone"] = time_zone
@ -335,7 +349,7 @@ class GoogleCalendar:
def make_public(self): def make_public(self):
"""make calendar public""" """make calendar public"""
rule_public = { rule_public: Dict[str, Union[str, Dict[str, str]]] = {
"scope": { "scope": {
"type": "default", "type": "default",
}, },
@ -354,7 +368,7 @@ class GoogleCalendar:
email -- email to add email -- email to add
""" """
rule_owner = { rule_owner: Dict[str, Union[str, Dict[str, str]]] = {
"scope": { "scope": {
"type": "user", "type": "user",
"value": email, "value": email,

View File

@ -1,13 +1,13 @@
import datetime import datetime
import logging import logging
from typing import Union, Dict, Callable, Optional from typing import Union, Dict, Callable, Optional, Mapping, TypeAlias, TypedDict
from icalendar import Calendar, Event from icalendar import Calendar, Event
from pytz import utc from pytz import utc
from .gcal import EventData, EventList from .gcal import EventData, EventList, EventDateOrDateTime, EventDateTime, EventDate
DateDateTime = Union[datetime.date, datetime.datetime] DateDateTime: TypeAlias = Union[datetime.date, datetime.datetime]
def format_datetime_utc(value: DateDateTime) -> str: def format_datetime_utc(value: DateDateTime) -> str:
@ -28,7 +28,7 @@ def format_datetime_utc(value: DateDateTime) -> str:
def gcal_date_or_datetime( def gcal_date_or_datetime(
value: DateDateTime, check_value: Optional[DateDateTime] = None value: DateDateTime, check_value: Optional[DateDateTime] = None
) -> Dict[str, str]: ) -> EventDateOrDateTime:
"""date or datetime to gcal (start or end dict) """date or datetime to gcal (start or end dict)
Arguments: Arguments:
@ -42,14 +42,14 @@ def gcal_date_or_datetime(
if check_value is None: if check_value is None:
check_value = value check_value = value
result: Dict[str, str] = {} result: EventDateOrDateTime
if isinstance(check_value, datetime.datetime): if isinstance(check_value, datetime.datetime):
result["dateTime"] = format_datetime_utc(value) result = EventDateTime(dateTime=format_datetime_utc(value))
else: else:
if isinstance(check_value, datetime.date): if isinstance(check_value, datetime.date):
if isinstance(value, datetime.datetime): if isinstance(value, datetime.datetime):
value = datetime.date(value.year, value.month, value.day) value = datetime.date(value.year, value.month, value.day)
result["date"] = value.isoformat() result = EventDate(date=value.isoformat())
return result return result
@ -82,7 +82,7 @@ class EventConverter(Event):
return format_datetime_utc(self.decoded(prop)) return format_datetime_utc(self.decoded(prop))
def _gcal_start(self) -> Dict[str, str]: def _gcal_start(self) -> EventDateOrDateTime:
"""event start dict from icalendar event """event start dict from icalendar event
Raises: Raises:
@ -95,7 +95,7 @@ class EventConverter(Event):
value = self.decoded("DTSTART") value = self.decoded("DTSTART")
return gcal_date_or_datetime(value) return gcal_date_or_datetime(value)
def _gcal_end(self) -> Dict[str, str]: def _gcal_end(self) -> EventDateOrDateTime:
"""event end dict from icalendar event """event end dict from icalendar event
Raises: Raises:
@ -104,7 +104,7 @@ class EventConverter(Event):
dict dict
""" """
result: Dict[str, str] result: EventDateOrDateTime
if "DTEND" in self: if "DTEND" in self:
value = self.decoded("DTEND") value = self.decoded("DTEND")
result = gcal_date_or_datetime(value) result = gcal_date_or_datetime(value)
@ -146,7 +146,7 @@ class EventConverter(Event):
dict - google calendar#event resource dict - google calendar#event resource
""" """
event = { event: EventData = {
"iCalUID": self._str_prop("UID"), "iCalUID": self._str_prop("UID"),
"start": self._gcal_start(), "start": self._gcal_start(),
"end": self._gcal_end(), "end": self._gcal_end(),
@ -185,7 +185,8 @@ class CalendarConverter:
def events_to_gcal(self) -> EventList: 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") calendar: Calendar = self.calendar
ics_events = calendar.walk(name="VEVENT")
self.logger.info("%d events read", len(ics_events)) self.logger.info("%d events read", len(ics_events))
result = list(map(lambda event: EventConverter(event).to_gcal(), ics_events)) result = list(map(lambda event: EventConverter(event).to_gcal(), ics_events))