1
0
mirror of https://github.com/b4tman/sync_ics2gcal synced 2026-02-04 15:25:04 +00:00

24 Commits

Author SHA1 Message Date
d2d43d02da Merge branch 'converter/pydantic' of https://github.com/b4tman/sync_ics2gcal into converter/pydantic 2021-10-21 09:58:14 +03:00
2e114db5c9 + bench_converter 2021-10-21 09:57:17 +03:00
2ab96d1b00 travis ci: dist = focal 2021-10-21 09:52:27 +03:00
15951ba200 try pydantic for converter
---
x2 slower:
before: best: _82001700 ns,     avg: _85379966.2 ns,    median: _84408700.0 ns
after__: best: 162860900 ns,     avg: 175015097.0 ns,    median: 171212750.0 ns
2021-10-17 17:10:44 +03:00
d03e5691ee add pydantic 2021-10-17 17:07:36 +03:00
3686bc29ee + bench_converter 2021-10-15 14:53:20 +03:00
9603718c83 remove install from requirements.txt 2021-10-15 09:37:57 +03:00
f7a84cc58c use POETRY_PYPI_TOKEN_PYPI to publish
https://python-poetry.org/docs/repositories/
2021-10-15 09:34:07 +03:00
782ab05126 Merge branch 'feature/poetry' into develop 2021-10-09 16:02:59 +03:00
d08671e722 update README 2021-10-09 15:54:31 +03:00
df2f3ef483 fix CodeQL
remove git checkout HEAD^2
2021-10-09 15:37:58 +03:00
9e40b6fbbd use poetry in ci 2021-10-09 15:27:38 +03:00
b86ef30397 migrate to poetry 2021-10-09 15:09:07 +03:00
dependabot[bot]
2b146bf15d Bump google-auth from 2.0.2 to 2.2.1
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 2.0.2 to 2.2.1.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v2.0.2...v2.2.1)

---
updated-dependencies:
- dependency-name: google-auth
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-01 10:34:26 +03:00
dependabot[bot]
6d4a9ddf30 Bump google-api-python-client from 2.19.0 to 2.23.0
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.19.0 to 2.23.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.19.0...v2.23.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-01 10:23:02 +03:00
dependabot[bot]
217a1ccf39 Bump google-api-python-client from 2.15.0 to 2.19.0
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.15.0 to 2.19.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.15.0...v2.19.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-01 11:59:36 +03:00
dependabot[bot]
1bc2fe1ea2 Bump google-auth from 1.34.0 to 2.0.2
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.34.0 to 2.0.2.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.34.0...v2.0.2)

---
updated-dependencies:
- dependency-name: google-auth
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-01 09:53:28 +03:00
dependabot[bot]
1187aa0f3e Bump google-api-python-client from 2.11.0 to 2.15.0
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.11.0 to 2.15.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.11.0...v2.15.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-01 11:35:57 +03:00
dependabot[bot]
c3312956e2 Bump google-auth from 1.32.1 to 1.34.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.32.1 to 1.34.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.32.1...v1.34.0)

---
updated-dependencies:
- dependency-name: google-auth
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-01 11:22:17 +03:00
dependabot[bot]
8910768b61 Bump google-api-python-client from 2.6.0 to 2.11.0
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.6.0 to 2.11.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.6.0...v2.11.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-01 11:28:46 +03:00
dependabot[bot]
3274b85ca6 Bump google-auth from 1.30.1 to 1.32.1
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.30.1 to 1.32.1.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.30.1...v1.32.1)

---
updated-dependencies:
- dependency-name: google-auth
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-01 10:44:18 +03:00
dependabot[bot]
8d4a21c72d Bump google-api-python-client from 2.3.0 to 2.6.0
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.3.0 to 2.6.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.3.0...v2.6.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-01 13:29:00 +03:00
dependabot[bot]
412c916b15 Bump google-auth from 1.30.0 to 1.30.1
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.30.0 to 1.30.1.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.30.0...v1.30.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-01 09:13:31 +03:00
052ba440d0 type aliases 2021-05-19 12:55:54 +03:00
18 changed files with 1091 additions and 158 deletions

View File

