mirror of
https://github.com/b4tman/sync_ics2gcal
synced 2026-02-04 15:25:04 +00:00
Compare commits
5 Commits
master
...
converter/
| Author | SHA1 | Date | |
|---|---|---|---|
|
d2d43d02da
|
|||
|
2e114db5c9
|
|||
|
15951ba200
|
|||
|
d03e5691ee
|
|||
|
3686bc29ee
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
|
|||||||
9
.github/workflows/pythonpackage.yml
vendored
9
.github/workflows/pythonpackage.yml
vendored
@@ -17,16 +17,21 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
max-parallel: 4
|
max-parallel: 4
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.7', '3.8', '3.9', '3.10']
|
python-version: [3.6, 3.7, 3.8, 3.9]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Upgrade pip
|
- name: Upgrade pip
|
||||||
run: python -m pip install --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
|
- name: Install Poetry
|
||||||
uses: snok/install-poetry@v1
|
uses: snok/install-poetry@v1
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
|
|||||||
7
.github/workflows/pythonpublish.yml
vendored
7
.github/workflows/pythonpublish.yml
vendored
@@ -8,13 +8,18 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Upgrade pip
|
- name: Upgrade pip
|
||||||
run: python -m pip install --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
|
- name: Install Poetry
|
||||||
uses: snok/install-poetry@v1
|
uses: snok/install-poetry@v1
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
|
|||||||
21
.travis.yml
Normal file
21
.travis.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
language: python
|
||||||
|
os: linux
|
||||||
|
dist: focal
|
||||||
|
|
||||||
|
python:
|
||||||
|
- "3.6"
|
||||||
|
- "3.7"
|
||||||
|
- "3.8"
|
||||||
|
- "3.9"
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- pip install poetry
|
||||||
|
install:
|
||||||
|
- poetry install
|
||||||
|
script:
|
||||||
|
# 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
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# sync_ics2gcal
|
# sync_ics2gcal
|
||||||
|
|
||||||
[](https://badge.fury.io/py/sync-ics2gcal)
|
[](https://badge.fury.io/py/sync-ics2gcal)
|
||||||
|
[](https://travis-ci.org/b4tman/sync_ics2gcal)
|
||||||

|

|
||||||
|
|
||||||
Python scripts for sync .ics file with Google calendar
|
Python scripts for sync .ics file with Google calendar
|
||||||
|
|||||||
547
poetry.lock
generated
547
poetry.lock
generated
@@ -8,48 +8,37 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "21.4.0"
|
version = "21.2.0"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Classes Without Boilerplate"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[package.extras]
|
[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", "cloudpickle"]
|
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"]
|
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", "cloudpickle"]
|
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", "cloudpickle"]
|
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "autopep8"
|
||||||
version = "22.3.0"
|
version = "1.5.7"
|
||||||
description = "The uncompromising code formatter."
|
description = "A tool that automatically formats Python code to conform to the PEP 8 style guide"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6.2"
|
python-versions = "*"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
click = ">=8.0.0"
|
pycodestyle = ">=2.7.0"
|
||||||
mypy-extensions = ">=0.4.3"
|
toml = "*"
|
||||||
pathspec = ">=0.9.0"
|
|
||||||
platformdirs = ">=2"
|
|
||||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
|
||||||
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
|
|
||||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
colorama = ["colorama (>=0.4.3)"]
|
|
||||||
d = ["aiohttp (>=3.7.4)"]
|
|
||||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
|
||||||
uvloop = ["uvloop (>=0.15.2)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cachetools"
|
name = "cachetools"
|
||||||
version = "5.0.0"
|
version = "4.2.4"
|
||||||
description = "Extensible memoizing collections and decorators"
|
description = "Extensible memoizing collections and decorators"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "~=3.7"
|
python-versions = "~=3.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
@@ -61,7 +50,7 @@ python-versions = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "charset-normalizer"
|
name = "charset-normalizer"
|
||||||
version = "2.0.12"
|
version = "2.0.6"
|
||||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -70,18 +59,6 @@ python-versions = ">=3.5.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
unicode_backport = ["unicodedata2"]
|
unicode_backport = ["unicodedata2"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "click"
|
|
||||||
version = "8.0.4"
|
|
||||||
description = "Composable command line interface toolkit"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
|
||||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@@ -90,6 +67,14 @@ category = "dev"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
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]]
|
[[package]]
|
||||||
name = "fire"
|
name = "fire"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -104,21 +89,21 @@ termcolor = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flake8"
|
name = "flake8"
|
||||||
version = "4.0.1"
|
version = "3.9.2"
|
||||||
description = "the modular source code checker: pep8 pyflakes and co"
|
description = "the modular source code checker: pep8 pyflakes and co"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""}
|
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
mccabe = ">=0.6.0,<0.7.0"
|
mccabe = ">=0.6.0,<0.7.0"
|
||||||
pycodestyle = ">=2.8.0,<2.9.0"
|
pycodestyle = ">=2.7.0,<2.8.0"
|
||||||
pyflakes = ">=2.4.0,<2.5.0"
|
pyflakes = ">=2.3.0,<2.4.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "google-api-core"
|
name = "google-api-core"
|
||||||
version = "2.5.0"
|
version = "2.1.0"
|
||||||
description = "Google API client core library"
|
description = "Google API client core library"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -126,46 +111,45 @@ python-versions = ">=3.6"
|
|||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
google-auth = ">=1.25.0,<3.0dev"
|
google-auth = ">=1.25.0,<3.0dev"
|
||||||
googleapis-common-protos = ">=1.52.0,<2.0dev"
|
googleapis-common-protos = ">=1.6.0,<2.0dev"
|
||||||
protobuf = ">=3.12.0"
|
protobuf = ">=3.12.0"
|
||||||
requests = ">=2.18.0,<3.0.0dev"
|
requests = ">=2.18.0,<3.0.0dev"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"]
|
grpc = ["grpcio (>=1.33.2,<2.0dev)"]
|
||||||
grpcgcp = ["grpcio-gcp (>=0.2.2)"]
|
grpcgcp = ["grpcio-gcp (>=0.2.2)"]
|
||||||
grpcio-gcp = ["grpcio-gcp (>=0.2.2)"]
|
grpcio-gcp = ["grpcio-gcp (>=0.2.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "google-api-python-client"
|
name = "google-api-python-client"
|
||||||
version = "2.49.0"
|
version = "2.23.0"
|
||||||
description = "Google API Client Library for Python"
|
description = "Google API Client Library for Python"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
google-api-core = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev"
|
google-api-core = ">=1.21.0,<3.0.0dev"
|
||||||
google-auth = ">=1.16.0,<3.0.0dev"
|
google-auth = ">=1.16.0,<3.0.0dev"
|
||||||
google-auth-httplib2 = ">=0.1.0"
|
google-auth-httplib2 = ">=0.1.0"
|
||||||
httplib2 = ">=0.15.0,<1dev"
|
httplib2 = ">=0.15.0,<1dev"
|
||||||
uritemplate = ">=3.0.1,<5"
|
uritemplate = ">=3.0.0,<4dev"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "google-auth"
|
name = "google-auth"
|
||||||
version = "2.6.6"
|
version = "2.2.1"
|
||||||
description = "Google Authentication Library"
|
description = "Google Authentication Library"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
|
python-versions = ">= 3.6"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
cachetools = ">=2.0.0,<6.0"
|
cachetools = ">=2.0.0,<5.0"
|
||||||
pyasn1-modules = ">=0.2.1"
|
pyasn1-modules = ">=0.2.1"
|
||||||
rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""}
|
rsa = ">=3.1.4,<5"
|
||||||
six = ">=1.9.0"
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"]
|
aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"]
|
||||||
pyopenssl = ["pyopenssl (>=20.0.0)"]
|
pyopenssl = ["pyopenssl (>=20.0.0)"]
|
||||||
reauth = ["pyu2f (>=0.1.5)"]
|
reauth = ["pyu2f (>=0.1.5)"]
|
||||||
|
|
||||||
@@ -184,7 +168,7 @@ six = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "googleapis-common-protos"
|
name = "googleapis-common-protos"
|
||||||
version = "1.54.0"
|
version = "1.53.0"
|
||||||
description = "Common protobufs used in Google APIs"
|
description = "Common protobufs used in Google APIs"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -198,18 +182,18 @@ grpc = ["grpcio (>=1.0.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httplib2"
|
name = "httplib2"
|
||||||
version = "0.20.4"
|
version = "0.20.1"
|
||||||
description = "A comprehensive HTTP client library."
|
description = "A comprehensive HTTP client library."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""}
|
pyparsing = ">=2.4.2,<3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icalendar"
|
name = "icalendar"
|
||||||
version = "4.0.9"
|
version = "4.0.7"
|
||||||
description = "iCalendar parser/generator"
|
description = "iCalendar parser/generator"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -221,7 +205,7 @@ pytz = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.3"
|
version = "3.2"
|
||||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -229,7 +213,7 @@ python-versions = ">=3.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "importlib-metadata"
|
name = "importlib-metadata"
|
||||||
version = "4.2.0"
|
version = "4.8.1"
|
||||||
description = "Read metadata from Python packages"
|
description = "Read metadata from Python packages"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -241,7 +225,8 @@ zipp = ">=0.5"
|
|||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
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)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
|
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]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
@@ -259,44 +244,16 @@ category = "dev"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mypy-extensions"
|
|
||||||
version = "0.4.3"
|
|
||||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "21.3"
|
version = "21.0"
|
||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
pyparsing = ">=2.0.2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pathspec"
|
|
||||||
version = "0.9.0"
|
|
||||||
description = "Utility library for gitignore style pattern matching of file paths."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "platformdirs"
|
|
||||||
version = "2.5.1"
|
|
||||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
|
|
||||||
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
@@ -315,7 +272,7 @@ testing = ["pytest", "pytest-benchmark"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf"
|
name = "protobuf"
|
||||||
version = "3.19.4"
|
version = "3.18.1"
|
||||||
description = "Protocol Buffers"
|
description = "Protocol Buffers"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -323,11 +280,11 @@ python-versions = ">=3.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "py"
|
name = "py"
|
||||||
version = "1.11.0"
|
version = "1.10.0"
|
||||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyasn1"
|
name = "pyasn1"
|
||||||
@@ -350,15 +307,31 @@ pyasn1 = ">=0.4.6,<0.5.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycodestyle"
|
name = "pycodestyle"
|
||||||
version = "2.8.0"
|
version = "2.7.0"
|
||||||
description = "Python style guide checker"
|
description = "Python style guide checker"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
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]]
|
[[package]]
|
||||||
name = "pyflakes"
|
name = "pyflakes"
|
||||||
version = "2.4.0"
|
version = "2.3.1"
|
||||||
description = "passive checker of Python programs"
|
description = "passive checker of Python programs"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -366,22 +339,19 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyparsing"
|
name = "pyparsing"
|
||||||
version = "3.0.7"
|
version = "2.4.7"
|
||||||
description = "Python parsing module"
|
description = "Python parsing module"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
diagrams = ["jinja2", "railroad-diagrams"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "7.1.2"
|
version = "6.2.5"
|
||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||||
@@ -392,10 +362,10 @@ iniconfig = "*"
|
|||||||
packaging = "*"
|
packaging = "*"
|
||||||
pluggy = ">=0.12,<2.0"
|
pluggy = ">=0.12,<2.0"
|
||||||
py = ">=1.8.2"
|
py = ">=1.8.2"
|
||||||
tomli = ">=1.0.0"
|
toml = "*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
@@ -410,7 +380,7 @@ six = ">=1.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2022.1"
|
version = "2021.1"
|
||||||
description = "World timezone definitions, modern and historical"
|
description = "World timezone definitions, modern and historical"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -418,15 +388,15 @@ python-versions = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0"
|
version = "5.4.1"
|
||||||
description = "YAML parser and emitter for Python"
|
description = "YAML parser and emitter for Python"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.27.1"
|
version = "2.26.0"
|
||||||
description = "Python HTTP for Humans."
|
description = "Python HTTP for Humans."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -444,11 +414,11 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "4.8"
|
version = "4.7.2"
|
||||||
description = "Pure-Python RSA implementation"
|
description = "Pure-Python RSA implementation"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6,<4"
|
python-versions = ">=3.5, <4"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pyasn1 = ">=0.1.3"
|
pyasn1 = ">=0.1.3"
|
||||||
@@ -470,40 +440,32 @@ optional = false
|
|||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "toml"
|
||||||
version = "2.0.1"
|
version = "0.10.2"
|
||||||
description = "A lil' TOML parser"
|
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typed-ast"
|
|
||||||
version = "1.5.2"
|
|
||||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.1.1"
|
version = "3.10.0.2"
|
||||||
description = "Backported and Experimental Type Hints for Python 3.6+"
|
description = "Backported and Experimental Type Hints for Python 3.5+"
|
||||||
category = "dev"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uritemplate"
|
name = "uritemplate"
|
||||||
version = "4.1.1"
|
version = "3.0.1"
|
||||||
description = "Implementation of RFC 6570 URI Templates"
|
description = "URI templates"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "1.26.8"
|
version = "1.26.7"
|
||||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -516,20 +478,20 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zipp"
|
name = "zipp"
|
||||||
version = "3.7.0"
|
version = "3.6.0"
|
||||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
||||||
testing = ["pytest (>=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"]
|
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]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = ">=3.6.1"
|
||||||
content-hash = "98872c98b9b2ff9d51a549f5d93e030fd77e038c030dec9daa1b0bd3b924e38b"
|
content-hash = "9145569d3597a35a93b05b114ac7bcdba29e1b1fb215793b427b0e40fbe52bef"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
atomicwrites = [
|
atomicwrites = [
|
||||||
@@ -537,96 +499,75 @@ atomicwrites = [
|
|||||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
||||||
]
|
]
|
||||||
attrs = [
|
attrs = [
|
||||||
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
|
{file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
|
||||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
{file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
|
||||||
]
|
]
|
||||||
black = [
|
autopep8 = [
|
||||||
{file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"},
|
{file = "autopep8-1.5.7-py2.py3-none-any.whl", hash = "sha256:aa213493c30dcdac99537249ee65b24af0b2c29f2e83cd8b3f68760441ed0db9"},
|
||||||
{file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"},
|
{file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"},
|
||||||
{file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"},
|
|
||||||
{file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"},
|
|
||||||
{file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"},
|
|
||||||
{file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"},
|
|
||||||
{file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"},
|
|
||||||
{file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"},
|
|
||||||
{file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"},
|
|
||||||
{file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"},
|
|
||||||
{file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"},
|
|
||||||
{file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"},
|
|
||||||
{file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"},
|
|
||||||
{file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"},
|
|
||||||
{file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"},
|
|
||||||
{file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"},
|
|
||||||
{file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"},
|
|
||||||
{file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"},
|
|
||||||
{file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"},
|
|
||||||
{file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"},
|
|
||||||
{file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"},
|
|
||||||
{file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"},
|
|
||||||
{file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
|
|
||||||
]
|
]
|
||||||
cachetools = [
|
cachetools = [
|
||||||
{file = "cachetools-5.0.0-py3-none-any.whl", hash = "sha256:8fecd4203a38af17928be7b90689d8083603073622229ca7077b72d8e5a976e4"},
|
{file = "cachetools-4.2.4-py3-none-any.whl", hash = "sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1"},
|
||||||
{file = "cachetools-5.0.0.tar.gz", hash = "sha256:486471dfa8799eb7ec503a8059e263db000cdda20075ce5e48903087f79d5fd6"},
|
{file = "cachetools-4.2.4.tar.gz", hash = "sha256:89ea6f1b638d5a73a4f9226be57ac5e4f399d22770b92355f92dcb0f7f001693"},
|
||||||
]
|
]
|
||||||
certifi = [
|
certifi = [
|
||||||
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
|
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
|
||||||
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
|
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
|
||||||
]
|
]
|
||||||
charset-normalizer = [
|
charset-normalizer = [
|
||||||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
{file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"},
|
||||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
{file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"},
|
||||||
]
|
|
||||||
click = [
|
|
||||||
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
|
|
||||||
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
|
|
||||||
]
|
]
|
||||||
colorama = [
|
colorama = [
|
||||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
{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 = [
|
fire = [
|
||||||
{file = "fire-0.4.0.tar.gz", hash = "sha256:c5e2b8763699d1142393a46d0e3e790c5eb2f0706082df8f647878842c216a62"},
|
{file = "fire-0.4.0.tar.gz", hash = "sha256:c5e2b8763699d1142393a46d0e3e790c5eb2f0706082df8f647878842c216a62"},
|
||||||
]
|
]
|
||||||
flake8 = [
|
flake8 = [
|
||||||
{file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
|
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
|
||||||
{file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
|
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
|
||||||
]
|
]
|
||||||
google-api-core = [
|
google-api-core = [
|
||||||
{file = "google-api-core-2.5.0.tar.gz", hash = "sha256:f33863a6709651703b8b18b67093514838c79f2b04d02aa501203079f24b8018"},
|
{file = "google-api-core-2.1.0.tar.gz", hash = "sha256:5ec27b942b34d04559cbf3674430bb83fc3d74e7d32b8bbd31c4466e71740b83"},
|
||||||
{file = "google_api_core-2.5.0-py2.py3-none-any.whl", hash = "sha256:7d030edbd3a0e994d796e62716022752684e863a6df9864b6ca82a1616c2a5a6"},
|
{file = "google_api_core-2.1.0-py2.py3-none-any.whl", hash = "sha256:c344e1aacd8330527c5130bdfe03118d8859ce798bcf0e5d23770ab6873e0615"},
|
||||||
]
|
]
|
||||||
google-api-python-client = [
|
google-api-python-client = [
|
||||||
{file = "google-api-python-client-2.49.0.tar.gz", hash = "sha256:629bbde991ce2d9697c6da37f2416f7aeb01ba01505b166066a415b3c3ce1dfc"},
|
{file = "google-api-python-client-2.23.0.tar.gz", hash = "sha256:f117a595717fc384446f6235019e6a83fc9df821bd9d05dba7ff14aa96c70f52"},
|
||||||
{file = "google_api_python_client-2.49.0-py2.py3-none-any.whl", hash = "sha256:7e172b06abeff7170108596446f0c8e56a129a40ef29a802270a02c0b07e993d"},
|
{file = "google_api_python_client-2.23.0-py2.py3-none-any.whl", hash = "sha256:a7b364eff63ca75d827cfb241a0f8567157976e879046c1ff20ddf735bad618e"},
|
||||||
]
|
]
|
||||||
google-auth = [
|
google-auth = [
|
||||||
{file = "google-auth-2.6.6.tar.gz", hash = "sha256:1ba4938e032b73deb51e59c4656a00e0939cf0b1112575099f136babb4563312"},
|
{file = "google-auth-2.2.1.tar.gz", hash = "sha256:6dc8173abd50f25b6e62fc5b42802c96fc7cd9deb9bfeeb10a79f5606225cdf4"},
|
||||||
{file = "google_auth-2.6.6-py2.py3-none-any.whl", hash = "sha256:349ac49b18b01019453cc99c11c92ed772739778c92f184002b7ab3a5b7ac77d"},
|
{file = "google_auth-2.2.1-py2.py3-none-any.whl", hash = "sha256:2a92b485afed5292946b324e91fcbe03db277ee4cb64c998c6cfa66d4af01dee"},
|
||||||
]
|
]
|
||||||
google-auth-httplib2 = [
|
google-auth-httplib2 = [
|
||||||
{file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"},
|
{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"},
|
{file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"},
|
||||||
]
|
]
|
||||||
googleapis-common-protos = [
|
googleapis-common-protos = [
|
||||||
{file = "googleapis-common-protos-1.54.0.tar.gz", hash = "sha256:a4031d6ec6c2b1b6dc3e0be7e10a1bd72fb0b18b07ef9be7b51f2c1004ce2437"},
|
{file = "googleapis-common-protos-1.53.0.tar.gz", hash = "sha256:a88ee8903aa0a81f6c3cec2d5cf62d3c8aa67c06439b0496b49048fb1854ebf4"},
|
||||||
{file = "googleapis_common_protos-1.54.0-py2.py3-none-any.whl", hash = "sha256:e54345a2add15dc5e1a7891c27731ff347b4c33765d79b5ed7026a6c0c7cbcae"},
|
{file = "googleapis_common_protos-1.53.0-py2.py3-none-any.whl", hash = "sha256:f6d561ab8fb16b30020b940e2dd01cd80082f4762fa9f3ee670f4419b4b8dbd0"},
|
||||||
]
|
]
|
||||||
httplib2 = [
|
httplib2 = [
|
||||||
{file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"},
|
{file = "httplib2-0.20.1-py3-none-any.whl", hash = "sha256:8fa4dbf2fbf839b71f8c7837a831e00fcdc860feca99b8bda58ceae4bc53d185"},
|
||||||
{file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"},
|
{file = "httplib2-0.20.1.tar.gz", hash = "sha256:0efbcb8bfbfbc11578130d87d8afcc65c2274c6eb446e59fc674e4d7c972d327"},
|
||||||
]
|
]
|
||||||
icalendar = [
|
icalendar = [
|
||||||
{file = "icalendar-4.0.9-py2.py3-none-any.whl", hash = "sha256:cf1446ffdf1b6ad469451a8966cfa7694f5fac796ac6fc7cd93e28c51a637d2c"},
|
{file = "icalendar-4.0.7-py2.py3-none-any.whl", hash = "sha256:8c35be16c1d0581a276002af883297aeffa8116e366fdce4d5318e1424aa1903"},
|
||||||
{file = "icalendar-4.0.9.tar.gz", hash = "sha256:cc73fa9c848744843046228cb66ea86cd8c18d73a51b140f7c003f760b84a997"},
|
{file = "icalendar-4.0.7.tar.gz", hash = "sha256:0fc18d87f66e0b5da84fa731389496cfe18e4c21304e8f6713556b2e8724a7a4"},
|
||||||
]
|
]
|
||||||
idna = [
|
idna = [
|
||||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
{file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
|
||||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
{file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
|
||||||
]
|
]
|
||||||
importlib-metadata = [
|
importlib-metadata = [
|
||||||
{file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"},
|
{file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
|
||||||
{file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"},
|
{file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
|
||||||
]
|
]
|
||||||
iniconfig = [
|
iniconfig = [
|
||||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||||
@@ -636,57 +577,40 @@ mccabe = [
|
|||||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||||
]
|
]
|
||||||
mypy-extensions = [
|
|
||||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
|
||||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
|
||||||
]
|
|
||||||
packaging = [
|
packaging = [
|
||||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
{file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
|
||||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
{file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
|
||||||
]
|
|
||||||
pathspec = [
|
|
||||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
|
||||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
|
||||||
]
|
|
||||||
platformdirs = [
|
|
||||||
{file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
|
|
||||||
{file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"},
|
|
||||||
]
|
]
|
||||||
pluggy = [
|
pluggy = [
|
||||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||||
]
|
]
|
||||||
protobuf = [
|
protobuf = [
|
||||||
{file = "protobuf-3.19.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f51d5a9f137f7a2cec2d326a74b6e3fc79d635d69ffe1b036d39fc7d75430d37"},
|
{file = "protobuf-3.18.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fa6d1049d5315566f55c04d0b50c0033415144f96a9d25c820dc542fe2bb7f45"},
|
||||||
{file = "protobuf-3.19.4-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09297b7972da685ce269ec52af761743714996b4381c085205914c41fcab59fb"},
|
{file = "protobuf-3.18.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e2790c580070cff2921b93d562539ae027064340151c50db6aaf94c33048cd"},
|
||||||
{file = "protobuf-3.19.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072fbc78d705d3edc7ccac58a62c4c8e0cec856987da7df8aca86e647be4e35c"},
|
{file = "protobuf-3.18.1-cp36-cp36m-win32.whl", hash = "sha256:7e2f0677d68ecdd1cfda2abea65873f5bc7c3f5aae199404a3f5c1d1198c1a63"},
|
||||||
{file = "protobuf-3.19.4-cp310-cp310-win32.whl", hash = "sha256:7bb03bc2873a2842e5ebb4801f5c7ff1bfbdf426f85d0172f7644fcda0671ae0"},
|
{file = "protobuf-3.18.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6f714f5de9d40b3bec90ede4a688cce52f637ccdc5403afcda1f67598f4fdcd7"},
|
||||||
{file = "protobuf-3.19.4-cp310-cp310-win_amd64.whl", hash = "sha256:f358aa33e03b7a84e0d91270a4d4d8f5df6921abe99a377828839e8ed0c04e07"},
|
{file = "protobuf-3.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a7be937c319146cc9f2626f0181e6809062c353e1fe449ecd0df374ba1036b2"},
|
||||||
{file = "protobuf-3.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1c91ef4110fdd2c590effb5dca8fdbdcb3bf563eece99287019c4204f53d81a4"},
|
{file = "protobuf-3.18.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:10544fc7ace885a882623083c24da5b14148c77563acddc3c58d66f6153c09cd"},
|
||||||
{file = "protobuf-3.19.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c438268eebb8cf039552897d78f402d734a404f1360592fef55297285f7f953f"},
|
{file = "protobuf-3.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ee8b11e3eb2ed38f12137c3c132270a0b1dd509e317228ac47b67f21a583f1"},
|
||||||
{file = "protobuf-3.19.4-cp36-cp36m-win32.whl", hash = "sha256:835a9c949dc193953c319603b2961c5c8f4327957fe23d914ca80d982665e8ee"},
|
{file = "protobuf-3.18.1-cp37-cp37m-win32.whl", hash = "sha256:c492c217d3f69f4d2d5619571e52ab98538edbf53caf67e53ea92bd0a3b5670f"},
|
||||||
{file = "protobuf-3.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4276cdec4447bd5015453e41bdc0c0c1234eda08420b7c9a18b8d647add51e4b"},
|
{file = "protobuf-3.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3c1644f8a7f19b45c7a4c32278e2a55ae9e7e2f9e5f02d960a61f04a4890d3e6"},
|
||||||
{file = "protobuf-3.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6cbc312be5e71869d9d5ea25147cdf652a6781cf4d906497ca7690b7b9b5df13"},
|
{file = "protobuf-3.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9ac691f7b24e4371dcd3980e4f5d6c840a2010da37986203053fee995786ec5"},
|
||||||
{file = "protobuf-3.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54a1473077f3b616779ce31f477351a45b4fef8c9fd7892d6d87e287a38df368"},
|
{file = "protobuf-3.18.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:93bad12895d8b0ebc66b605c2ef1802311595f881aef032d9f13282b7550e6b2"},
|
||||||
{file = "protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:435bb78b37fc386f9275a7035fe4fb1364484e38980d0dd91bc834a02c5ec909"},
|
{file = "protobuf-3.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0851b5b89191e1976d34fa2e8eb8659829dfb45252053224cf9df857fb5f6a45"},
|
||||||
{file = "protobuf-3.19.4-cp37-cp37m-win32.whl", hash = "sha256:16f519de1313f1b7139ad70772e7db515b1420d208cb16c6d7858ea989fc64a9"},
|
{file = "protobuf-3.18.1-cp38-cp38-win32.whl", hash = "sha256:09d9268f6f9da81b7657adcf2fb397524c82f20cdf9e0db3ff4e7567977abd67"},
|
||||||
{file = "protobuf-3.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc076c03381f5c1d9bb1abdcc5503d9ca8b53cf0a9d31a9f6754ec9e6c8af0f"},
|
{file = "protobuf-3.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d6d927774c0ec746fed15a4faff5f44aad0b7a3421fadb6f3ae5ca1f2f8ae26e"},
|
||||||
{file = "protobuf-3.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69da7d39e39942bd52848438462674c463e23963a1fdaa84d88df7fbd7e749b2"},
|
{file = "protobuf-3.18.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d19c9cb805fd2be1d59eee39e152367ee92a30167e77bd06c8819f8f0009a4c"},
|
||||||
{file = "protobuf-3.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48ed3877fa43e22bcacc852ca76d4775741f9709dd9575881a373bd3e85e54b2"},
|
{file = "protobuf-3.18.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:387f621bf7295a331f8c8a6962d097ceddeb85356792888cfa6a5c6bfc6886a4"},
|
||||||
{file = "protobuf-3.19.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd95d1dfb9c4f4563e6093a9aa19d9c186bf98fa54da5252531cc0d3a07977e7"},
|
{file = "protobuf-3.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1c5d3966c856f60a9d8d62f4455d70c31026422acdd5c228edf22b65b16c38"},
|
||||||
{file = "protobuf-3.19.4-cp38-cp38-win32.whl", hash = "sha256:b38057450a0c566cbd04890a40edf916db890f2818e8682221611d78dc32ae26"},
|
{file = "protobuf-3.18.1-cp39-cp39-win32.whl", hash = "sha256:f20f803892f2135e8b96dc58c9a0c6a7ad8436794bf8784af229498d939b4c77"},
|
||||||
{file = "protobuf-3.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:7ca7da9c339ca8890d66958f5462beabd611eca6c958691a8fe6eccbd1eb0c6e"},
|
{file = "protobuf-3.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:d76201380f41a2d83fb613a4683059d1fcafbe969518b3e409e279a8788fde2f"},
|
||||||
{file = "protobuf-3.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36cecbabbda242915529b8ff364f2263cd4de7c46bbe361418b5ed859677ba58"},
|
{file = "protobuf-3.18.1-py2.py3-none-any.whl", hash = "sha256:61ca58e14033ca0dfa484a31d57237c1be3b6013454c7f53876a20fc88dd69b1"},
|
||||||
{file = "protobuf-3.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c1068287025f8ea025103e37d62ffd63fec8e9e636246b89c341aeda8a67c934"},
|
{file = "protobuf-3.18.1.tar.gz", hash = "sha256:1c9bb40503751087300dd12ce2e90899d68628977905c76effc48e66d089391e"},
|
||||||
{file = "protobuf-3.19.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96bd766831596d6014ca88d86dc8fe0fb2e428c0b02432fd9db3943202bf8c5e"},
|
|
||||||
{file = "protobuf-3.19.4-cp39-cp39-win32.whl", hash = "sha256:84123274d982b9e248a143dadd1b9815049f4477dc783bf84efe6250eb4b836a"},
|
|
||||||
{file = "protobuf-3.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:3112b58aac3bac9c8be2b60a9daf6b558ca3f7681c130dcdd788ade7c9ffbdca"},
|
|
||||||
{file = "protobuf-3.19.4-py2.py3-none-any.whl", hash = "sha256:8961c3a78ebfcd000920c9060a262f082f29838682b1f7201889300c1fbe0616"},
|
|
||||||
{file = "protobuf-3.19.4.tar.gz", hash = "sha256:9df0c10adf3e83015ced42a9a7bd64e13d06c4cf45c340d2c63020ea04499d0a"},
|
|
||||||
]
|
]
|
||||||
py = [
|
py = [
|
||||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
{file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
|
||||||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
{file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
|
||||||
]
|
]
|
||||||
pyasn1 = [
|
pyasn1 = [
|
||||||
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
|
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
|
||||||
@@ -719,71 +643,91 @@ pyasn1-modules = [
|
|||||||
{file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"},
|
{file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"},
|
||||||
]
|
]
|
||||||
pycodestyle = [
|
pycodestyle = [
|
||||||
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
|
{file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
|
||||||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
{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 = [
|
pyflakes = [
|
||||||
{file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
|
{file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
|
||||||
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
|
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
|
||||||
]
|
]
|
||||||
pyparsing = [
|
pyparsing = [
|
||||||
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
|
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||||
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
|
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||||
]
|
]
|
||||||
pytest = [
|
pytest = [
|
||||||
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
|
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
|
||||||
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
|
{file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
|
||||||
]
|
]
|
||||||
python-dateutil = [
|
python-dateutil = [
|
||||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
{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"},
|
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||||
]
|
]
|
||||||
pytz = [
|
pytz = [
|
||||||
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
|
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
|
||||||
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
|
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
|
||||||
]
|
]
|
||||||
pyyaml = [
|
pyyaml = [
|
||||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||||
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
|
||||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
|
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
|
||||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
|
{file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
|
||||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
|
{file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
|
||||||
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
|
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
|
||||||
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
|
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
|
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
|
{file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
|
{file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
|
{file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
|
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
|
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
|
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
|
{file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
|
{file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
|
{file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
|
{file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
|
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
|
||||||
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
|
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
|
||||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
|
{file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
|
||||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
|
{file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
|
||||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
|
{file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
|
||||||
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
|
{file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
|
||||||
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
|
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
|
||||||
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
|
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
|
||||||
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
|
{file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
|
||||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
|
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
|
||||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
|
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
|
||||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
|
|
||||||
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
|
|
||||||
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
|
||||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
|
||||||
]
|
]
|
||||||
requests = [
|
requests = [
|
||||||
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
|
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
|
||||||
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
|
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
|
||||||
]
|
]
|
||||||
rsa = [
|
rsa = [
|
||||||
{file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"},
|
{file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"},
|
||||||
{file = "rsa-4.8.tar.gz", hash = "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17"},
|
{file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"},
|
||||||
]
|
]
|
||||||
six = [
|
six = [
|
||||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
@@ -792,49 +736,24 @@ six = [
|
|||||||
termcolor = [
|
termcolor = [
|
||||||
{file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
|
{file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
|
||||||
]
|
]
|
||||||
tomli = [
|
toml = [
|
||||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||||
]
|
|
||||||
typed-ast = [
|
|
||||||
{file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"},
|
|
||||||
{file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"},
|
|
||||||
{file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"},
|
|
||||||
{file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"},
|
|
||||||
{file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"},
|
|
||||||
{file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"},
|
|
||||||
{file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"},
|
|
||||||
{file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"},
|
|
||||||
{file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"},
|
|
||||||
{file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"},
|
|
||||||
{file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"},
|
|
||||||
{file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"},
|
|
||||||
{file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"},
|
|
||||||
{file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"},
|
|
||||||
{file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"},
|
|
||||||
{file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"},
|
|
||||||
{file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"},
|
|
||||||
{file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"},
|
|
||||||
{file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"},
|
|
||||||
{file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"},
|
|
||||||
{file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"},
|
|
||||||
{file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"},
|
|
||||||
{file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"},
|
|
||||||
{file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"},
|
|
||||||
]
|
]
|
||||||
typing-extensions = [
|
typing-extensions = [
|
||||||
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
|
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
||||||
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
|
{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 = [
|
uritemplate = [
|
||||||
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
|
{file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"},
|
||||||
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
|
{file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"},
|
||||||
]
|
]
|
||||||
urllib3 = [
|
urllib3 = [
|
||||||
{file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
|
{file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
|
||||||
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
|
{file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
|
||||||
]
|
]
|
||||||
zipp = [
|
zipp = [
|
||||||
{file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"},
|
{file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
|
||||||
{file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"},
|
{file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "sync_ics2gcal"
|
name = "sync_ics2gcal"
|
||||||
version = "0.1.4"
|
version = "0.1.3"
|
||||||
description = "Sync ics file with Google calendar"
|
description = "Sync ics file with Google calendar"
|
||||||
authors = ["Dmitry Belyaev <b4tm4n@mail.ru>"]
|
authors = ["Dmitry Belyaev <b4tm4n@mail.ru>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -11,6 +11,7 @@ keywords = ["icalendar", "sync", "google", "calendar"]
|
|||||||
classifiers = [
|
classifiers = [
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
'Programming Language :: Python :: 3.9',
|
'Programming Language :: Python :: 3.9',
|
||||||
@@ -18,18 +19,19 @@ classifiers = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7"
|
python = ">=3.6.1"
|
||||||
google-auth = "2.6.6"
|
google-auth = "2.2.1"
|
||||||
google-api-python-client = "2.49.0"
|
google-api-python-client = "2.23.0"
|
||||||
icalendar = "4.0.9"
|
icalendar = "4.0.7"
|
||||||
pytz = "2022.1"
|
pytz = "2021.1"
|
||||||
PyYAML = "6.0"
|
PyYAML = "5.4.1"
|
||||||
fire = "0.4.0"
|
fire = "0.4.0"
|
||||||
|
pydantic = "^1.8.2"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pytest = "^7.1.2"
|
pytest = "^6.2.5"
|
||||||
flake8 = "^4.0.1"
|
flake8 = "^3.9.2"
|
||||||
black = "^22.3.0"
|
autopep8 = "^1.5.7"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
sync-ics2gcal = "sync_ics2gcal.sync_calendar:main"
|
sync-ics2gcal = "sync_ics2gcal.sync_calendar:main"
|
||||||
|
|||||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
from .ical import CalendarConverter, EventConverter, DateDateTime
|
|
||||||
|
from .ical import (
|
||||||
|
CalendarConverter,
|
||||||
|
EventConverter,
|
||||||
|
DateDateTime
|
||||||
|
)
|
||||||
|
|
||||||
from .gcal import (
|
from .gcal import (
|
||||||
GoogleCalendarService,
|
GoogleCalendarService,
|
||||||
GoogleCalendar,
|
GoogleCalendar,
|
||||||
EventData,
|
EventData,
|
||||||
EventList,
|
EventList,
|
||||||
EventTuple,
|
EventTuple
|
||||||
)
|
)
|
||||||
|
|
||||||
from .sync import CalendarSync
|
from .sync import (
|
||||||
|
CalendarSync
|
||||||
|
)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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]]
|
EventData = Dict[str, Union[str, 'EventData', None]]
|
||||||
EventList = List[EventData]
|
EventList = List[EventData]
|
||||||
EventTuple = Tuple[EventData, EventData]
|
EventTuple = Tuple[EventData, EventData]
|
||||||
|
|
||||||
@@ -26,25 +26,24 @@ class GoogleCalendarService:
|
|||||||
( https://googleapis.dev/python/google-auth/latest/reference/google.auth.html#google.auth.default )
|
( https://googleapis.dev/python/google-auth/latest/reference/google.auth.html#google.auth.default )
|
||||||
"""
|
"""
|
||||||
|
|
||||||
scopes = ["https://www.googleapis.com/auth/calendar"]
|
scopes = ['https://www.googleapis.com/auth/calendar']
|
||||||
credentials, _ = google.auth.default(scopes=scopes)
|
credentials, _ = google.auth.default(scopes=scopes)
|
||||||
service = discovery.build(
|
service = discovery.build(
|
||||||
"calendar", "v3", credentials=credentials, cache_discovery=False
|
'calendar', 'v3', credentials=credentials, cache_discovery=False)
|
||||||
)
|
|
||||||
return service
|
return service
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_srv_acc_file(service_account_file: str):
|
def from_srv_acc_file(service_account_file: str):
|
||||||
"""make service Resource from service account filename (authorize)"""
|
"""make service Resource from service account filename (authorize)
|
||||||
|
"""
|
||||||
|
|
||||||
scopes = ["https://www.googleapis.com/auth/calendar"]
|
scopes = ['https://www.googleapis.com/auth/calendar']
|
||||||
credentials = service_account.Credentials.from_service_account_file(
|
credentials = service_account.Credentials.from_service_account_file(
|
||||||
service_account_file
|
service_account_file)
|
||||||
)
|
|
||||||
scoped_credentials = credentials.with_scopes(scopes)
|
scoped_credentials = credentials.with_scopes(scopes)
|
||||||
service = discovery.build(
|
service = discovery.build(
|
||||||
"calendar", "v3", credentials=scoped_credentials, cache_discovery=False
|
'calendar', 'v3', credentials=scoped_credentials,
|
||||||
)
|
cache_discovery=False)
|
||||||
return service
|
return service
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -59,8 +58,9 @@ class GoogleCalendarService:
|
|||||||
-- None: default credentials will be used
|
-- None: default credentials will be used
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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 = GoogleCalendarService.from_srv_acc_file(
|
||||||
|
config['service_account'])
|
||||||
else:
|
else:
|
||||||
service = GoogleCalendarService.default()
|
service = GoogleCalendarService.default()
|
||||||
return service
|
return service
|
||||||
@@ -77,21 +77,22 @@ def select_event_key(event: EventData) -> Optional[str]:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
key = None
|
key = None
|
||||||
if "iCalUID" in event:
|
if 'iCalUID' in event:
|
||||||
key = "iCalUID"
|
key = 'iCalUID'
|
||||||
elif "id" in event:
|
elif 'id' in event:
|
||||||
key = "id"
|
key = 'id'
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
class GoogleCalendar:
|
class GoogleCalendar:
|
||||||
"""class to interact with calendar on Google"""
|
"""class to interact with calendar on google
|
||||||
|
"""
|
||||||
|
|
||||||
logger = logging.getLogger("GoogleCalendar")
|
logger = logging.getLogger('GoogleCalendar')
|
||||||
|
|
||||||
def __init__(self, service: discovery.Resource, calendar_id: Optional[str]):
|
def __init__(self, service: discovery.Resource, calendarId: Optional[str]):
|
||||||
self.service: discovery.Resource = service
|
self.service: discovery.Resource = service
|
||||||
self.calendar_id: str = calendar_id
|
self.calendarId: str = calendarId
|
||||||
|
|
||||||
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
|
||||||
@@ -110,47 +111,39 @@ class GoogleCalendar:
|
|||||||
|
|
||||||
if exception is not None:
|
if exception is not None:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
"failed to %s event with %s: %s, exception: %s",
|
'failed to %s event with %s: %s, exception: %s',
|
||||||
action,
|
action, key, event.get(key), str(exception)
|
||||||
key,
|
|
||||||
event.get(key),
|
|
||||||
str(exception),
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
resp_key = select_event_key(response)
|
resp_key = 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
|
||||||
self.logger.info("event %s ok, %s: %s", action, key, event.get(key))
|
self.logger.info('event %s ok, %s: %s',
|
||||||
|
action, key, event.get(key))
|
||||||
|
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
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 = 'nextPageToken,items(id,iCalUID,updated)'
|
||||||
events = []
|
events = []
|
||||||
page_token = None
|
page_token = None
|
||||||
time_min = (
|
timeMin = utc.normalize(start.astimezone(utc)).replace(
|
||||||
utc.normalize(start.astimezone(utc)).replace(tzinfo=None).isoformat() + "Z"
|
tzinfo=None).isoformat() + 'Z'
|
||||||
)
|
|
||||||
while True:
|
while True:
|
||||||
response = (
|
response = self.service.events().list(calendarId=self.calendarId,
|
||||||
self.service.events()
|
|
||||||
.list(
|
|
||||||
calendarId=self.calendar_id,
|
|
||||||
pageToken=page_token,
|
pageToken=page_token,
|
||||||
singleEvents=True,
|
singleEvents=True,
|
||||||
timeMin=time_min,
|
timeMin=timeMin,
|
||||||
fields=fields,
|
fields=fields).execute()
|
||||||
)
|
if 'items' in response:
|
||||||
.execute()
|
events.extend(response['items'])
|
||||||
)
|
page_token = response.get('nextPageToken')
|
||||||
if "items" in response:
|
|
||||||
events.extend(response["items"])
|
|
||||||
page_token = response.get("nextPageToken")
|
|
||||||
if not page_token:
|
if not page_token:
|
||||||
break
|
break
|
||||||
self.logger.info("%d events listed", len(events))
|
self.logger.info('%d events listed', len(events))
|
||||||
return events
|
return events
|
||||||
|
|
||||||
def find_exists(self, events: List) -> Tuple[List[EventTuple], EventList]:
|
def find_exists(self, events: List) -> Tuple[List[EventTuple], EventList]:
|
||||||
@@ -164,7 +157,7 @@ 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 = 'items(id,iCalUID,updated)'
|
||||||
events_by_req = []
|
events_by_req = []
|
||||||
exists = []
|
exists = []
|
||||||
not_found = []
|
not_found = []
|
||||||
@@ -173,15 +166,14 @@ class GoogleCalendar:
|
|||||||
found = False
|
found = False
|
||||||
cur_event = events_by_req[int(request_id)]
|
cur_event = events_by_req[int(request_id)]
|
||||||
if exception is None:
|
if exception is None:
|
||||||
found = [] != response["items"]
|
found = ([] != response['items'])
|
||||||
else:
|
else:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
"exception %s, while listing event with UID: %s",
|
'exception %s, while listing event with UID: %s',
|
||||||
str(exception),
|
str(exception), cur_event['iCalUID'])
|
||||||
cur_event["iCalUID"],
|
|
||||||
)
|
|
||||||
if found:
|
if found:
|
||||||
exists.append((cur_event, response["items"][0]))
|
exists.append(
|
||||||
|
(cur_event, response['items'][0]))
|
||||||
else:
|
else:
|
||||||
not_found.append(events_by_req[int(request_id)])
|
not_found.append(events_by_req[int(request_id)])
|
||||||
|
|
||||||
@@ -189,18 +181,17 @@ class GoogleCalendar:
|
|||||||
i = 0
|
i = 0
|
||||||
for event in events:
|
for event in events:
|
||||||
events_by_req.append(event)
|
events_by_req.append(event)
|
||||||
batch.add(
|
batch.add(self.service.events().list(calendarId=self.calendarId,
|
||||||
self.service.events().list(
|
iCalUID=event['iCalUID'],
|
||||||
calendarId=self.calendar_id,
|
|
||||||
iCalUID=event["iCalUID"],
|
|
||||||
showDeleted=True,
|
showDeleted=True,
|
||||||
fields=fields,
|
fields=fields
|
||||||
),
|
),
|
||||||
request_id=str(i),
|
request_id=str(i)
|
||||||
)
|
)
|
||||||
i += 1
|
i += 1
|
||||||
batch.execute()
|
batch.execute()
|
||||||
self.logger.info("%d events exists, %d not found", len(exists), len(not_found))
|
self.logger.info('%d events exists, %d not found',
|
||||||
|
len(exists), len(not_found))
|
||||||
return exists, not_found
|
return exists, not_found
|
||||||
|
|
||||||
def insert_events(self, events: EventList):
|
def insert_events(self, events: EventList):
|
||||||
@@ -210,19 +201,17 @@ class GoogleCalendar:
|
|||||||
events - events list
|
events - events list
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fields = "id"
|
fields = 'id'
|
||||||
events_by_req = []
|
events_by_req = []
|
||||||
|
|
||||||
insert_callback = self._make_request_callback("insert", events_by_req)
|
insert_callback = self._make_request_callback('insert', events_by_req)
|
||||||
batch = self.service.new_batch_http_request(callback=insert_callback)
|
batch = self.service.new_batch_http_request(callback=insert_callback)
|
||||||
i = 0
|
i = 0
|
||||||
for event in events:
|
for event in events:
|
||||||
events_by_req.append(event)
|
events_by_req.append(event)
|
||||||
batch.add(
|
batch.add(self.service.events().insert(
|
||||||
self.service.events().insert(
|
calendarId=self.calendarId, body=event, fields=fields),
|
||||||
calendarId=self.calendar_id, body=event, fields=fields
|
request_id=str(i)
|
||||||
),
|
|
||||||
request_id=str(i),
|
|
||||||
)
|
)
|
||||||
i += 1
|
i += 1
|
||||||
batch.execute()
|
batch.execute()
|
||||||
@@ -234,23 +223,19 @@ class GoogleCalendar:
|
|||||||
event_tuples -- list of tuples: (new_event, exists_event)
|
event_tuples -- list of tuples: (new_event, exists_event)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fields = "id"
|
fields = 'id'
|
||||||
events_by_req = []
|
events_by_req = []
|
||||||
|
|
||||||
patch_callback = self._make_request_callback("patch", events_by_req)
|
patch_callback = self._make_request_callback('patch', events_by_req)
|
||||||
batch = self.service.new_batch_http_request(callback=patch_callback)
|
batch = self.service.new_batch_http_request(callback=patch_callback)
|
||||||
i = 0
|
i = 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
|
||||||
events_by_req.append(event_new)
|
events_by_req.append(event_new)
|
||||||
batch.add(
|
batch.add(self.service.events().patch(
|
||||||
self.service.events().patch(
|
calendarId=self.calendarId, eventId=event_old['id'],
|
||||||
calendarId=self.calendar_id, eventId=event_old["id"], body=event_new
|
body=event_new), fields=fields, request_id=str(i))
|
||||||
),
|
|
||||||
fields=fields,
|
|
||||||
request_id=str(i),
|
|
||||||
)
|
|
||||||
i += 1
|
i += 1
|
||||||
batch.execute()
|
batch.execute()
|
||||||
|
|
||||||
@@ -261,25 +246,19 @@ class GoogleCalendar:
|
|||||||
event_tuples -- list of tuples: (new_event, exists_event)
|
event_tuples -- list of tuples: (new_event, exists_event)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fields = "id"
|
fields = 'id'
|
||||||
events_by_req = []
|
events_by_req = []
|
||||||
|
|
||||||
update_callback = self._make_request_callback("update", events_by_req)
|
update_callback = self._make_request_callback('update', events_by_req)
|
||||||
batch = self.service.new_batch_http_request(callback=update_callback)
|
batch = self.service.new_batch_http_request(callback=update_callback)
|
||||||
i = 0
|
i = 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
|
||||||
events_by_req.append(event_new)
|
events_by_req.append(event_new)
|
||||||
batch.add(
|
batch.add(self.service.events().update(
|
||||||
self.service.events().update(
|
calendarId=self.calendarId, eventId=event_old['id'],
|
||||||
calendarId=self.calendar_id,
|
body=event_new, fields=fields), request_id=str(i))
|
||||||
eventId=event_old["id"],
|
|
||||||
body=event_new,
|
|
||||||
fields=fields,
|
|
||||||
),
|
|
||||||
request_id=str(i),
|
|
||||||
)
|
|
||||||
i += 1
|
i += 1
|
||||||
batch.execute()
|
batch.execute()
|
||||||
|
|
||||||
@@ -292,21 +271,18 @@ class GoogleCalendar:
|
|||||||
|
|
||||||
events_by_req = []
|
events_by_req = []
|
||||||
|
|
||||||
delete_callback = self._make_request_callback("delete", events_by_req)
|
delete_callback = self._make_request_callback('delete', events_by_req)
|
||||||
batch = self.service.new_batch_http_request(callback=delete_callback)
|
batch = self.service.new_batch_http_request(callback=delete_callback)
|
||||||
i = 0
|
i = 0
|
||||||
for event in events:
|
for event in events:
|
||||||
events_by_req.append(event)
|
events_by_req.append(event)
|
||||||
batch.add(
|
batch.add(self.service.events().delete(
|
||||||
self.service.events().delete(
|
calendarId=self.calendarId,
|
||||||
calendarId=self.calendar_id, eventId=event["id"]
|
eventId=event['id']), request_id=str(i))
|
||||||
),
|
|
||||||
request_id=str(i),
|
|
||||||
)
|
|
||||||
i += 1
|
i += 1
|
||||||
batch.execute()
|
batch.execute()
|
||||||
|
|
||||||
def create(self, summary: str, time_zone: Optional[str] = None) -> Any:
|
def create(self, summary: str, timeZone: Optional[str] = None) -> Any:
|
||||||
"""create calendar
|
"""create calendar
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -319,33 +295,36 @@ class GoogleCalendar:
|
|||||||
calendar Resource
|
calendar Resource
|
||||||
"""
|
"""
|
||||||
|
|
||||||
calendar = {"summary": summary}
|
calendar = {'summary': summary}
|
||||||
if time_zone is not None:
|
if timeZone is not None:
|
||||||
calendar["timeZone"] = time_zone
|
calendar['timeZone'] = timeZone
|
||||||
|
|
||||||
created_calendar = self.service.calendars().insert(body=calendar).execute()
|
created_calendar = self.service.calendars().insert(
|
||||||
self.calendar_id = created_calendar["id"]
|
body=calendar
|
||||||
|
).execute()
|
||||||
|
self.calendarId = created_calendar['id']
|
||||||
return created_calendar
|
return created_calendar
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""delete calendar"""
|
"""delete calendar
|
||||||
|
"""
|
||||||
|
|
||||||
self.service.calendars().delete(calendarId=self.calendar_id).execute()
|
self.service.calendars().delete(calendarId=self.calendarId).execute()
|
||||||
|
|
||||||
def make_public(self):
|
def make_public(self):
|
||||||
"""make calendar public"""
|
"""make calendar puplic
|
||||||
|
"""
|
||||||
|
|
||||||
rule_public = {
|
rule_public = {
|
||||||
"scope": {
|
'scope': {
|
||||||
"type": "default",
|
'type': 'default',
|
||||||
},
|
},
|
||||||
"role": "reader",
|
'role': 'reader'
|
||||||
}
|
}
|
||||||
return (
|
return self.service.acl().insert(
|
||||||
self.service.acl()
|
calendarId=self.calendarId,
|
||||||
.insert(calendarId=self.calendar_id, body=rule_public)
|
body=rule_public
|
||||||
.execute()
|
).execute()
|
||||||
)
|
|
||||||
|
|
||||||
def add_owner(self, email: str):
|
def add_owner(self, email: str):
|
||||||
"""add calendar owner by email
|
"""add calendar owner by email
|
||||||
@@ -355,14 +334,13 @@ class GoogleCalendar:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
rule_owner = {
|
rule_owner = {
|
||||||
"scope": {
|
'scope': {
|
||||||
"type": "user",
|
'type': 'user',
|
||||||
"value": email,
|
'value': email,
|
||||||
},
|
},
|
||||||
"role": "owner",
|
'role': 'owner'
|
||||||
}
|
}
|
||||||
return (
|
return self.service.acl().insert(
|
||||||
self.service.acl()
|
calendarId=self.calendarId,
|
||||||
.insert(calendarId=self.calendar_id, body=rule_owner)
|
body=rule_owner
|
||||||
.execute()
|
).execute()
|
||||||
)
|
|
||||||
|
|||||||
@@ -5,11 +5,56 @@ from typing import Union, Dict, Callable, Optional
|
|||||||
from icalendar import Calendar, Event
|
from icalendar import Calendar, Event
|
||||||
from pytz import utc
|
from pytz import utc
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
|
||||||
from .gcal import EventData, EventList
|
from .gcal import EventData, EventList
|
||||||
|
|
||||||
DateDateTime = Union[datetime.date, datetime.datetime]
|
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:
|
def format_datetime_utc(value: DateDateTime) -> str:
|
||||||
"""utc datetime as string from date or datetime value
|
"""utc datetime as string from date or datetime value
|
||||||
|
|
||||||
@@ -19,17 +64,20 @@ def format_datetime_utc(value: DateDateTime) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
utc datetime value as string in iso format
|
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 = datetime.datetime(
|
||||||
|
value.year, value.month, value.day, tzinfo=utc)
|
||||||
value = value.replace(microsecond=1)
|
value = value.replace(microsecond=1)
|
||||||
|
|
||||||
return utc.normalize(value.astimezone(utc)).replace(tzinfo=None).isoformat() + "Z"
|
return utc.normalize(
|
||||||
|
value.astimezone(utc)
|
||||||
|
).replace(tzinfo=None).isoformat() + 'Z'
|
||||||
|
|
||||||
|
|
||||||
def gcal_date_or_datetime(
|
def gcal_date_or_dateTime(value: DateDateTime,
|
||||||
value: DateDateTime, check_value: Optional[DateDateTime] = None
|
check_value: Optional[DateDateTime] = None) \
|
||||||
) -> Dict[str, str]:
|
-> Dict[str, str]:
|
||||||
"""date or datetime to gcal (start or end dict)
|
"""date or dateTime to gcal (start or end dict)
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
value: date or datetime
|
value: date or datetime
|
||||||
@@ -44,12 +92,12 @@ def gcal_date_or_datetime(
|
|||||||
|
|
||||||
result: Dict[str, str] = {}
|
result: Dict[str, str] = {}
|
||||||
if isinstance(check_value, datetime.datetime):
|
if isinstance(check_value, datetime.datetime):
|
||||||
result["dateTime"] = format_datetime_utc(value)
|
result['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['date'] = value.isoformat()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -62,13 +110,13 @@ class EventConverter(Event):
|
|||||||
"""decoded string property
|
"""decoded string property
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
prop - property name
|
prop - propperty name
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string value
|
string value
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.decoded(prop).decode(encoding="utf-8")
|
return self.decoded(prop).decode(encoding='utf-8')
|
||||||
|
|
||||||
def _datetime_str_prop(self, prop: str) -> str:
|
def _datetime_str_prop(self, prop: str) -> str:
|
||||||
"""utc datetime as string from property
|
"""utc datetime as string from property
|
||||||
@@ -82,7 +130,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) -> GCal_DateDateTime:
|
||||||
""" event start dict from icalendar event
|
""" event start dict from icalendar event
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
@@ -92,10 +140,10 @@ class EventConverter(Event):
|
|||||||
dict
|
dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value = self.decoded("DTSTART")
|
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
|
"""event end dict from icalendar event
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
@@ -104,31 +152,31 @@ class EventConverter(Event):
|
|||||||
dict
|
dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result: Dict[str, str]
|
result = None
|
||||||
if "DTEND" in self:
|
if 'DTEND' in self:
|
||||||
value = self.decoded("DTEND")
|
value = self.decoded('DTEND')
|
||||||
result = gcal_date_or_datetime(value)
|
result = GCal_DateDateTime.create_from(value)
|
||||||
elif "DURATION" in self:
|
elif 'DURATION' in self:
|
||||||
start_val = self.decoded("DTSTART")
|
start_val = self.decoded('DTSTART')
|
||||||
duration = self.decoded("DURATION")
|
duration = self.decoded('DURATION')
|
||||||
end_val = start_val + 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:
|
else:
|
||||||
raise ValueError("no DTEND or DURATION")
|
raise ValueError('no DTEND or DURATION')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _put_to_gcal(
|
def _put_to_gcal(self, gcal_event: EventData,
|
||||||
self,
|
prop: str, func: Callable[[str], str],
|
||||||
gcal_event: EventData,
|
ics_prop: Optional[str] = None):
|
||||||
prop: str,
|
"""get property from ical event if exist, and put to gcal event
|
||||||
func: Callable[[str], str],
|
|
||||||
ics_prop: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""get property from ical event if existed, and put to gcal event
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
gcal_event -- destination event
|
gcal_event -- dest event
|
||||||
prop -- property name
|
prop -- property name
|
||||||
func -- function to convert
|
func -- function to convert
|
||||||
ics_prop -- ical property name (default: {None})
|
ics_prop -- ical property name (default: {None})
|
||||||
@@ -139,6 +187,18 @@ class EventConverter(Event):
|
|||||||
if ics_prop in self:
|
if ics_prop in self:
|
||||||
gcal_event[prop] = func(ics_prop)
|
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:
|
def to_gcal(self) -> EventData:
|
||||||
"""Convert
|
"""Convert
|
||||||
|
|
||||||
@@ -146,48 +206,50 @@ class EventConverter(Event):
|
|||||||
dict - google calendar#event resource
|
dict - google calendar#event resource
|
||||||
"""
|
"""
|
||||||
|
|
||||||
event = {
|
kwargs = {
|
||||||
"iCalUID": self._str_prop("UID"),
|
'ical_uid': self._str_prop('UID'),
|
||||||
"start": self._gcal_start(),
|
'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)
|
return GCal_Event(**kwargs).dict(by_alias=True, exclude_defaults=True)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class CalendarConverter:
|
class CalendarConverter:
|
||||||
"""Convert icalendar events to google calendar resources"""
|
"""Convert icalendar events to google calendar resources
|
||||||
|
"""
|
||||||
|
|
||||||
logger = logging.getLogger("CalendarConverter")
|
logger = logging.getLogger('CalendarConverter')
|
||||||
|
|
||||||
def __init__(self, calendar: Optional[Calendar] = None):
|
def __init__(self, calendar: Optional[Calendar] = None):
|
||||||
self.calendar: Optional[Calendar] = calendar
|
self.calendar: Optional[Calendar] = calendar
|
||||||
|
|
||||||
def load(self, filename: str):
|
def load(self, filename: str):
|
||||||
"""load calendar from ics file"""
|
""" load calendar from ics file
|
||||||
with open(filename, "r", encoding="utf-8") as f:
|
"""
|
||||||
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
self.calendar = Calendar.from_ical(f.read())
|
self.calendar = Calendar.from_ical(f.read())
|
||||||
self.logger.info("%s loaded", filename)
|
self.logger.info('%s loaded', filename)
|
||||||
|
|
||||||
def loads(self, string: str):
|
def loads(self, string: str):
|
||||||
"""load calendar from ics string"""
|
""" load calendar from ics string
|
||||||
|
"""
|
||||||
self.calendar = Calendar.from_ical(string)
|
self.calendar = Calendar.from_ical(string)
|
||||||
|
|
||||||
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")
|
ics_events = self.calendar.walk(name='VEVENT')
|
||||||
self.logger.info("%d events read", len(ics_events))
|
self.logger.info('%d events readed', len(ics_events))
|
||||||
|
|
||||||
result = list(map(lambda event: EventConverter(event).to_gcal(), ics_events))
|
result = list(
|
||||||
self.logger.info("%d events converted", len(result))
|
map(lambda event: EventConverter(event).to_gcal(), ics_events))
|
||||||
|
self.logger.info('%d events converted', len(result))
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from . import GoogleCalendar, GoogleCalendarService
|
|||||||
def load_config(filename: str) -> Optional[Dict[str, Any]]:
|
def load_config(filename: str) -> Optional[Dict[str, Any]]:
|
||||||
result = None
|
result = None
|
||||||
try:
|
try:
|
||||||
with open(filename, "r", encoding="utf-8") as f:
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
result = yaml.safe_load(f)
|
result = yaml.safe_load(f)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
@@ -31,11 +31,8 @@ class PropertyCommands:
|
|||||||
calendar_id: calendar id
|
calendar_id: calendar id
|
||||||
property_name: property key
|
property_name: property key
|
||||||
"""
|
"""
|
||||||
response = (
|
response = self._service.calendarList().get(calendarId=calendar_id,
|
||||||
self._service.calendarList()
|
fields=property_name).execute()
|
||||||
.get(calendarId=calendar_id, fields=property_name)
|
|
||||||
.execute()
|
|
||||||
)
|
|
||||||
print(response.get(property_name))
|
print(response.get(property_name))
|
||||||
|
|
||||||
def set(self, calendar_id: str, property_name: str, property_value: str) -> None:
|
def set(self, calendar_id: str, property_name: str, property_value: str) -> None:
|
||||||
@@ -47,26 +44,22 @@ class PropertyCommands:
|
|||||||
property_value: property value
|
property_value: property value
|
||||||
"""
|
"""
|
||||||
body = {property_name: property_value}
|
body = {property_name: property_value}
|
||||||
response = (
|
response = self._service.calendarList().patch(body=body, calendarId=calendar_id).execute()
|
||||||
self._service.calendarList()
|
|
||||||
.patch(body=body, calendarId=calendar_id)
|
|
||||||
.execute()
|
|
||||||
)
|
|
||||||
print(response)
|
print(response)
|
||||||
|
|
||||||
|
|
||||||
class Commands:
|
class Commands:
|
||||||
""" manage google calendars in service account """
|
""" manage google calendars in service account """
|
||||||
|
|
||||||
def __init__(self, config: str = "config.yml"):
|
def __init__(self, config: str = 'config.yml'):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config(str): config filename
|
config(str): config filename
|
||||||
"""
|
"""
|
||||||
self._config: Optional[Dict[str, Any]] = load_config(config)
|
self._config: Optional[Dict[str, Any]] = load_config(config)
|
||||||
if self._config is not None and "logging" in self._config:
|
if self._config is not None and 'logging' in self._config:
|
||||||
logging.config.dictConfig(self._config["logging"])
|
logging.config.dictConfig(self._config['logging'])
|
||||||
self._service = GoogleCalendarService.from_config(self._config)
|
self._service = GoogleCalendarService.from_config(self._config)
|
||||||
self.property = PropertyCommands(self._service)
|
self.property = PropertyCommands(self._service)
|
||||||
|
|
||||||
@@ -78,28 +71,25 @@ class Commands:
|
|||||||
show_deleted: show deleted calendars
|
show_deleted: show deleted calendars
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fields: str = "nextPageToken,items(id,summary)"
|
fields: str = 'nextPageToken,items(id,summary)'
|
||||||
calendars: List[Dict[str, Any]] = []
|
calendars: List[Dict[str, Any]] = []
|
||||||
page_token: Optional[str] = None
|
page_token: Optional[str] = None
|
||||||
while True:
|
while True:
|
||||||
calendars_api = self._service.calendarList()
|
calendars_api = self._service.calendarList()
|
||||||
response = calendars_api.list(
|
response = calendars_api.list(fields=fields,
|
||||||
fields=fields,
|
|
||||||
pageToken=page_token,
|
pageToken=page_token,
|
||||||
showHidden=show_hidden,
|
showHidden=show_hidden,
|
||||||
showDeleted=show_deleted,
|
showDeleted=show_deleted
|
||||||
).execute()
|
).execute()
|
||||||
if "items" in response:
|
if 'items' in response:
|
||||||
calendars.extend(response["items"])
|
calendars.extend(response['items'])
|
||||||
page_token = response.get("nextPageToken")
|
page_token = response.get('nextPageToken')
|
||||||
if page_token is None:
|
if page_token is None:
|
||||||
break
|
break
|
||||||
for calendar in calendars:
|
for calendar in calendars:
|
||||||
print("{summary}: {id}".format_map(calendar))
|
print('{summary}: {id}'.format_map(calendar))
|
||||||
|
|
||||||
def create(
|
def create(self, summary: str, timezone: Optional[str] = None, public: bool = False) -> None:
|
||||||
self, summary: str, timezone: Optional[str] = None, public: bool = False
|
|
||||||
) -> None:
|
|
||||||
""" create calendar
|
""" create calendar
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -111,7 +101,7 @@ class Commands:
|
|||||||
calendar.create(summary, timezone)
|
calendar.create(summary, timezone)
|
||||||
if public:
|
if public:
|
||||||
calendar.make_public()
|
calendar.make_public()
|
||||||
print("{}: {}".format(summary, calendar.calendar_id))
|
print('{}: {}'.format(summary, calendar.calendarId))
|
||||||
|
|
||||||
def add_owner(self, calendar_id: str, email: str) -> None:
|
def add_owner(self, calendar_id: str, email: str) -> None:
|
||||||
""" add owner to calendar
|
""" add owner to calendar
|
||||||
@@ -122,7 +112,7 @@ class Commands:
|
|||||||
"""
|
"""
|
||||||
calendar = GoogleCalendar(self._service, calendar_id)
|
calendar = GoogleCalendar(self._service, calendar_id)
|
||||||
calendar.add_owner(email)
|
calendar.add_owner(email)
|
||||||
print("to {} added owner: {}".format(calendar_id, email))
|
print('to {} added owner: {}'.format(calendar_id, email))
|
||||||
|
|
||||||
def remove(self, calendar_id: str) -> None:
|
def remove(self, calendar_id: str) -> None:
|
||||||
""" remove calendar
|
""" remove calendar
|
||||||
@@ -132,7 +122,7 @@ class Commands:
|
|||||||
"""
|
"""
|
||||||
calendar = GoogleCalendar(self._service, calendar_id)
|
calendar = GoogleCalendar(self._service, calendar_id)
|
||||||
calendar.delete()
|
calendar.delete()
|
||||||
print("removed: {}".format(calendar_id))
|
print('removed: {}'.format(calendar_id))
|
||||||
|
|
||||||
def rename(self, calendar_id: str, summary: str) -> None:
|
def rename(self, calendar_id: str, summary: str) -> None:
|
||||||
""" rename calendar
|
""" rename calendar
|
||||||
@@ -141,14 +131,14 @@ class Commands:
|
|||||||
calendar_id: calendar id
|
calendar_id: calendar id
|
||||||
summary:
|
summary:
|
||||||
"""
|
"""
|
||||||
calendar = {"summary": summary}
|
calendar = {'summary': summary}
|
||||||
self._service.calendars().patch(body=calendar, calendarId=calendar_id).execute()
|
self._service.calendars().patch(body=calendar, calendarId=calendar_id).execute()
|
||||||
print("{}: {}".format(summary, calendar_id))
|
print('{}: {}'.format(summary, calendar_id))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
fire.Fire(Commands, name="manage-ics2gcal")
|
fire.Fire(Commands, name='manage-ics2gcal')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ from .ical import CalendarConverter, DateDateTime
|
|||||||
|
|
||||||
|
|
||||||
class CalendarSync:
|
class CalendarSync:
|
||||||
"""class for synchronize calendar with Google"""
|
"""class for syncronize calendar with google
|
||||||
|
"""
|
||||||
|
|
||||||
logger = logging.getLogger("CalendarSync")
|
logger = logging.getLogger('CalendarSync')
|
||||||
|
|
||||||
def __init__(self, gcalendar: GoogleCalendar, converter: CalendarConverter):
|
def __init__(self, gcalendar: GoogleCalendar, converter: CalendarConverter):
|
||||||
self.gcalendar: GoogleCalendar = gcalendar
|
self.gcalendar: GoogleCalendar = gcalendar
|
||||||
@@ -23,14 +24,15 @@ class CalendarSync:
|
|||||||
self.to_delete: EventList = []
|
self.to_delete: EventList = []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _events_list_compare(
|
def _events_list_compare(items_src: EventList,
|
||||||
items_src: EventList, items_dst: EventList, key: str = "iCalUID"
|
items_dst: EventList,
|
||||||
) -> Tuple[EventList, List[EventTuple], EventList]:
|
key: str = 'iCalUID') \
|
||||||
|
-> Tuple[EventList, List[EventTuple], EventList]:
|
||||||
""" compare list of events by key
|
""" compare list of events by key
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
items_src {list of dict} -- source events
|
items_src {list of dict} -- source events
|
||||||
items_dst {list of dict} -- destination events
|
items_dst {list of dict} -- dest events
|
||||||
key {str} -- name of key to compare (default: {'iCalUID'})
|
key {str} -- name of key to compare (default: {'iCalUID'})
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -39,8 +41,7 @@ class CalendarSync:
|
|||||||
items_to_delete)
|
items_to_delete)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_key(item: EventData) -> str:
|
def get_key(item: EventData) -> str: return item[key]
|
||||||
return item[key]
|
|
||||||
|
|
||||||
keys_src: Set[str] = set(map(get_key, items_src))
|
keys_src: Set[str] = set(map(get_key, items_src))
|
||||||
keys_dst: Set[str] = set(map(get_key, items_dst))
|
keys_dst: Set[str] = set(map(get_key, items_dst))
|
||||||
@@ -49,7 +50,9 @@ class CalendarSync:
|
|||||||
keys_to_update = keys_src & keys_dst
|
keys_to_update = keys_src & keys_dst
|
||||||
keys_to_delete = keys_dst - keys_src
|
keys_to_delete = keys_dst - keys_src
|
||||||
|
|
||||||
def items_by_keys(items: EventList, key_name: str, keys: Set[str]) -> EventList:
|
def items_by_keys(items: EventList,
|
||||||
|
key_name: str,
|
||||||
|
keys: Set[str]) -> EventList:
|
||||||
return list(filter(lambda item: item[key_name] in keys, items))
|
return list(filter(lambda item: item[key_name] in keys, items))
|
||||||
|
|
||||||
items_to_insert = items_by_keys(items_src, key, keys_to_insert)
|
items_to_insert = items_by_keys(items_src, key, keys_to_insert)
|
||||||
@@ -64,53 +67,50 @@ class CalendarSync:
|
|||||||
return items_to_insert, items_to_update, items_to_delete
|
return items_to_insert, items_to_update, items_to_delete
|
||||||
|
|
||||||
def _filter_events_to_update(self):
|
def _filter_events_to_update(self):
|
||||||
"""filter 'to_update' events by 'updated' datetime"""
|
""" filter 'to_update' events by 'updated' datetime
|
||||||
|
"""
|
||||||
|
|
||||||
def filter_updated(event_tuple: EventTuple) -> bool:
|
def filter_updated(event_tuple: EventTuple) -> bool:
|
||||||
new, old = event_tuple
|
new, old = event_tuple
|
||||||
if "updated" not in new or "updated" not in old:
|
new_date = dateutil.parser.parse(new['updated'])
|
||||||
return True
|
old_date = dateutil.parser.parse(old['updated'])
|
||||||
new_date = dateutil.parser.parse(new["updated"])
|
|
||||||
old_date = dateutil.parser.parse(old["updated"])
|
|
||||||
return new_date > old_date
|
return new_date > old_date
|
||||||
|
|
||||||
self.to_update = list(filter(filter_updated, self.to_update))
|
self.to_update = list(filter(filter_updated, self.to_update))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _filter_events_by_date(
|
def _filter_events_by_date(events: EventList,
|
||||||
events: EventList,
|
|
||||||
date: DateDateTime,
|
date: DateDateTime,
|
||||||
op: Callable[[DateDateTime, DateDateTime], bool],
|
op: Callable[[DateDateTime,
|
||||||
) -> EventList:
|
DateDateTime], bool]) -> EventList:
|
||||||
""" filter events by start datetime
|
""" filter events by start datetime
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
events -- events list
|
events -- events list
|
||||||
date {datetime} -- datetime to compare
|
date {datetime} -- datetime to compare
|
||||||
op {operator} -- comparison operator
|
op {operator} -- comparsion operator
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list of filtered events
|
list of filtred events
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filter_by_date(event: EventData) -> bool:
|
def filter_by_date(event: EventData) -> bool:
|
||||||
date_cmp = date
|
date_cmp = date
|
||||||
event_start: Dict[str, str] = event["start"]
|
event_start: Dict[str, str] = event['start']
|
||||||
event_date: Union[DateDateTime, str, None] = None
|
event_date: Union[DateDateTime, str, None] = None
|
||||||
compare_dates = False
|
compare_dates = False
|
||||||
|
|
||||||
if "date" in event_start:
|
if 'date' in event_start:
|
||||||
event_date = event_start["date"]
|
event_date = event_start['date']
|
||||||
compare_dates = True
|
compare_dates = True
|
||||||
elif "dateTime" in event_start:
|
elif 'dateTime' in event_start:
|
||||||
event_date = event_start["dateTime"]
|
event_date = event_start['dateTime']
|
||||||
|
|
||||||
event_date = dateutil.parser.parse(event_date)
|
event_date = dateutil.parser.parse(event_date)
|
||||||
if compare_dates:
|
if compare_dates:
|
||||||
date_cmp = datetime.date(date.year, date.month, date.day)
|
date_cmp = datetime.date(date.year, date.month, date.day)
|
||||||
event_date = datetime.date(
|
event_date = datetime.date(
|
||||||
event_date.year, event_date.month, event_date.day
|
event_date.year, event_date.month, event_date.day)
|
||||||
)
|
|
||||||
|
|
||||||
return op(event_date, date_cmp)
|
return op(event_date, date_cmp)
|
||||||
|
|
||||||
@@ -118,13 +118,13 @@ class CalendarSync:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _tz_aware_datetime(date: DateDateTime) -> datetime.datetime:
|
def _tz_aware_datetime(date: DateDateTime) -> datetime.datetime:
|
||||||
"""make tz aware datetime from datetime/date (utc if no tz-info)
|
"""make tz aware datetime from datetime/date (utc if no tzinfo)
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
date - date or datetime / with or without tz-info
|
date - date or datetime / with or without tzinfo
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
datetime with tz-info
|
datetime with tzinfo
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(date, datetime.datetime):
|
if not isinstance(date, datetime.datetime):
|
||||||
@@ -134,7 +134,7 @@ class CalendarSync:
|
|||||||
return date
|
return date
|
||||||
|
|
||||||
def prepare_sync(self, start_date: DateDateTime) -> None:
|
def prepare_sync(self, start_date: DateDateTime) -> None:
|
||||||
"""prepare sync lists by comparison of events
|
"""prepare sync lists by comparsion of events
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
start_date -- date/datetime to start sync
|
start_date -- date/datetime to start sync
|
||||||
@@ -147,47 +147,44 @@ class CalendarSync:
|
|||||||
|
|
||||||
# divide source events by start datetime
|
# divide source events by start datetime
|
||||||
events_src_pending = CalendarSync._filter_events_by_date(
|
events_src_pending = CalendarSync._filter_events_by_date(
|
||||||
events_src, start_date, operator.ge
|
events_src, start_date, operator.ge)
|
||||||
)
|
|
||||||
events_src_past = CalendarSync._filter_events_by_date(
|
events_src_past = CalendarSync._filter_events_by_date(
|
||||||
events_src, start_date, operator.lt
|
events_src, start_date, operator.lt)
|
||||||
)
|
|
||||||
|
|
||||||
# first events comparison
|
# first events comparsion
|
||||||
(
|
self.to_insert, self.to_update, self.to_delete = CalendarSync._events_list_compare(
|
||||||
self.to_insert,
|
events_src_pending, events_dst)
|
||||||
self.to_update,
|
|
||||||
self.to_delete,
|
|
||||||
) = CalendarSync._events_list_compare(events_src_pending, events_dst)
|
|
||||||
|
|
||||||
# find in events 'to_delete' past events from source, for update (move to past)
|
# find in events 'to_delete' past events from source, for update (move to past)
|
||||||
_, add_to_update, self.to_delete = CalendarSync._events_list_compare(
|
_, add_to_update, self.to_delete = CalendarSync._events_list_compare(
|
||||||
events_src_past, self.to_delete
|
events_src_past, self.to_delete)
|
||||||
)
|
|
||||||
self.to_update.extend(add_to_update)
|
self.to_update.extend(add_to_update)
|
||||||
|
|
||||||
# find if events 'to_insert' exists in gcalendar, for update them
|
# find if events 'to_insert' exists in gcalendar, for update them
|
||||||
add_to_update, self.to_insert = self.gcalendar.find_exists(self.to_insert)
|
add_to_update, self.to_insert = self.gcalendar.find_exists(
|
||||||
|
self.to_insert)
|
||||||
self.to_update.extend(add_to_update)
|
self.to_update.extend(add_to_update)
|
||||||
|
|
||||||
# exclude outdated events from 'to_update' list, by 'updated' field
|
# exclude outdated events from 'to_update' list, by 'updated' field
|
||||||
self._filter_events_to_update()
|
self._filter_events_to_update()
|
||||||
|
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
"prepared to sync: ( insert: %d, update: %d, delete: %d )",
|
'prepared to sync: ( insert: %d, update: %d, delete: %d )',
|
||||||
len(self.to_insert),
|
len(self.to_insert),
|
||||||
len(self.to_update),
|
len(self.to_update),
|
||||||
len(self.to_delete),
|
len(self.to_delete)
|
||||||
)
|
)
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
"""clear prepared sync lists (insert, update, delete)"""
|
""" clear prepared sync lists (insert, update, delete)
|
||||||
|
"""
|
||||||
self.to_insert.clear()
|
self.to_insert.clear()
|
||||||
self.to_update.clear()
|
self.to_update.clear()
|
||||||
self.to_delete.clear()
|
self.to_delete.clear()
|
||||||
|
|
||||||
def apply(self) -> None:
|
def apply(self) -> None:
|
||||||
"""apply sync (insert, update, delete), using prepared lists of events"""
|
""" apply sync (insert, update, delete), using prepared lists of events
|
||||||
|
"""
|
||||||
|
|
||||||
self.gcalendar.insert_events(self.to_insert)
|
self.gcalendar.insert_events(self.to_insert)
|
||||||
self.gcalendar.update_events(self.to_update)
|
self.gcalendar.update_events(self.to_update)
|
||||||
@@ -195,4 +192,4 @@ class CalendarSync:
|
|||||||
|
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
self.logger.info("sync done")
|
self.logger.info('sync done')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Dict, Any, Union
|
from typing import Dict, Any
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -6,48 +6,49 @@ import dateutil.parser
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import logging.config
|
import logging.config
|
||||||
from . import CalendarConverter, GoogleCalendarService, GoogleCalendar, CalendarSync
|
from . import (
|
||||||
|
CalendarConverter,
|
||||||
ConfigDate = Union[str, datetime.datetime]
|
GoogleCalendarService,
|
||||||
|
GoogleCalendar,
|
||||||
|
CalendarSync
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_config() -> Dict[str, Any]:
|
def load_config() -> Dict[str, Any]:
|
||||||
with open("config.yml", "r", encoding="utf-8") as f:
|
with open('config.yml', 'r', encoding='utf-8') as f:
|
||||||
result = yaml.safe_load(f)
|
result = yaml.safe_load(f)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_start_date(date: ConfigDate) -> datetime.datetime:
|
def get_start_date(date_str: str) -> datetime.datetime:
|
||||||
if isinstance(date, datetime.datetime):
|
if 'now' == date_str:
|
||||||
return date
|
|
||||||
if "now" == date:
|
|
||||||
result = datetime.datetime.utcnow()
|
result = datetime.datetime.utcnow()
|
||||||
else:
|
else:
|
||||||
result = dateutil.parser.parse(date)
|
result = dateutil.parser.parse(date_str)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
config = load_config()
|
config = load_config()
|
||||||
|
|
||||||
if "logging" in config:
|
if 'logging' in config:
|
||||||
logging.config.dictConfig(config["logging"])
|
logging.config.dictConfig(config['logging'])
|
||||||
|
|
||||||
calendar_id: str = config["calendar"]["google_id"]
|
calendarId: str = config['calendar']['google_id']
|
||||||
ics_filepath: str = config["calendar"]["source"]
|
ics_filepath: str = config['calendar']['source']
|
||||||
|
|
||||||
start = get_start_date(config["start_from"])
|
start = get_start_date(config['start_from'])
|
||||||
|
|
||||||
converter = CalendarConverter()
|
converter = CalendarConverter()
|
||||||
converter.load(ics_filepath)
|
converter.load(ics_filepath)
|
||||||
|
|
||||||
service = GoogleCalendarService.from_config(config)
|
service = GoogleCalendarService.from_config(config)
|
||||||
gcalendar = GoogleCalendar(service, calendar_id)
|
gcalendar = GoogleCalendar(service, calendarId)
|
||||||
|
|
||||||
sync = CalendarSync(gcalendar, converter)
|
sync = CalendarSync(gcalendar, converter)
|
||||||
sync.prepare_sync(start)
|
sync.prepare_sync(start)
|
||||||
sync.apply()
|
sync.apply()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
105
tests/bench_converter.py
Normal file
105
tests/bench_converter.py
Normal 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()
|
||||||
@@ -1,52 +1,31 @@
|
|||||||
import datetime
|
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pytz import timezone, utc
|
|
||||||
|
|
||||||
from sync_ics2gcal import CalendarConverter
|
from sync_ics2gcal import CalendarConverter
|
||||||
from sync_ics2gcal.ical import format_datetime_utc
|
|
||||||
|
|
||||||
uid = "UID:uisgtr8tre93wewe0yr8wqy@test.com"
|
uid = "UID:uisgtr8tre93wewe0yr8wqy@test.com"
|
||||||
only_start_date = (
|
only_start_date = uid + """
|
||||||
uid
|
|
||||||
+ """
|
|
||||||
DTSTART;VALUE=DATE:20180215
|
DTSTART;VALUE=DATE:20180215
|
||||||
"""
|
"""
|
||||||
)
|
date_val = only_start_date + """
|
||||||
date_val = (
|
|
||||||
only_start_date
|
|
||||||
+ """
|
|
||||||
DTEND;VALUE=DATE:20180217
|
DTEND;VALUE=DATE:20180217
|
||||||
"""
|
"""
|
||||||
)
|
date_duration = only_start_date + """
|
||||||
date_duration = (
|
|
||||||
only_start_date
|
|
||||||
+ """
|
|
||||||
DURATION:P2D
|
DURATION:P2D
|
||||||
"""
|
"""
|
||||||
)
|
datetime_utc_val = uid + """
|
||||||
datetime_utc_val = (
|
|
||||||
uid
|
|
||||||
+ """
|
|
||||||
DTSTART;VALUE=DATE-TIME:20180319T092001Z
|
DTSTART;VALUE=DATE-TIME:20180319T092001Z
|
||||||
DTEND:20180321T102501Z
|
DTEND:20180321T102501Z
|
||||||
"""
|
"""
|
||||||
)
|
datetime_utc_duration = uid + """
|
||||||
datetime_utc_duration = (
|
|
||||||
uid
|
|
||||||
+ """
|
|
||||||
DTSTART;VALUE=DATE-TIME:20180319T092001Z
|
DTSTART;VALUE=DATE-TIME:20180319T092001Z
|
||||||
DURATION:P2DT1H5M
|
DURATION:P2DT1H5M
|
||||||
"""
|
"""
|
||||||
)
|
created_updated = date_val + """
|
||||||
created_updated = (
|
|
||||||
date_val
|
|
||||||
+ """
|
|
||||||
CREATED:20180320T071155Z
|
CREATED:20180320T071155Z
|
||||||
LAST-MODIFIED:20180326T120235Z
|
LAST-MODIFIED:20180326T120235Z
|
||||||
"""
|
"""
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ics_test_cal(content: str) -> str:
|
def ics_test_cal(content: str) -> str:
|
||||||
@@ -78,29 +57,14 @@ def test_event_no_end():
|
|||||||
converter.events_to_gcal()
|
converter.events_to_gcal()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(
|
@pytest.fixture(params=[
|
||||||
params=[
|
("date", ics_test_event(date_val), '2018-02-15', '2018-02-17'),
|
||||||
("date", ics_test_event(date_val), "2018-02-15", "2018-02-17"),
|
("date", ics_test_event(date_duration), '2018-02-15', '2018-02-17'),
|
||||||
("date", ics_test_event(date_duration), "2018-02-15", "2018-02-17"),
|
("dateTime", ics_test_event(datetime_utc_val),
|
||||||
(
|
'2018-03-19T09:20:01.000001Z', '2018-03-21T10:25:01.000001Z'),
|
||||||
"dateTime",
|
("dateTime", ics_test_event(datetime_utc_duration), '2018-03-19T09:20:01.000001Z', '2018-03-21T10:25:01.000001Z')],
|
||||||
ics_test_event(datetime_utc_val),
|
ids=['date values', 'date duration',
|
||||||
"2018-03-19T09:20:01.000001Z",
|
'datetime utc values', 'datetime utc duration']
|
||||||
"2018-03-21T10:25:01.000001Z",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dateTime",
|
|
||||||
ics_test_event(datetime_utc_duration),
|
|
||||||
"2018-03-19T09:20:01.000001Z",
|
|
||||||
"2018-03-21T10:25:01.000001Z",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
ids=[
|
|
||||||
"date values",
|
|
||||||
"date duration",
|
|
||||||
"datetime utc values",
|
|
||||||
"datetime utc duration",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
def param_events_start_end(request):
|
def param_events_start_end(request):
|
||||||
return request.param
|
return request.param
|
||||||
@@ -113,8 +77,12 @@ def test_event_start_end(param_events_start_end: Tuple[str, str, str, str]):
|
|||||||
events = converter.events_to_gcal()
|
events = converter.events_to_gcal()
|
||||||
assert len(events) == 1
|
assert len(events) == 1
|
||||||
event = events[0]
|
event = events[0]
|
||||||
assert event["start"] == {date_type: start}
|
assert event['start'] == {
|
||||||
assert event["end"] == {date_type: end}
|
date_type: start
|
||||||
|
}
|
||||||
|
assert event['end'] == {
|
||||||
|
date_type: end
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_event_created_updated():
|
def test_event_created_updated():
|
||||||
@@ -123,24 +91,5 @@ def test_event_created_updated():
|
|||||||
events = converter.events_to_gcal()
|
events = converter.events_to_gcal()
|
||||||
assert len(events) == 1
|
assert len(events) == 1
|
||||||
event = events[0]
|
event = events[0]
|
||||||
assert event["created"] == "2018-03-20T07:11:55.000001Z"
|
assert event['created'] == '2018-03-20T07:11:55.000001Z'
|
||||||
assert event["updated"] == "2018-03-26T12:02:35.000001Z"
|
assert event['updated'] == '2018-03-26T12:02:35.000001Z'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"value,expected_str",
|
|
||||||
[
|
|
||||||
(
|
|
||||||
datetime.datetime(2022, 6, 3, 13, 52, 15, 1, utc),
|
|
||||||
"2022-06-03T13:52:15.000001Z",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
datetime.datetime(2022, 6, 3, 13, 52, 15, 1, timezone("Europe/Moscow")),
|
|
||||||
"2022-06-03T11:22:15.000001Z",
|
|
||||||
),
|
|
||||||
(datetime.date(2022, 6, 3), "2022-06-03T00:00:00.000001Z"),
|
|
||||||
],
|
|
||||||
ids=["utc", "with timezone", "date"],
|
|
||||||
)
|
|
||||||
def test_format_datetime_utc(value: datetime.datetime, expected_str: str):
|
|
||||||
assert format_datetime_utc(value) == expected_str
|
|
||||||
|
|||||||
@@ -14,30 +14,28 @@ from sync_ics2gcal import CalendarSync
|
|||||||
|
|
||||||
def sha1(string: Union[str, bytes]) -> str:
|
def sha1(string: Union[str, bytes]) -> str:
|
||||||
if isinstance(string, str):
|
if isinstance(string, str):
|
||||||
string = string.encode("utf8")
|
string = string.encode('utf8')
|
||||||
h = hashlib.sha1()
|
h = hashlib.sha1()
|
||||||
h.update(string)
|
h.update(string)
|
||||||
return h.hexdigest()
|
return h.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def gen_events(
|
def gen_events(start: int,
|
||||||
start: int,
|
|
||||||
stop: int,
|
stop: int,
|
||||||
start_time: Union[datetime.datetime, datetime.date],
|
start_time: Union[datetime.datetime, datetime.date],
|
||||||
no_time: bool = False,
|
no_time: bool = False) -> List[Dict[str, Union[str, Dict[str, str]]]]:
|
||||||
) -> List[Dict[str, Union[str, Dict[str, str]]]]:
|
|
||||||
if no_time:
|
if no_time:
|
||||||
start_time = datetime.date(start_time.year, start_time.month, start_time.day)
|
start_time = datetime.date(
|
||||||
|
start_time.year, start_time.month, start_time.day)
|
||||||
duration: datetime.timedelta = datetime.date(1, 1, 2) - datetime.date(1, 1, 1)
|
duration: datetime.timedelta = datetime.date(1, 1, 2) - datetime.date(1, 1, 1)
|
||||||
date_key: str = "date"
|
date_key: str = "date"
|
||||||
date_end: str = ""
|
date_end: str = ''
|
||||||
else:
|
else:
|
||||||
start_time = utc.normalize(start_time.astimezone(utc)).replace(tzinfo=None)
|
start_time = utc.normalize(
|
||||||
duration: datetime.timedelta = datetime.datetime(
|
start_time.astimezone(utc)).replace(tzinfo=None)
|
||||||
1, 1, 1, 2
|
duration: datetime.timedelta = datetime.datetime(1, 1, 1, 2) - datetime.datetime(1, 1, 1, 1)
|
||||||
) - datetime.datetime(1, 1, 1, 1)
|
|
||||||
date_key: str = "dateTime"
|
date_key: str = "dateTime"
|
||||||
date_end: str = "Z"
|
date_end: str = 'Z'
|
||||||
|
|
||||||
result: List[Dict[str, Union[str, Dict[str, str]]]] = []
|
result: List[Dict[str, Union[str, Dict[str, str]]]] = []
|
||||||
for i in range(start, stop):
|
for i in range(start, stop):
|
||||||
@@ -47,18 +45,17 @@ def gen_events(
|
|||||||
updated: Union[datetime.datetime, datetime.date] = event_start
|
updated: Union[datetime.datetime, datetime.date] = event_start
|
||||||
if no_time:
|
if no_time:
|
||||||
updated = datetime.datetime(
|
updated = datetime.datetime(
|
||||||
updated.year, updated.month, updated.day, 0, 0, 0, 1, tzinfo=utc
|
updated.year, updated.month, updated.day, 0, 0, 0, 1, tzinfo=utc)
|
||||||
)
|
|
||||||
|
|
||||||
event: Dict[str, Union[str, Dict[str, str]]] = {
|
event: Dict[str, Union[str, Dict[str, str]]] = {
|
||||||
"summary": "test event __ {}".format(i),
|
'summary': 'test event __ {}'.format(i),
|
||||||
"location": "la la la {}".format(i),
|
'location': 'la la la {}'.format(i),
|
||||||
"description": "test TEST -- test event {}".format(i),
|
'description': 'test TEST -- test event {}'.format(i),
|
||||||
"iCalUID": "{}@test.com".format(sha1("test - event {}".format(i))),
|
"iCalUID": "{}@test.com".format(sha1("test - event {}".format(i))),
|
||||||
"updated": updated.isoformat() + "Z",
|
"updated": updated.isoformat() + 'Z',
|
||||||
"created": updated.isoformat() + "Z",
|
"created": updated.isoformat() + 'Z',
|
||||||
"start": {date_key: event_start.isoformat() + date_end},
|
'start': {date_key: event_start.isoformat() + date_end},
|
||||||
"end": {date_key: event_end.isoformat() + date_end},
|
'end': {date_key: event_end.isoformat() + date_end}
|
||||||
}
|
}
|
||||||
result.append(event)
|
result.append(event)
|
||||||
return result
|
return result
|
||||||
@@ -67,21 +64,19 @@ def gen_events(
|
|||||||
def gen_list_to_compare(start: int, stop: int) -> List[Dict[str, str]]:
|
def gen_list_to_compare(start: int, stop: int) -> List[Dict[str, str]]:
|
||||||
result: List[Dict[str, str]] = []
|
result: List[Dict[str, str]] = []
|
||||||
for i in range(start, stop):
|
for i in range(start, stop):
|
||||||
result.append({"iCalUID": "test{:06d}".format(i)})
|
result.append({'iCalUID': 'test{:06d}'.format(i)})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_start_date(
|
def get_start_date(event: Dict[str, Union[str, Dict[str, str]]]) -> Union[datetime.datetime, datetime.date]:
|
||||||
event: Dict[str, Union[str, Dict[str, str]]]
|
event_start: Dict[str, str] = event['start']
|
||||||
) -> Union[datetime.datetime, datetime.date]:
|
|
||||||
event_start: Dict[str, str] = event["start"]
|
|
||||||
start_date: Optional[str] = None
|
start_date: Optional[str] = None
|
||||||
is_date = False
|
is_date = False
|
||||||
if "date" in event_start:
|
if 'date' in event_start:
|
||||||
start_date = event_start["date"]
|
start_date = event_start['date']
|
||||||
is_date = True
|
is_date = True
|
||||||
if "dateTime" in event_start:
|
if 'dateTime' in event_start:
|
||||||
start_date = event_start["dateTime"]
|
start_date = event_start['dateTime']
|
||||||
|
|
||||||
result = dateutil.parser.parse(start_date)
|
result = dateutil.parser.parse(start_date)
|
||||||
if is_date:
|
if is_date:
|
||||||
@@ -95,7 +90,8 @@ def test_compare():
|
|||||||
# [1..2n]
|
# [1..2n]
|
||||||
lst_src = gen_list_to_compare(1, 1 + part_len * 2)
|
lst_src = gen_list_to_compare(1, 1 + part_len * 2)
|
||||||
# [n..3n]
|
# [n..3n]
|
||||||
lst_dst = gen_list_to_compare(1 + part_len, 1 + part_len * 3)
|
lst_dst = gen_list_to_compare(
|
||||||
|
1 + part_len, 1 + part_len * 3)
|
||||||
|
|
||||||
lst_src_rnd = deepcopy(lst_src)
|
lst_src_rnd = deepcopy(lst_src)
|
||||||
lst_dst_rnd = deepcopy(lst_dst)
|
lst_dst_rnd = deepcopy(lst_dst)
|
||||||
@@ -103,14 +99,15 @@ def test_compare():
|
|||||||
shuffle(lst_src_rnd)
|
shuffle(lst_src_rnd)
|
||||||
shuffle(lst_dst_rnd)
|
shuffle(lst_dst_rnd)
|
||||||
|
|
||||||
to_ins, to_upd, to_del = CalendarSync._events_list_compare(lst_src_rnd, lst_dst_rnd)
|
to_ins, to_upd, to_del = CalendarSync._events_list_compare(
|
||||||
|
lst_src_rnd, lst_dst_rnd)
|
||||||
|
|
||||||
assert len(to_ins) == part_len
|
assert len(to_ins) == part_len
|
||||||
assert len(to_upd) == part_len
|
assert len(to_upd) == part_len
|
||||||
assert len(to_del) == part_len
|
assert len(to_del) == part_len
|
||||||
|
|
||||||
assert sorted(to_ins, key=lambda x: x["iCalUID"]) == lst_src[:part_len]
|
assert sorted(to_ins, key=lambda x: x['iCalUID']) == lst_src[:part_len]
|
||||||
assert sorted(to_del, key=lambda x: x["iCalUID"]) == lst_dst[part_len:]
|
assert sorted(to_del, key=lambda x: x['iCalUID']) == lst_dst[part_len:]
|
||||||
|
|
||||||
to_upd_ok = list(zip(lst_src[part_len:], lst_dst[:part_len]))
|
to_upd_ok = list(zip(lst_src[part_len:], lst_dst[:part_len]))
|
||||||
assert len(to_upd) == len(to_upd_ok)
|
assert len(to_upd) == len(to_upd_ok)
|
||||||
@@ -118,29 +115,35 @@ def test_compare():
|
|||||||
assert item in to_upd
|
assert item in to_upd
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("no_time", [True, False], ids=["date", "dateTime"])
|
@pytest.mark.parametrize("no_time", [True, False], ids=['date', 'dateTime'])
|
||||||
def test_filter_events_by_date(no_time: bool):
|
def test_filter_events_by_date(no_time: bool):
|
||||||
msk = timezone("Europe/Moscow")
|
msk = timezone('Europe/Moscow')
|
||||||
now = utc.localize(datetime.datetime.utcnow())
|
now = utc.localize(datetime.datetime.utcnow())
|
||||||
msk_now = msk.normalize(now.astimezone(msk))
|
msk_now = msk.normalize(now.astimezone(msk))
|
||||||
|
|
||||||
part_len = 5
|
part_len = 5
|
||||||
|
|
||||||
if no_time:
|
if no_time:
|
||||||
duration = datetime.date(1, 1, 2) - datetime.date(1, 1, 1)
|
duration = datetime.date(
|
||||||
|
1, 1, 2) - datetime.date(1, 1, 1)
|
||||||
else:
|
else:
|
||||||
duration = datetime.datetime(1, 1, 1, 2) - datetime.datetime(1, 1, 1, 1)
|
duration = datetime.datetime(
|
||||||
|
1, 1, 1, 2) - datetime.datetime(1, 1, 1, 1)
|
||||||
|
|
||||||
date_cmp = msk_now + (duration * part_len)
|
date_cmp = msk_now + (duration * part_len)
|
||||||
|
|
||||||
if no_time:
|
if no_time:
|
||||||
date_cmp = datetime.date(date_cmp.year, date_cmp.month, date_cmp.day)
|
date_cmp = datetime.date(
|
||||||
|
date_cmp.year, date_cmp.month, date_cmp.day)
|
||||||
|
|
||||||
events = gen_events(1, 1 + (part_len * 2), msk_now, no_time)
|
events = gen_events(
|
||||||
|
1, 1 + (part_len * 2), msk_now, no_time)
|
||||||
shuffle(events)
|
shuffle(events)
|
||||||
|
|
||||||
events_pending = CalendarSync._filter_events_by_date(events, date_cmp, operator.ge)
|
events_pending = CalendarSync._filter_events_by_date(
|
||||||
events_past = CalendarSync._filter_events_by_date(events, date_cmp, operator.lt)
|
events, date_cmp, operator.ge)
|
||||||
|
events_past = CalendarSync._filter_events_by_date(
|
||||||
|
events, date_cmp, operator.lt)
|
||||||
|
|
||||||
assert len(events_pending) == 1 + part_len
|
assert len(events_pending) == 1 + part_len
|
||||||
assert len(events_past) == part_len - 1
|
assert len(events_past) == part_len - 1
|
||||||
@@ -153,11 +156,12 @@ def test_filter_events_by_date(no_time: bool):
|
|||||||
|
|
||||||
|
|
||||||
def test_filter_events_to_update():
|
def test_filter_events_to_update():
|
||||||
msk = timezone("Europe/Moscow")
|
msk = timezone('Europe/Moscow')
|
||||||
now = utc.localize(datetime.datetime.utcnow())
|
now = utc.localize(datetime.datetime.utcnow())
|
||||||
msk_now = msk.normalize(now.astimezone(msk))
|
msk_now = msk.normalize(now.astimezone(msk))
|
||||||
|
|
||||||
one_hour = datetime.datetime(1, 1, 1, 2) - datetime.datetime(1, 1, 1, 1)
|
one_hour = datetime.datetime(
|
||||||
|
1, 1, 1, 2) - datetime.datetime(1, 1, 1, 1)
|
||||||
date_upd = msk_now + (one_hour * 5)
|
date_upd = msk_now + (one_hour * 5)
|
||||||
|
|
||||||
count = 10
|
count = 10
|
||||||
@@ -174,30 +178,3 @@ def test_filter_events_to_update():
|
|||||||
|
|
||||||
assert len(sync1.to_update) == count
|
assert len(sync1.to_update) == count
|
||||||
assert sync2.to_update == []
|
assert sync2.to_update == []
|
||||||
|
|
||||||
|
|
||||||
def test_filter_events_no_updated():
|
|
||||||
"""
|
|
||||||
test filtering events that not have 'updated' field
|
|
||||||
such events should always pass the filter
|
|
||||||
"""
|
|
||||||
now = datetime.datetime.utcnow()
|
|
||||||
yesterday = now - datetime.timedelta(days=-1)
|
|
||||||
|
|
||||||
count = 10
|
|
||||||
events_old = gen_events(1, 1 + count, now)
|
|
||||||
events_new = gen_events(1, 1 + count, now)
|
|
||||||
|
|
||||||
# 1/2 updated=yesterday, 1/2 no updated field
|
|
||||||
i = 0
|
|
||||||
for event in events_new:
|
|
||||||
if 0 == i % 2:
|
|
||||||
event["updated"] = yesterday.isoformat() + "Z"
|
|
||||||
else:
|
|
||||||
del event["updated"]
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
sync = CalendarSync(None, None)
|
|
||||||
sync.to_update = list(zip(events_old, events_new))
|
|
||||||
sync._filter_events_to_update()
|
|
||||||
assert len(sync.to_update) == count // 2
|
|
||||||
|
|||||||
Reference in New Issue
Block a user