mirror of
https://github.com/b4tman/sync_ics2gcal
synced 2024-12-04 17:56:54 +00:00
Merge branch 'converter/pydantic' of https://github.com/b4tman/sync_ics2gcal into converter/pydantic
This commit is contained in:
commit
d2d43d02da
74
poetry.lock
generated
74
poetry.lock
generated
@ -20,6 +20,18 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
|
||||
|
||||
[[package]]
|
||||
name = "autopep8"
|
||||
version = "1.5.7"
|
||||
description = "A tool that automatically formats Python code to conform to the PEP 8 style guide"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
pycodestyle = ">=2.7.0"
|
||||
toml = "*"
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "4.2.4"
|
||||
@ -55,6 +67,14 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "dataclasses"
|
||||
version = "0.8"
|
||||
description = "A backport of the dataclasses module for Python 3.6"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6, <3.7"
|
||||
|
||||
[[package]]
|
||||
name = "fire"
|
||||
version = "0.4.0"
|
||||
@ -293,6 +313,22 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.8.2"
|
||||
description = "Data validation and settings management using python 3.6 type hinting"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
|
||||
[package.dependencies]
|
||||
dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""}
|
||||
typing-extensions = ">=3.7.4.3"
|
||||
|
||||
[package.extras]
|
||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyflakes"
|
||||
version = "2.3.1"
|
||||
@ -415,7 +451,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
name = "typing-extensions"
|
||||
version = "3.10.0.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.5+"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
@ -454,8 +490,8 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.6"
|
||||
content-hash = "712c4284d2a0349b959dad1bfec496daa5020135d340ef3859b641d567276b33"
|
||||
python-versions = ">=3.6.1"
|
||||
content-hash = "9145569d3597a35a93b05b114ac7bcdba29e1b1fb215793b427b0e40fbe52bef"
|
||||
|
||||
[metadata.files]
|
||||
atomicwrites = [
|
||||
@ -466,6 +502,10 @@ attrs = [
|
||||
{file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
|
||||
{file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
|
||||
]
|
||||
autopep8 = [
|
||||
{file = "autopep8-1.5.7-py2.py3-none-any.whl", hash = "sha256:aa213493c30dcdac99537249ee65b24af0b2c29f2e83cd8b3f68760441ed0db9"},
|
||||
{file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"},
|
||||
]
|
||||
cachetools = [
|
||||
{file = "cachetools-4.2.4-py3-none-any.whl", hash = "sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1"},
|
||||
{file = "cachetools-4.2.4.tar.gz", hash = "sha256:89ea6f1b638d5a73a4f9226be57ac5e4f399d22770b92355f92dcb0f7f001693"},
|
||||
@ -482,6 +522,10 @@ colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||
]
|
||||
dataclasses = [
|
||||
{file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
|
||||
{file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
|
||||
]
|
||||
fire = [
|
||||
{file = "fire-0.4.0.tar.gz", hash = "sha256:c5e2b8763699d1142393a46d0e3e790c5eb2f0706082df8f647878842c216a62"},
|
||||
]
|
||||
@ -602,6 +646,30 @@ pycodestyle = [
|
||||
{file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
|
||||
{file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
|
||||
]
|
||||
pydantic = [
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"},
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"},
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"},
|
||||
{file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
|
||||
{file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
|
||||
]
|
||||
pyflakes = [
|
||||
{file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
|
||||
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
|
||||
|
@ -19,17 +19,19 @@ classifiers = [
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.6"
|
||||
python = ">=3.6.1"
|
||||
google-auth = "2.2.1"
|
||||
google-api-python-client = "2.23.0"
|
||||
icalendar = "4.0.7"
|
||||
pytz = "2021.1"
|
||||
PyYAML = "5.4.1"
|
||||
fire = "0.4.0"
|
||||
pydantic = "^1.8.2"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^6.2.5"
|
||||
flake8 = "^3.9.2"
|
||||
autopep8 = "^1.5.7"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
sync-ics2gcal = "sync_ics2gcal.sync_calendar:main"
|
||||
|
@ -5,11 +5,56 @@ from typing import Union, Dict, Callable, Optional
|
||||
from icalendar import Calendar, Event
|
||||
from pytz import utc
|
||||
|
||||
import pydantic
|
||||
|
||||
from .gcal import EventData, EventList
|
||||
|
||||
DateDateTime = Union[datetime.date, datetime.datetime]
|
||||
|
||||
|
||||
class GCal_DateDateTime(pydantic.BaseModel):
|
||||
date: Optional[str] = pydantic.Field(default=None)
|
||||
date_time: Optional[str] = pydantic.Field(alias='dateTime', default=None)
|
||||
timezone: Optional[str] = pydantic.Field(alias='timeZone', default=None)
|
||||
|
||||
@pydantic.root_validator(allow_reuse=True)
|
||||
def check_only_date_or_datetime(cls, values):
|
||||
date = values.get('date', None)
|
||||
date_time = values.get('date_time', None)
|
||||
assert (date is None) != (date_time is None), \
|
||||
'only date or date_time must be provided'
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
def create_from(cls, value: DateDateTime) -> 'GCal_DateDateTime':
|
||||
key: str = 'date'
|
||||
str_value: str = ''
|
||||
if type(value) is datetime.datetime:
|
||||
key = 'date_time'
|
||||
str_value = format_datetime_utc(value)
|
||||
else:
|
||||
str_value = value.isoformat()
|
||||
return cls(**{key: str_value})
|
||||
|
||||
class Config:
|
||||
allow_population_by_field_name = True
|
||||
|
||||
|
||||
class GCal_Event(pydantic.BaseModel):
|
||||
created: Optional[str] = None
|
||||
updated: Optional[str] = None
|
||||
summary: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
start: GCal_DateDateTime
|
||||
end: GCal_DateDateTime
|
||||
transparency: Optional[str] = None
|
||||
ical_uid: str = pydantic.Field(alias='iCalUID')
|
||||
|
||||
class Config:
|
||||
allow_population_by_field_name = True
|
||||
|
||||
|
||||
def format_datetime_utc(value: DateDateTime) -> str:
|
||||
"""utc datetime as string from date or datetime value
|
||||
|
||||
@ -19,7 +64,7 @@ def format_datetime_utc(value: DateDateTime) -> str:
|
||||
Returns:
|
||||
utc datetime value as string in iso format
|
||||
"""
|
||||
if not isinstance(value, datetime.datetime):
|
||||
if type(value) is datetime.date:
|
||||
value = datetime.datetime(
|
||||
value.year, value.month, value.day, tzinfo=utc)
|
||||
value = value.replace(microsecond=1)
|
||||
@ -85,7 +130,7 @@ class EventConverter(Event):
|
||||
|
||||
return format_datetime_utc(self.decoded(prop))
|
||||
|
||||
def _gcal_start(self) -> Dict[str, str]:
|
||||
def _gcal_start(self) -> GCal_DateDateTime:
|
||||
""" event start dict from icalendar event
|
||||
|
||||
Raises:
|
||||
@ -96,9 +141,9 @@ class EventConverter(Event):
|
||||
"""
|
||||
|
||||
value = self.decoded('DTSTART')
|
||||
return gcal_date_or_dateTime(value)
|
||||
return GCal_DateDateTime.create_from(value)
|
||||
|
||||
def _gcal_end(self) -> Dict[str, str]:
|
||||
def _gcal_end(self) -> GCal_DateDateTime:
|
||||
"""event end dict from icalendar event
|
||||
|
||||
Raises:
|
||||
@ -110,13 +155,17 @@ class EventConverter(Event):
|
||||
result = None
|
||||
if 'DTEND' in self:
|
||||
value = self.decoded('DTEND')
|
||||
result = gcal_date_or_dateTime(value)
|
||||
result = GCal_DateDateTime.create_from(value)
|
||||
elif 'DURATION' in self:
|
||||
start_val = self.decoded('DTSTART')
|
||||
duration = self.decoded('DURATION')
|
||||
end_val = start_val + duration
|
||||
if type(start_val) is datetime.date:
|
||||
if type(end_val) is datetime.datetime:
|
||||
end_val = datetime.date(
|
||||
end_val.year, end_val.month, end_val.day)
|
||||
|
||||
result = gcal_date_or_dateTime(end_val, check_value=start_val)
|
||||
result = GCal_DateDateTime.create_from(end_val)
|
||||
else:
|
||||
raise ValueError('no DTEND or DURATION')
|
||||
return result
|
||||
@ -138,6 +187,18 @@ class EventConverter(Event):
|
||||
if ics_prop in self:
|
||||
gcal_event[prop] = func(ics_prop)
|
||||
|
||||
def _get_prop(self, prop: str, func: Callable[[str], str]):
|
||||
"""get property from ical event if exist else None
|
||||
|
||||
Arguments:
|
||||
prop -- property name
|
||||
func -- function to convert
|
||||
"""
|
||||
|
||||
if prop not in self:
|
||||
return None
|
||||
return func(prop)
|
||||
|
||||
def to_gcal(self) -> EventData:
|
||||
"""Convert
|
||||
|
||||
@ -145,24 +206,19 @@ class EventConverter(Event):
|
||||
dict - google calendar#event resource
|
||||
"""
|
||||
|
||||
event = {
|
||||
'iCalUID': self._str_prop('UID'),
|
||||
kwargs = {
|
||||
'ical_uid': self._str_prop('UID'),
|
||||
'start': self._gcal_start(),
|
||||
'end': self._gcal_end()
|
||||
'end': self._gcal_end(),
|
||||
'summary': self._get_prop('SUMMARY', self._str_prop),
|
||||
'description': self._get_prop('DESCRIPTION', self._str_prop),
|
||||
'location': self._get_prop('LOCATION', self._str_prop),
|
||||
'created': self._get_prop('CREATED', self._datetime_str_prop),
|
||||
'updated': self._get_prop('LAST-MODIFIED', self._datetime_str_prop),
|
||||
'transparency': self._get_prop('TRANSP', lambda prop: self._str_prop(prop).lower()),
|
||||
}
|
||||
|
||||
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,
|
||||
'transparency',
|
||||
lambda prop: self._str_prop(prop).lower(), 'TRANSP')
|
||||
|
||||
return event
|
||||
return GCal_Event(**kwargs).dict(by_alias=True, exclude_defaults=True)
|
||||
|
||||
|
||||
class CalendarConverter:
|
||||
|
Loading…
Reference in New Issue
Block a user