@@ -1 +0,0 @@
ref-names: $Format:%D$

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
.git_archival.txt export-subst

View File

@@ -22,11 +22,6 @@ jobs:
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1

View File

@@ -13,30 +13,34 @@ on:
jobs:
build:
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Upgrade pip
run: python -m pip install --upgrade pip
- name: Load cached Poetry installation
uses: actions/cache@v2
with:
path: ~/.local
key: poetry-0
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install deps
run: poetry install
- name: Lint with flake8
run: |
pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
poetry run flake8 sync_ics2gcal --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
poetry run flake8 sync_ics2gcal --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pip install pytest
pytest -v
run: poetry run pytest -v

View File

@@ -10,17 +10,24 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools setuptools_scm setuptools_scm_git_archive wheel twine
- name: Build and publish
- name: Upgrade pip
run: python -m pip install --upgrade pip
- name: Load cached Poetry installation
uses: actions/cache@v2
with:
path: ~/.local
key: poetry-0
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install deps
run: poetry install
- name: Build
run: poetry build
- name: Publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.pypi_token }}
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.pypi_token }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
poetry publish -n

View File

@@ -1,4 +1,6 @@
language: python
os: linux
dist: focal
python:
- "3.6"
@@ -6,5 +8,14 @@ python:
- "3.8"
- "3.9"
before_install:
- pip install poetry
install:
- poetry install
script:
- pytest -v
# stop the build if there are Python syntax errors or undefined names
- poetry run flake8 sync_ics2gcal --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
- poetry run flake8 sync_ics2gcal --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
# run tests
- poetry run pytest -v

View File

@@ -1,7 +0,0 @@
include pyproject.toml
# Include the README
include *.md
# Include the license file
include LICENSE

View File

