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:
parent
4a85424215
commit
dc23acb7d2
@ -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,
|
||||||
|
@ -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))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user