@@ -14,10 +14,15 @@ To install from [PyPI](https://pypi.org/project/sync-ics2gcal/) with [pip](https
pip install sync-ics2gcal
```
Or download source code and install:
Or download source code and install using poetry:
```sh
python setup.py install
# install poetry
pip install poetry
# install project and deps to virtualenv
poetry install
# run
poetry run sync-ics2gcal
```
## Configuration

759
poetry.lock generated Normal file
View File

@@ -0,0 +1,759 @@
[[package]]
name = "atomicwrites"
version = "1.4.0"
description = "Atomic file writes."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "attrs"
version = "21.2.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
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"
description = "Extensible memoizing collections and decorators"
category = "main"
optional = false
python-versions = "~=3.5"
[[package]]
name = "certifi"
version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "charset-normalizer"
version = "2.0.6"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.5.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
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"
description = "A library for automatically generating command line interfaces."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
six = "*"
termcolor = "*"
[[package]]
name = "flake8"
version = "3.9.2"
description = "the modular source code checker: pep8 pyflakes and co"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[package.dependencies]
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
mccabe = ">=0.6.0,<0.7.0"
pycodestyle = ">=2.7.0,<2.8.0"
pyflakes = ">=2.3.0,<2.4.0"
[[package]]
name = "google-api-core"
version = "2.1.0"
description = "Google API client core library"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
google-auth = ">=1.25.0,<3.0dev"
googleapis-common-protos = ">=1.6.0,<2.0dev"
protobuf = ">=3.12.0"
requests = ">=2.18.0,<3.0.0dev"
[package.extras]
grpc = ["grpcio (>=1.33.2,<2.0dev)"]
grpcgcp = ["grpcio-gcp (>=0.2.2)"]
grpcio-gcp = ["grpcio-gcp (>=0.2.2)"]
[[package]]
name = "google-api-python-client"
version = "2.23.0"
description = "Google API Client Library for Python"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
google-api-core = ">=1.21.0,<3.0.0dev"
google-auth = ">=1.16.0,<3.0.0dev"
google-auth-httplib2 = ">=0.1.0"
httplib2 = ">=0.15.0,<1dev"
uritemplate = ">=3.0.0,<4dev"
[[package]]
name = "google-auth"
version = "2.2.1"
description = "Google Authentication Library"
category = "main"
optional = false
python-versions = ">= 3.6"
[package.dependencies]
cachetools = ">=2.0.0,<5.0"
pyasn1-modules = ">=0.2.1"
rsa = ">=3.1.4,<5"
[package.extras]
aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"]
pyopenssl = ["pyopenssl (>=20.0.0)"]
reauth = ["pyu2f (>=0.1.5)"]
[[package]]
name = "google-auth-httplib2"
version = "0.1.0"
description = "Google Authentication Library: httplib2 transport"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
google-auth = "*"
httplib2 = ">=0.15.0"
six = "*"
[[package]]
name = "googleapis-common-protos"
version = "1.53.0"
description = "Common protobufs used in Google APIs"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
protobuf = ">=3.12.0"
[package.extras]
grpc = ["grpcio (>=1.0.0)"]
[[package]]
name = "httplib2"
version = "0.20.1"
description = "A comprehensive HTTP client library."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
pyparsing = ">=2.4.2,<3"
[[package]]
name = "icalendar"
version = "4.0.7"
description = "iCalendar parser/generator"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
python-dateutil = "*"
pytz = "*"
[[package]]
name = "idna"
version = "3.2"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
version = "4.8.1"
description = "Read metadata from Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
perf = ["ipython"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
[[package]]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "mccabe"
version = "0.6.1"
description = "McCabe checker, plugin for flake8"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "packaging"
version = "21.0"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2"
[[package]]
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "protobuf"
version = "3.18.1"
description = "Protocol Buffers"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "py"
version = "1.10.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pyasn1"
version = "0.4.8"
description = "ASN.1 types and codecs"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pyasn1-modules"
version = "0.2.8"
description = "A collection of ASN.1-based protocols modules."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
pyasn1 = ">=0.4.6,<0.5.0"
[[package]]
name = "pycodestyle"
version = "2.7.0"
description = "Python style guide checker"
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"
description = "passive checker of Python programs"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pyparsing"
version = "2.4.7"
description = "Python parsing module"
category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "pytest"
version = "6.2.5"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
py = ">=1.8.2"
toml = "*"
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
name = "python-dateutil"
version = "2.8.2"
description = "Extensions to the standard Python datetime module"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
[package.dependencies]
six = ">=1.5"
[[package]]
name = "pytz"
version = "2021.1"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pyyaml"
version = "5.4.1"
description = "YAML parser and emitter for Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[[package]]
name = "requests"
version = "2.26.0"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "rsa"
version = "4.7.2"
description = "Pure-Python RSA implementation"
category = "main"
optional = false
python-versions = ">=3.5, <4"
[package.dependencies]
pyasn1 = ">=0.1.3"
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "termcolor"
version = "1.1.0"
description = "ANSII Color formatting for output in terminal."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "typing-extensions"
version = "3.10.0.2"
description = "Backported and Experimental Type Hints for Python 3.5+"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "uritemplate"
version = "3.0.1"
description = "URI templates"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "urllib3"
version = "1.26.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "zipp"
version = "3.6.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
[metadata]
lock-version = "1.1"
python-versions = ">=3.6.1"
content-hash = "9145569d3597a35a93b05b114ac7bcdba29e1b1fb215793b427b0e40fbe52bef"
[metadata.files]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
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"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"},
{file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"},
]
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"},
]
flake8 = [
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
]
google-api-core = [
{file = "google-api-core-2.1.0.tar.gz", hash = "sha256:5ec27b942b34d04559cbf3674430bb83fc3d74e7d32b8bbd31c4466e71740b83"},
{file = "google_api_core-2.1.0-py2.py3-none-any.whl", hash = "sha256:c344e1aacd8330527c5130bdfe03118d8859ce798bcf0e5d23770ab6873e0615"},
]
google-api-python-client = [
{file = "google-api-python-client-2.23.0.tar.gz", hash = "sha256:f117a595717fc384446f6235019e6a83fc9df821bd9d05dba7ff14aa96c70f52"},
{file = "google_api_python_client-2.23.0-py2.py3-none-any.whl", hash = "sha256:a7b364eff63ca75d827cfb241a0f8567157976e879046c1ff20ddf735bad618e"},
]
google-auth = [
{file = "google-auth-2.2.1.tar.gz", hash = "sha256:6dc8173abd50f25b6e62fc5b42802c96fc7cd9deb9bfeeb10a79f5606225cdf4"},
{file = "google_auth-2.2.1-py2.py3-none-any.whl", hash = "sha256:2a92b485afed5292946b324e91fcbe03db277ee4cb64c998c6cfa66d4af01dee"},
]
google-auth-httplib2 = [
{file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"},
{file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"},
]
googleapis-common-protos = [
{file = "googleapis-common-protos-1.53.0.tar.gz", hash = "sha256:a88ee8903aa0a81f6c3cec2d5cf62d3c8aa67c06439b0496b49048fb1854ebf4"},
{file = "googleapis_common_protos-1.53.0-py2.py3-none-any.whl", hash = "sha256:f6d561ab8fb16b30020b940e2dd01cd80082f4762fa9f3ee670f4419b4b8dbd0"},
]
httplib2 = [
{file = "httplib2-0.20.1-py3-none-any.whl", hash = "sha256:8fa4dbf2fbf839b71f8c7837a831e00fcdc860feca99b8bda58ceae4bc53d185"},
{file = "httplib2-0.20.1.tar.gz", hash = "sha256:0efbcb8bfbfbc11578130d87d8afcc65c2274c6eb446e59fc674e4d7c972d327"},
]
icalendar = [
{file = "icalendar-4.0.7-py2.py3-none-any.whl", hash = "sha256:8c35be16c1d0581a276002af883297aeffa8116e366fdce4d5318e1424aa1903"},
{file = "icalendar-4.0.7.tar.gz", hash = "sha256:0fc18d87f66e0b5da84fa731389496cfe18e4c21304e8f6713556b2e8724a7a4"},
]
idna = [
{file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
{file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
]
importlib-metadata = [
{file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
{file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
packaging = [
{file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
{file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
protobuf = [
{file = "protobuf-3.18.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fa6d1049d5315566f55c04d0b50c0033415144f96a9d25c820dc542fe2bb7f45"},
{file = "protobuf-3.18.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e2790c580070cff2921b93d562539ae027064340151c50db6aaf94c33048cd"},
{file = "protobuf-3.18.1-cp36-cp36m-win32.whl", hash = "sha256:7e2f0677d68ecdd1cfda2abea65873f5bc7c3f5aae199404a3f5c1d1198c1a63"},
{file = "protobuf-3.18.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6f714f5de9d40b3bec90ede4a688cce52f637ccdc5403afcda1f67598f4fdcd7"},
{file = "protobuf-3.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a7be937c319146cc9f2626f0181e6809062c353e1fe449ecd0df374ba1036b2"},
{file = "protobuf-3.18.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:10544fc7ace885a882623083c24da5b14148c77563acddc3c58d66f6153c09cd"},
{file = "protobuf-3.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ee8b11e3eb2ed38f12137c3c132270a0b1dd509e317228ac47b67f21a583f1"},
{file = "protobuf-3.18.1-cp37-cp37m-win32.whl", hash = "sha256:c492c217d3f69f4d2d5619571e52ab98538edbf53caf67e53ea92bd0a3b5670f"},
{file = "protobuf-3.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3c1644f8a7f19b45c7a4c32278e2a55ae9e7e2f9e5f02d960a61f04a4890d3e6"},
{file = "protobuf-3.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9ac691f7b24e4371dcd3980e4f5d6c840a2010da37986203053fee995786ec5"},
{file = "protobuf-3.18.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:93bad12895d8b0ebc66b605c2ef1802311595f881aef032d9f13282b7550e6b2"},
{file = "protobuf-3.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0851b5b89191e1976d34fa2e8eb8659829dfb45252053224cf9df857fb5f6a45"},
{file = "protobuf-3.18.1-cp38-cp38-win32.whl", hash = "sha256:09d9268f6f9da81b7657adcf2fb397524c82f20cdf9e0db3ff4e7567977abd67"},
{file = "protobuf-3.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d6d927774c0ec746fed15a4faff5f44aad0b7a3421fadb6f3ae5ca1f2f8ae26e"},
{file = "protobuf-3.18.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d19c9cb805fd2be1d59eee39e152367ee92a30167e77bd06c8819f8f0009a4c"},
{file = "protobuf-3.18.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:387f621bf7295a331f8c8a6962d097ceddeb85356792888cfa6a5c6bfc6886a4"},
{file = "protobuf-3.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1c5d3966c856f60a9d8d62f4455d70c31026422acdd5c228edf22b65b16c38"},
{file = "protobuf-3.18.1-cp39-cp39-win32.whl", hash = "sha256:f20f803892f2135e8b96dc58c9a0c6a7ad8436794bf8784af229498d939b4c77"},
{file = "protobuf-3.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:d76201380f41a2d83fb613a4683059d1fcafbe969518b3e409e279a8788fde2f"},
{file = "protobuf-3.18.1-py2.py3-none-any.whl", hash = "sha256:61ca58e14033ca0dfa484a31d57237c1be3b6013454c7f53876a20fc88dd69b1"},
{file = "protobuf-3.18.1.tar.gz", hash = "sha256:1c9bb40503751087300dd12ce2e90899d68628977905c76effc48e66d089391e"},
]
py = [
{file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
{file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
]
pyasn1 = [
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
{file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
{file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
{file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
{file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
{file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
{file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
{file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
{file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
{file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
]
pyasn1-modules = [
{file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"},
{file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"},
{file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"},
{file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"},
{file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"},
{file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"},
{file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"},
{file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"},
{file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"},
{file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"},
{file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"},
{file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"},
{file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"},
]
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"},
]
pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
pytest = [
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
{file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
]
python-dateutil = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
]
pytz = [
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
]
pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
{file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
{file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
{file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
{file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
{file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
{file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
{file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
{file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
{file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
{file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
{file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
{file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
{file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
{file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
]
requests = [
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
]
rsa = [
{file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"},
{file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
termcolor = [
{file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
typing-extensions = [
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
{file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
]
uritemplate = [
{file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"},
{file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"},
]
urllib3 = [
{file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
{file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
]
zipp = [
{file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
{file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
]

View File

@@ -1,3 +1,42 @@
[tool.poetry]
name = "sync_ics2gcal"
version = "0.1.3"
description = "Sync ics file with Google calendar"
authors = ["Dmitry Belyaev <b4tm4n@mail.ru>"]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/b4tman/sync_ics2gcal"
repository = "https://github.com/b4tman/sync_ics2gcal"
keywords = ["icalendar", "sync", "google", "calendar"]
classifiers = [
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
]
[tool.poetry.dependencies]
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"
manage-ics2gcal = "sync_ics2gcal.manage_calendars:main"
[build-system]
requires = ["setuptools>=40.8.0", "wheel", "setuptools_scm"]
build-backend = "setuptools.build_meta"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,5 +1,5 @@
google-auth==1.30.0
google-api-python-client==2.3.0
google-auth==2.2.1
google-api-python-client==2.23.0
icalendar==4.0.7
pytz==2021.1
PyYAML==5.4.1

View File

@@ -1,7 +0,0 @@
[metadata]
license_files = LICENSE
[options]
setup_requires =
setuptools_scm
setuptools_scm_git_archive

View File

@@ -1,45 +0,0 @@
import setuptools
with open('README.md', 'r') as fh:
long_description = fh.read()
setuptools.setup(
name='sync-ics2gcal',
author='Dmitry Belyaev',
author_email='b4tm4n@mail.ru',
license='MIT',
description='Sync ics file with Google calendar',
long_description=long_description,
long_description_content_type='text/markdown',
url='https://github.com/b4tman/sync_ics2gcal',
use_scm_version={
'fallback_version': '0.1',
'local_scheme': 'no-local-version'
},
setup_requires=['setuptools_scm', 'setuptools_scm_git_archive'],
packages=setuptools.find_packages(exclude=['tests']),
classifiers=[
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
python_requires='>=3.6',
install_requires=[
'google-auth>=1.5.0',
'google-api-python-client>=1.7.0',
'icalendar>=4.0.1',
'pytz',
'PyYAML>=3.13',
'fire>=0.4.0'
],
entry_points={
"console_scripts": [
"sync-ics2gcal = sync_ics2gcal.sync_calendar:main",
"manage-ics2gcal = sync_ics2gcal.manage_calendars:main",
]
}
)

View File

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

View File

@@ -1,11 +1,15 @@
import logging
from datetime import datetime
from typing import List, Dict, Any, Callable, Tuple, Optional, Union
import google.auth
from google.oauth2 import service_account
from googleapiclient import discovery
from pytz import utc
from datetime import datetime
from typing import List, Dict, Any, Callable, Tuple, Optional
EventData = Dict[str, Union[str, 'EventData', None]]
EventList = List[EventData]
EventTuple = Tuple[EventData, EventData]
class GoogleCalendarService:
@@ -62,7 +66,7 @@ class GoogleCalendarService:
return service
def select_event_key(event: Dict[str, Any]) -> Optional[str]:
def select_event_key(event: EventData) -> Optional[str]:
"""select event key for logging
Arguments:
@@ -90,7 +94,7 @@ class GoogleCalendar:
self.service: discovery.Resource = service
self.calendarId: str = calendarId
def _make_request_callback(self, action: str, events_by_req: List[Dict[str, Any]]) -> Callable:
def _make_request_callback(self, action: str, events_by_req: EventList) -> Callable:
"""make callback for log result of batch request
Arguments:
@@ -117,9 +121,10 @@ class GoogleCalendar:
key = resp_key
self.logger.info('event %s ok, %s: %s',
action, key, event.get(key))
return callback
def list_events_from(self, start: datetime) -> List[Dict[str, Any]]:
def list_events_from(self, start: datetime) -> EventList:
""" list events from calendar, where start date >= start
"""
fields = 'nextPageToken,items(id,iCalUID,updated)'
@@ -141,7 +146,7 @@ class GoogleCalendar:
self.logger.info('%d events listed', len(events))
return events
def find_exists(self, events: List) -> Tuple[List[Tuple[Dict[str, Any], Dict[str, Any]]], List[Dict[str, Any]]]:
def find_exists(self, events: List) -> Tuple[List[EventTuple], EventList]:
""" find existing events from list, by 'iCalUID' field
Arguments:
@@ -189,7 +194,7 @@ class GoogleCalendar:
len(exists), len(not_found))
return exists, not_found
def insert_events(self, events: List[Dict[str, Any]]):
def insert_events(self, events: EventList):
""" insert list of events
Arguments:
@@ -211,7 +216,7 @@ class GoogleCalendar:
i += 1
batch.execute()
def patch_events(self, event_tuples: List[Tuple[Dict[str, Any], Dict[str, Any]]]):
def patch_events(self, event_tuples: List[EventTuple]):
""" patch (update) events
Arguments:
@@ -234,7 +239,7 @@ class GoogleCalendar:
i += 1
batch.execute()
def update_events(self, event_tuples: List[Tuple[Dict[str, Any], Dict[str, Any]]]):
def update_events(self, event_tuples: List[EventTuple]):
""" update events
Arguments:
@@ -257,7 +262,7 @@ class GoogleCalendar:
i += 1
batch.execute()
def delete_events(self, events: List[Dict[str, Any]]):
def delete_events(self, events: EventList):
""" delete events
Arguments:

View File

@@ -1,12 +1,61 @@
import datetime
import logging
from typing import Union, Dict, Any, Callable, Optional, List
from typing import Union, Dict, Callable, Optional
from icalendar import Calendar, Event
from pytz import utc
import pydantic
def format_datetime_utc(value: Union[datetime.date, datetime.datetime]) -> str:
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
Arguments:
@@ -15,7 +64,7 @@ def format_datetime_utc(value: Union[datetime.date, datetime.datetime]) -> 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)
@@ -25,8 +74,8 @@ def format_datetime_utc(value: Union[datetime.date, datetime.datetime]) -> str:
).replace(tzinfo=None).isoformat() + 'Z'
def gcal_date_or_dateTime(value: Union[datetime.date, datetime.datetime],
check_value: Union[datetime.date, datetime.datetime, None] = None)\
def gcal_date_or_dateTime(value: DateDateTime,
check_value: Optional[DateDateTime] = None) \
-> Dict[str, str]:
"""date or dateTime to gcal (start or end dict)
@@ -81,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:
@@ -92,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:
@@ -106,18 +155,22 @@ 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
def _put_to_gcal(self, gcal_event: Dict[str, Any],
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 exist, and put to gcal event
@@ -134,31 +187,38 @@ class EventConverter(Event):
if ics_prop in self:
gcal_event[prop] = func(ics_prop)
def to_gcal(self) -> Dict[str, Any]:
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
Returns:
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:
@@ -182,7 +242,7 @@ class CalendarConverter:
"""
self.calendar = Calendar.from_ical(string)
def events_to_gcal(self) -> List[Dict[str, Any]]:
def events_to_gcal(self) -> EventList:
"""Convert events to google calendar resources
"""

View File

@@ -1,13 +1,13 @@
import datetime
import logging
import operator
from typing import List, Any, Dict, Set, Tuple, Union, Callable
from typing import List, Dict, Set, Tuple, Union, Callable
import dateutil.parser
from pytz import utc
from .gcal import GoogleCalendar
from .ical import CalendarConverter
from .gcal import GoogleCalendar, EventData, EventList, EventTuple
from .ical import CalendarConverter, DateDateTime
class CalendarSync:
@@ -19,15 +19,15 @@ class CalendarSync:
def __init__(self, gcalendar: GoogleCalendar, converter: CalendarConverter):
self.gcalendar: GoogleCalendar = gcalendar
self.converter: CalendarConverter = converter
self.to_insert: List[Dict[str, Any]] = []
self.to_update: List[Tuple[Dict[str, Any], Dict[str, Any]]] = []
self.to_delete: List[Dict[str, Any]] = []
self.to_insert: EventList = []
self.to_update: List[EventTuple] = []
self.to_delete: EventList = []
@staticmethod
def _events_list_compare(items_src: List[Dict[str, Any]],
items_dst: List[Dict[str, Any]],
def _events_list_compare(items_src: EventList,
items_dst: EventList,
key: str = 'iCalUID') \
-> Tuple[List[Dict[str, Any]], List[Tuple[Dict[str, Any], Dict[str, Any]]], List[Dict[str, Any]]]:
-> Tuple[EventList, List[EventTuple], EventList]:
""" compare list of events by key
Arguments:
@@ -41,7 +41,7 @@ class CalendarSync:
items_to_delete)
"""
def get_key(item: Dict[str, Any]) -> 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 +50,9 @@ class CalendarSync:
keys_to_update = keys_src & keys_dst
keys_to_delete = keys_dst - keys_src
def items_by_keys(items: List[Dict[str, Any]],
def items_by_keys(items: EventList,
key_name: str,
keys: Set[str]) -> List[Dict[str, Any]]:
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)
@@ -70,7 +70,7 @@ class CalendarSync:
""" filter 'to_update' events by 'updated' datetime
"""
def filter_updated(event_tuple: Tuple[Dict[str, Any], Dict[str, Any]]) -> bool:
def filter_updated(event_tuple: EventTuple) -> bool:
new, old = event_tuple
new_date = dateutil.parser.parse(new['updated'])
old_date = dateutil.parser.parse(old['updated'])
@@ -79,10 +79,10 @@ class CalendarSync:
self.to_update = list(filter(filter_updated, self.to_update))
@staticmethod
def _filter_events_by_date(events: List[Dict[str, Any]],
date: Union[datetime.date, datetime.datetime],
op: Callable[[Union[datetime.date, datetime.datetime],
Union[datetime.date, datetime.datetime]], bool]) -> List[Dict[str, Any]]:
def _filter_events_by_date(events: EventList,
date: DateDateTime,
op: Callable[[DateDateTime,
DateDateTime], bool]) -> EventList:
""" filter events by start datetime
Arguments:
@@ -94,10 +94,10 @@ class CalendarSync:
list of filtred events
"""
def filter_by_date(event: Dict[str, Any]) -> bool:
def filter_by_date(event: EventData) -> bool:
date_cmp = date
event_start: Dict[str, str] = event['start']
event_date: Union[datetime.date, datetime.datetime, str, None] = None
event_date: Union[DateDateTime, str, None] = None
compare_dates = False
if 'date' in event_start:
@@ -117,7 +117,7 @@ class CalendarSync:
return list(filter(filter_by_date, events))
@staticmethod
def _tz_aware_datetime(date: Union[datetime.date, datetime.datetime]) -> datetime.datetime:
def _tz_aware_datetime(date: DateDateTime) -> datetime.datetime:
"""make tz aware datetime from datetime/date (utc if no tzinfo)
Arguments:
@@ -133,7 +133,7 @@ class CalendarSync:
date = date.replace(tzinfo=utc)
return date
def prepare_sync(self, start_date: Union[datetime.date, datetime.datetime]) -> None:
def prepare_sync(self, start_date: DateDateTime) -> None:
"""prepare sync lists by comparsion of events
Arguments:

105
tests/bench_converter.py Normal file
View File

@@ -0,0 +1,105 @@
from typing import Iterable, List, Tuple, Union, Optional
from uuid import uuid4
import datetime
from itertools import islice
from dataclasses import dataclass
import time
import statistics
import functools
from sync_ics2gcal import CalendarConverter
@dataclass
class IcsTestEvent:
uid: str
start_date: Union[datetime.datetime, datetime.date]
end_date: Union[datetime.datetime, datetime.date, None] = None
duration: Optional[datetime.timedelta] = None
created: Union[datetime.datetime, datetime.date, None] = None
updated: Union[datetime.datetime, datetime.date, None] = None
@staticmethod
def _format_datetime(value: Union[datetime.datetime, datetime.date]):
result: str = ''
if isinstance(value, datetime.datetime):
result += f'DATE-TIME:{value.strftime("%Y%m%dT%H%M%SZ")}'
else:
result += f'DATE:{value.strftime("%Y%m%d")}'
return result
def render(self) -> str:
result: str = ''
result += 'BEGIN:VEVENT\r\n'
result += f'UID:{self.uid}\r\n'
result += f'DTSTART;VALUE={IcsTestEvent._format_datetime(self.start_date)}\r\n'
if self.end_date is not None:
result += f'DTEND;VALUE={IcsTestEvent._format_datetime(self.end_date)}\r\n'
else:
result += f'DURATION:P{self.duration.days}D\r\n'
if self.created is not None:
result += f'CREATED:{self.created.strftime("%Y%m%dT%H%M%SZ")}\r\n'
if self.updated is not None:
result += f'LAST-MODIFIED:{self.updated.strftime("%Y%m%dT%H%M%SZ")}\r\n'
result += 'END:VEVENT\r\n'
return result
@dataclass
class IcsTestCalendar:
events: List[IcsTestEvent]
def render(self) -> str:
result: str = ''
result += 'BEGIN:VCALENDAR\r\n'
for event in self.events:
result += event.render()
result += 'END:VCALENDAR\r\n'
return result
def gen_test_calendar(events_count: int) -> IcsTestCalendar:
def gen_events() -> Iterable[IcsTestEvent]:
for i in range(10000000):
uid = f'{uuid4()}@test.com'
start_date = datetime.datetime.now() + datetime.timedelta(hours=i)
end_date = start_date + datetime.timedelta(hours=1)
event: IcsTestEvent = IcsTestEvent(
uid=uid, start_date=start_date, end_date=end_date, created=start_date, updated=start_date)
yield event
events: List[IcsTestEvent] = list(islice(gen_events(), events_count))
result: IcsTestCalendar = IcsTestCalendar(events)
return result
test_calendar: IcsTestCalendar = gen_test_calendar(1000)
ics_test_calendar: str = test_calendar.render()
converter = CalendarConverter()
converter.loads(ics_test_calendar)
def bench(num_iters=1000):
def make_wrapper(func):
@functools.wraps(func)
def wrapper(*args, **kw):
times = []
for _ in range(num_iters):
t0 = time.perf_counter_ns()
result = func(*args, **kw)
t1 = time.perf_counter_ns()
times.append(t1 - t0)
best = min(times)
avg = round(sum(times) / num_iters, 2)
median = statistics.median(times)
print(
f'{func.__name__} x {num_iters} => best: {best} ns, \tavg: {avg} ns, \tmedian: {median} ns')
return result
return wrapper()
return make_wrapper
@bench(num_iters=500)
def events_to_gcal():
converter.events_to_gcal()