mirror of
https://github.com/b4tman/sync_ics2gcal
synced 2025-01-21 07:28:24 +00:00
commit
a18be3d079
17
.github/workflows/pythonpackage.yml
vendored
17
.github/workflows/pythonpackage.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: ['3.7', '3.8', '3.9', '3.10']
|
||||
python-version: ['3.8', '3.9', '3.10']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -39,3 +39,18 @@ jobs:
|
||||
poetry run flake8 sync_ics2gcal --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with pytest
|
||||
run: poetry run pytest -v
|
||||
|
||||
- name: Check type annotations with mypy
|
||||
run: |
|
||||
mkdir mypy_report
|
||||
poetry run mypy --pretty --html-report mypy_report/ .
|
||||
|
||||
- name: Check type annotations with mypy strict mode (not failing)
|
||||
run: |
|
||||
poetry run mypy --strict --pretty . || true
|
||||
|
||||
- name: Upload mypy report
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: mypy_report
|
||||
path: mypy_report/
|
||||
|
41
.github/workflows/reviewdog.yml
vendored
Normal file
41
.github/workflows/reviewdog.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: reviewdog
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Upgrade pip
|
||||
run: python -m pip install --upgrade pip
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
|
||||
- name: Install deps
|
||||
run: poetry install
|
||||
|
||||
- name: setup mypy
|
||||
run: |
|
||||
mkdir tmp_bin/
|
||||
echo "#!/bin/sh" > tmp_bin/mypy
|
||||
echo "poetry run mypy \$@" >> tmp_bin/mypy
|
||||
chmod +x tmp_bin/mypy
|
||||
echo "$(pwd)/tmp_bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: tsuyoshicho/action-mypy@v3
|
||||
with:
|
||||
reporter: github-pr-review
|
||||
level: warning
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,8 +4,13 @@ service-account.json
|
||||
my-test*.ics
|
||||
.vscode/
|
||||
.idea/
|
||||
.venv/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
/dist/
|
||||
/*.egg-info/
|
||||
/build/
|
||||
/.eggs/
|
||||
venv/
|
||||
mypy_report/
|
||||
tmp_bin/
|
||||
|
363
poetry.lock
generated
363
poetry.lock
generated
@ -45,7 +45,7 @@ uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "5.0.0"
|
||||
version = "5.2.0"
|
||||
description = "Extensible memoizing collections and decorators"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -53,11 +53,11 @@ python-versions = "~=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2021.10.8"
|
||||
version = "2022.5.18.1"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
@ -72,11 +72,11 @@ unicode_backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.0.4"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
@ -118,7 +118,7 @@ pyflakes = ">=2.4.0,<2.5.0"
|
||||
|
||||
[[package]]
|
||||
name = "google-api-core"
|
||||
version = "2.5.0"
|
||||
version = "2.8.1"
|
||||
description = "Google API client core library"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -126,14 +126,14 @@ python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
google-auth = ">=1.25.0,<3.0dev"
|
||||
googleapis-common-protos = ">=1.52.0,<2.0dev"
|
||||
protobuf = ">=3.12.0"
|
||||
googleapis-common-protos = ">=1.56.2,<2.0dev"
|
||||
protobuf = ">=3.15.0,<4.0.0dev"
|
||||
requests = ">=2.18.0,<3.0.0dev"
|
||||
|
||||
[package.extras]
|
||||
grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"]
|
||||
grpcgcp = ["grpcio-gcp (>=0.2.2)"]
|
||||
grpcio-gcp = ["grpcio-gcp (>=0.2.2)"]
|
||||
grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"]
|
||||
grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
@ -184,17 +184,17 @@ six = "*"
|
||||
|
||||
[[package]]
|
||||
name = "googleapis-common-protos"
|
||||
version = "1.54.0"
|
||||
version = "1.56.2"
|
||||
description = "Common protobufs used in Google APIs"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
protobuf = ">=3.12.0"
|
||||
protobuf = ">=3.15.0,<4.0.0dev"
|
||||
|
||||
[package.extras]
|
||||
grpc = ["grpcio (>=1.0.0)"]
|
||||
grpc = ["grpcio (>=1.0.0,<2.0.0dev)"]
|
||||
|
||||
[[package]]
|
||||
name = "httplib2"
|
||||
@ -251,6 +251,20 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "4.9.0"
|
||||
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
|
||||
|
||||
[package.extras]
|
||||
cssselect = ["cssselect (>=0.7)"]
|
||||
html5 = ["html5lib"]
|
||||
htmlsoup = ["beautifulsoup4"]
|
||||
source = ["Cython (>=0.29.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "mccabe"
|
||||
version = "0.6.1"
|
||||
@ -259,6 +273,25 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.960"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=0.4.3"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
|
||||
typing-extensions = ">=3.10"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
python2 = ["typed-ast (>=1.4.0,<2)"]
|
||||
reports = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
@ -288,15 +321,15 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
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)"]
|
||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
@ -315,11 +348,11 @@ testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "3.19.4"
|
||||
version = "3.20.1"
|
||||
description = "Protocol Buffers"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "py"
|
||||
@ -366,14 +399,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.7"
|
||||
description = "Python parsing module"
|
||||
version = "3.0.9"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.6.8"
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["jinja2", "railroad-diagrams"]
|
||||
diagrams = ["railroad-diagrams", "jinja2"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
@ -479,19 +512,43 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "typed-ast"
|
||||
version = "1.5.2"
|
||||
version = "1.5.4"
|
||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.1.1"
|
||||
description = "Backported and Experimental Type Hints for Python 3.6+"
|
||||
name = "types-python-dateutil"
|
||||
version = "2.8.17"
|
||||
description = "Typing stubs for python-dateutil"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-pytz"
|
||||
version = "2021.3.8"
|
||||
description = "Typing stubs for pytz"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.8"
|
||||
description = "Typing stubs for PyYAML"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.2.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "uritemplate"
|
||||
@ -503,33 +560,33 @@ python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.8"
|
||||
version = "1.26.9"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlipy (>=0.6.0)"]
|
||||
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.7.0"
|
||||
version = "3.8.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
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"]
|
||||
docs = ["sphinx", "jaraco.packaging (>=9)", "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 (>=0.9.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "98872c98b9b2ff9d51a549f5d93e030fd77e038c030dec9daa1b0bd3b924e38b"
|
||||
content-hash = "f02b70a57ff445e4b532d74b94b67eaad788e0a40665286dbe16c5e541702b78"
|
||||
|
||||
[metadata.files]
|
||||
atomicwrites = [
|
||||
@ -566,20 +623,20 @@ black = [
|
||||
{file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
|
||||
]
|
||||
cachetools = [
|
||||
{file = "cachetools-5.0.0-py3-none-any.whl", hash = "sha256:8fecd4203a38af17928be7b90689d8083603073622229ca7077b72d8e5a976e4"},
|
||||
{file = "cachetools-5.0.0.tar.gz", hash = "sha256:486471dfa8799eb7ec503a8059e263db000cdda20075ce5e48903087f79d5fd6"},
|
||||
{file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"},
|
||||
{file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"},
|
||||
]
|
||||
certifi = [
|
||||
{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-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"},
|
||||
{file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
|
||||
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
@ -593,8 +650,8 @@ flake8 = [
|
||||
{file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
|
||||
]
|
||||
google-api-core = [
|
||||
{file = "google-api-core-2.5.0.tar.gz", hash = "sha256:f33863a6709651703b8b18b67093514838c79f2b04d02aa501203079f24b8018"},
|
||||
{file = "google_api_core-2.5.0-py2.py3-none-any.whl", hash = "sha256:7d030edbd3a0e994d796e62716022752684e863a6df9864b6ca82a1616c2a5a6"},
|
||||
{file = "google-api-core-2.8.1.tar.gz", hash = "sha256:958024c6aa3460b08f35741231076a4dd9a4c819a6a39d44da9627febe8b28f0"},
|
||||
{file = "google_api_core-2.8.1-py3-none-any.whl", hash = "sha256:ce1daa49644b50398093d2a9ad886501aa845e2602af70c3001b9f402a9d7359"},
|
||||
]
|
||||
google-api-python-client = [
|
||||
{file = "google-api-python-client-2.49.0.tar.gz", hash = "sha256:629bbde991ce2d9697c6da37f2416f7aeb01ba01505b166066a415b3c3ce1dfc"},
|
||||
@ -609,8 +666,8 @@ google-auth-httplib2 = [
|
||||
{file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"},
|
||||
]
|
||||
googleapis-common-protos = [
|
||||
{file = "googleapis-common-protos-1.54.0.tar.gz", hash = "sha256:a4031d6ec6c2b1b6dc3e0be7e10a1bd72fb0b18b07ef9be7b51f2c1004ce2437"},
|
||||
{file = "googleapis_common_protos-1.54.0-py2.py3-none-any.whl", hash = "sha256:e54345a2add15dc5e1a7891c27731ff347b4c33765d79b5ed7026a6c0c7cbcae"},
|
||||
{file = "googleapis-common-protos-1.56.2.tar.gz", hash = "sha256:b09b56f5463070c2153753ef123f07d2e49235e89148e9b2459ec8ed2f68d7d3"},
|
||||
{file = "googleapis_common_protos-1.56.2-py2.py3-none-any.whl", hash = "sha256:023eaea9d8c1cceccd9587c6af6c20f33eeeb05d4148670f2b0322dc1511700c"},
|
||||
]
|
||||
httplib2 = [
|
||||
{file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"},
|
||||
@ -632,10 +689,100 @@ iniconfig = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
]
|
||||
lxml = [
|
||||
{file = "lxml-4.9.0-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b5031d151d6147eac53366d6ec87da84cd4d8c5e80b1d9948a667a7164116e39"},
|
||||
{file = "lxml-4.9.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5d52e1173f52020392f593f87a6af2d4055dd800574a5cb0af4ea3878801d307"},
|
||||
{file = "lxml-4.9.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3af00ee88376022589ceeb8170eb67dacf5f7cd625ea59fa0977d719777d4ae8"},
|
||||
{file = "lxml-4.9.0-cp27-cp27m-win32.whl", hash = "sha256:1057356b808d149bc14eb8f37bb89129f237df488661c1e0fc0376ca90e1d2c3"},
|
||||
{file = "lxml-4.9.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f6d23a01921b741774f35e924d418a43cf03eca1444f3fdfd7978d35a5aaab8b"},
|
||||
{file = "lxml-4.9.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56e19fb6e4b8bd07fb20028d03d3bc67bcc0621347fbde64f248e44839771756"},
|
||||
{file = "lxml-4.9.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4cd69bca464e892ea4ed544ba6a7850aaff6f8d792f8055a10638db60acbac18"},
|
||||
{file = "lxml-4.9.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:94b181dd2777890139e49a5336bf3a9a3378ce66132c665fe8db4e8b7683cde2"},
|
||||
{file = "lxml-4.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:607224ffae9a0cf0a2f6e14f5f6bce43e83a6fbdaa647891729c103bdd6a5593"},
|
||||
{file = "lxml-4.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:11d62c97ceff9bab94b6b29c010ea5fb6831743459bb759c917f49ba75601cd0"},
|
||||
{file = "lxml-4.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:70a198030d26f5e569367f0f04509b63256faa76a22886280eea69a4f535dd40"},
|
||||
{file = "lxml-4.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3cf816aed8125cfc9e6e5c6c31ff94278320d591bd7970c4a0233bee0d1c8790"},
|
||||
{file = "lxml-4.9.0-cp310-cp310-win32.whl", hash = "sha256:65b3b5f12c6fb5611e79157214f3cd533083f9b058bf2fc8a1c5cc5ee40fdc5a"},
|
||||
{file = "lxml-4.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:0aa4cce579512c33373ca4c5e23c21e40c1aa1a33533a75e51b654834fd0e4f2"},
|
||||
{file = "lxml-4.9.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63419db39df8dc5564f6f103102c4665f7e4d9cb64030e98cf7a74eae5d5760d"},
|
||||
{file = "lxml-4.9.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d8e5021e770b0a3084c30dda5901d5fce6d4474feaf0ced8f8e5a82702502fbb"},
|
||||
{file = "lxml-4.9.0-cp35-cp35m-win32.whl", hash = "sha256:f17b9df97c5ecdfb56c5e85b3c9df9831246df698f8581c6e111ac664c7c656e"},
|
||||
{file = "lxml-4.9.0-cp35-cp35m-win_amd64.whl", hash = "sha256:75da29a0752c8f2395df0115ac1681cefbdd4418676015be8178b733704cbff2"},
|
||||
{file = "lxml-4.9.0-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:e4d020ecf3740b7312bacab2cb966bb720fd4d3490562d373b4ad91dd1857c0d"},
|
||||
{file = "lxml-4.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b71c52d69b91af7d18c13aef1b0cc3baee36b78607c711eb14a52bf3aa7c815e"},
|
||||
{file = "lxml-4.9.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cf04a1a38e961d4a764d2940af9b941b66263ed5584392ef875ee9c1e360a3"},
|
||||
{file = "lxml-4.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:915ecf7d486df17cc65aeefdb680d5ad4390cc8c857cf8db3fe241ed234f856a"},
|
||||
{file = "lxml-4.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e564d5a771b4015f34166a05ea2165b7e283635c41b1347696117f780084b46d"},
|
||||
{file = "lxml-4.9.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c2a57755e366e0ac7ebdb3e9207f159c3bf1afed02392ab18453ce81f5ee92ee"},
|
||||
{file = "lxml-4.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:00f3a6f88fd5f4357844dd91a1abac5f466c6799f1b7f1da2df6665253845b11"},
|
||||
{file = "lxml-4.9.0-cp36-cp36m-win32.whl", hash = "sha256:9093a359a86650a3dbd6532c3e4d21a6f58ba2cb60d0e72db0848115d24c10ba"},
|
||||
{file = "lxml-4.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d1690c4d37674a5f0cdafbc5ed7e360800afcf06928c2a024c779c046891bf09"},
|
||||
{file = "lxml-4.9.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:6af7f51a6010748fc1bb71917318d953c9673e4ae3f6d285aaf93ef5b2eb11c1"},
|
||||
{file = "lxml-4.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:eabdbe04ee0a7e760fa6cd9e799d2b020d098c580ba99107d52e1e5e538b1ecb"},
|
||||
{file = "lxml-4.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b1e22f3ee4d75ca261b6bffbf64f6f178cb194b1be3191065a09f8d98828daa9"},
|
||||
{file = "lxml-4.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:53b0410b220766321759f7f9066da67b1d0d4a7f6636a477984cbb1d98483955"},
|
||||
{file = "lxml-4.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d76da27f5e3e9bc40eba6ed7a9e985f57547e98cf20521d91215707f2fb57e0f"},
|
||||
{file = "lxml-4.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:686565ac77ff94a8965c11829af253d9e2ce3bf0d9225b1d2eb5c4d4666d0dca"},
|
||||
{file = "lxml-4.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b62d1431b4c40cda43cc986f19b8c86b1d2ae8918cfc00f4776fdf070b65c0c4"},
|
||||
{file = "lxml-4.9.0-cp37-cp37m-win32.whl", hash = "sha256:4becd16750ca5c2a1b1588269322b2cebd10c07738f336c922b658dbab96a61c"},
|
||||
{file = "lxml-4.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e35a298691b9e10e5a5631f8f0ba605b30ebe19208dc8f58b670462f53753641"},
|
||||
{file = "lxml-4.9.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:aa7447bf7c1a15ef24e2b86a277b585dd3f055e8890ac7f97374d170187daa97"},
|
||||
{file = "lxml-4.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:612ef8f2795a89ba3a1d4c8c1af84d8453fd53ee611aa5ad460fdd2cab426fc2"},
|
||||
{file = "lxml-4.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:1bfb791a8fcdbf55d1d41b8be940393687bec0e9b12733f0796668086d1a23ff"},
|
||||
{file = "lxml-4.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:024684e0c5cfa121c22140d3a0898a3a9b2ea0f0fd2c229b6658af4bdf1155e5"},
|
||||
{file = "lxml-4.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81c29c8741fa07ecec8ec7417c3d8d1e2f18cf5a10a280f4e1c3f8c3590228b2"},
|
||||
{file = "lxml-4.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6467626fa74f96f4d80fc6ec2555799e97fff8f36e0bfc7f67769f83e59cff40"},
|
||||
{file = "lxml-4.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9cae837b988f44925d14d048fa6a8c54f197c8b1223fd9ee9c27084f84606143"},
|
||||
{file = "lxml-4.9.0-cp38-cp38-win32.whl", hash = "sha256:5a49ad78543925e1a4196e20c9c54492afa4f1502c2a563f73097e2044c75190"},
|
||||
{file = "lxml-4.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:bb7c1b029e54e26e01b1d1d912fc21abb65650d16ea9a191d026def4ed0859ed"},
|
||||
{file = "lxml-4.9.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d0d03b9636f1326772e6854459728676354d4c7731dae9902b180e2065ba3da6"},
|
||||
{file = "lxml-4.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:9af19eb789d674b59a9bee5005779757aab857c40bf9cc313cb01eafac55ce55"},
|
||||
{file = "lxml-4.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:dd00d28d1ab5fa7627f5abc957f29a6338a7395b724571a8cbff8fbed83aaa82"},
|
||||
{file = "lxml-4.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:754a1dd04bff8a509a31146bd8f3a5dc8191a8694d582dd5fb71ff09f0722c22"},
|
||||
{file = "lxml-4.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7679344f2270840dc5babc9ccbedbc04f7473c1f66d4676bb01680c0db85bcc"},
|
||||
{file = "lxml-4.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d882c2f3345261e898b9f604be76b61c901fbfa4ac32e3f51d5dc1edc89da3cb"},
|
||||
{file = "lxml-4.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e97c8fc761ad63909198acc892f34c20f37f3baa2c50a62d5ec5d7f1efc68a1"},
|
||||
{file = "lxml-4.9.0-cp39-cp39-win32.whl", hash = "sha256:cf9ec915857d260511399ab87e1e70fa13d6b2972258f8e620a3959468edfc32"},
|
||||
{file = "lxml-4.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:1254a79f8a67a3908de725caf59eae62d86738f6387b0a34b32e02abd6ae73db"},
|
||||
{file = "lxml-4.9.0-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:03370ec37fe562238d385e2c53089076dee53aabf8325cab964fdb04a9130fa0"},
|
||||
{file = "lxml-4.9.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f386def57742aacc3d864169dfce644a8c396f95aa35b41b69df53f558d56dd0"},
|
||||
{file = "lxml-4.9.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ea3f2e9eb41f973f73619e88bf7bd950b16b4c2ce73d15f24a11800ce1eaf276"},
|
||||
{file = "lxml-4.9.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d10659e6e5c53298e6d718fd126e793285bff904bb71d7239a17218f6a197b7"},
|
||||
{file = "lxml-4.9.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:fcdf70191f0d1761d190a436db06a46f05af60e1410e1507935f0332280c9268"},
|
||||
{file = "lxml-4.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:2b9c2341d96926b0d0e132e5c49ef85eb53fa92ae1c3a70f9072f3db0d32bc07"},
|
||||
{file = "lxml-4.9.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:615886ee84b6f42f1bdf1852a9669b5fe3b96b6ff27f1a7a330b67ad9911200a"},
|
||||
{file = "lxml-4.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:94f2e45b054dd759bed137b6e14ae8625495f7d90ddd23cf62c7a68f72b62656"},
|
||||
{file = "lxml-4.9.0.tar.gz", hash = "sha256:520461c36727268a989790aef08884347cd41f2d8ae855489ccf40b50321d8d7"},
|
||||
]
|
||||
mccabe = [
|
||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||
]
|
||||
mypy = [
|
||||
{file = "mypy-0.960-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3a3e525cd76c2c4f90f1449fd034ba21fcca68050ff7c8397bb7dd25dd8b8248"},
|
||||
{file = "mypy-0.960-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7a76dc4f91e92db119b1be293892df8379b08fd31795bb44e0ff84256d34c251"},
|
||||
{file = "mypy-0.960-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffdad80a92c100d1b0fe3d3cf1a4724136029a29afe8566404c0146747114382"},
|
||||
{file = "mypy-0.960-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7d390248ec07fa344b9f365e6ed9d205bd0205e485c555bed37c4235c868e9d5"},
|
||||
{file = "mypy-0.960-cp310-cp310-win_amd64.whl", hash = "sha256:925aa84369a07846b7f3b8556ccade1f371aa554f2bd4fb31cb97a24b73b036e"},
|
||||
{file = "mypy-0.960-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:239d6b2242d6c7f5822163ee082ef7a28ee02e7ac86c35593ef923796826a385"},
|
||||
{file = "mypy-0.960-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f1ba54d440d4feee49d8768ea952137316d454b15301c44403db3f2cb51af024"},
|
||||
{file = "mypy-0.960-cp36-cp36m-win_amd64.whl", hash = "sha256:cb7752b24528c118a7403ee955b6a578bfcf5879d5ee91790667c8ea511d2085"},
|
||||
{file = "mypy-0.960-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:826a2917c275e2ee05b7c7b736c1e6549a35b7ea5a198ca457f8c2ebea2cbecf"},
|
||||
{file = "mypy-0.960-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3eabcbd2525f295da322dff8175258f3fc4c3eb53f6d1929644ef4d99b92e72d"},
|
||||
{file = "mypy-0.960-cp37-cp37m-win_amd64.whl", hash = "sha256:f47322796c412271f5aea48381a528a613f33e0a115452d03ae35d673e6064f8"},
|
||||
{file = "mypy-0.960-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2c7f8bb9619290836a4e167e2ef1f2cf14d70e0bc36c04441e41487456561409"},
|
||||
{file = "mypy-0.960-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbfb873cf2b8d8c3c513367febde932e061a5f73f762896826ba06391d932b2a"},
|
||||
{file = "mypy-0.960-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc537885891382e08129d9862553b3d00d4be3eb15b8cae9e2466452f52b0117"},
|
||||
{file = "mypy-0.960-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:481f98c6b24383188c928f33dd2f0776690807e12e9989dd0419edd5c74aa53b"},
|
||||
{file = "mypy-0.960-cp38-cp38-win_amd64.whl", hash = "sha256:29dc94d9215c3eb80ac3c2ad29d0c22628accfb060348fd23d73abe3ace6c10d"},
|
||||
{file = "mypy-0.960-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:33d53a232bb79057f33332dbbb6393e68acbcb776d2f571ba4b1d50a2c8ba873"},
|
||||
{file = "mypy-0.960-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d645e9e7f7a5da3ec3bbcc314ebb9bb22c7ce39e70367830eb3c08d0140b9ce"},
|
||||
{file = "mypy-0.960-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85cf2b14d32b61db24ade8ac9ae7691bdfc572a403e3cb8537da936e74713275"},
|
||||
{file = "mypy-0.960-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a85a20b43fa69efc0b955eba1db435e2ffecb1ca695fe359768e0503b91ea89f"},
|
||||
{file = "mypy-0.960-cp39-cp39-win_amd64.whl", hash = "sha256:0ebfb3f414204b98c06791af37a3a96772203da60636e2897408517fcfeee7a8"},
|
||||
{file = "mypy-0.960-py3-none-any.whl", hash = "sha256:bfd4f6536bd384c27c392a8b8f790fd0ed5c0cf2f63fc2fed7bce56751d53026"},
|
||||
{file = "mypy-0.960.tar.gz", hash = "sha256:d4fccf04c1acf750babd74252e0f2db6bd2ac3aa8fe960797d9f3ef41cf2bfd4"},
|
||||
]
|
||||
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"},
|
||||
@ -649,40 +796,38 @@ pathspec = [
|
||||
{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"},
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
protobuf = [
|
||||
{file = "protobuf-3.19.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f51d5a9f137f7a2cec2d326a74b6e3fc79d635d69ffe1b036d39fc7d75430d37"},
|
||||
{file = "protobuf-3.19.4-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09297b7972da685ce269ec52af761743714996b4381c085205914c41fcab59fb"},
|
||||
{file = "protobuf-3.19.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072fbc78d705d3edc7ccac58a62c4c8e0cec856987da7df8aca86e647be4e35c"},
|
||||
{file = "protobuf-3.19.4-cp310-cp310-win32.whl", hash = "sha256:7bb03bc2873a2842e5ebb4801f5c7ff1bfbdf426f85d0172f7644fcda0671ae0"},
|
||||
{file = "protobuf-3.19.4-cp310-cp310-win_amd64.whl", hash = "sha256:f358aa33e03b7a84e0d91270a4d4d8f5df6921abe99a377828839e8ed0c04e07"},
|
||||
{file = "protobuf-3.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1c91ef4110fdd2c590effb5dca8fdbdcb3bf563eece99287019c4204f53d81a4"},
|
||||
{file = "protobuf-3.19.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c438268eebb8cf039552897d78f402d734a404f1360592fef55297285f7f953f"},
|
||||
{file = "protobuf-3.19.4-cp36-cp36m-win32.whl", hash = "sha256:835a9c949dc193953c319603b2961c5c8f4327957fe23d914ca80d982665e8ee"},
|
||||
{file = "protobuf-3.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4276cdec4447bd5015453e41bdc0c0c1234eda08420b7c9a18b8d647add51e4b"},
|
||||
{file = "protobuf-3.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6cbc312be5e71869d9d5ea25147cdf652a6781cf4d906497ca7690b7b9b5df13"},
|
||||
{file = "protobuf-3.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54a1473077f3b616779ce31f477351a45b4fef8c9fd7892d6d87e287a38df368"},
|
||||
{file = "protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:435bb78b37fc386f9275a7035fe4fb1364484e38980d0dd91bc834a02c5ec909"},
|
||||
{file = "protobuf-3.19.4-cp37-cp37m-win32.whl", hash = "sha256:16f519de1313f1b7139ad70772e7db515b1420d208cb16c6d7858ea989fc64a9"},
|
||||
{file = "protobuf-3.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc076c03381f5c1d9bb1abdcc5503d9ca8b53cf0a9d31a9f6754ec9e6c8af0f"},
|
||||
{file = "protobuf-3.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69da7d39e39942bd52848438462674c463e23963a1fdaa84d88df7fbd7e749b2"},
|
||||
{file = "protobuf-3.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48ed3877fa43e22bcacc852ca76d4775741f9709dd9575881a373bd3e85e54b2"},
|
||||
{file = "protobuf-3.19.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd95d1dfb9c4f4563e6093a9aa19d9c186bf98fa54da5252531cc0d3a07977e7"},
|
||||
{file = "protobuf-3.19.4-cp38-cp38-win32.whl", hash = "sha256:b38057450a0c566cbd04890a40edf916db890f2818e8682221611d78dc32ae26"},
|
||||
{file = "protobuf-3.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:7ca7da9c339ca8890d66958f5462beabd611eca6c958691a8fe6eccbd1eb0c6e"},
|
||||
{file = "protobuf-3.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36cecbabbda242915529b8ff364f2263cd4de7c46bbe361418b5ed859677ba58"},
|
||||
{file = "protobuf-3.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c1068287025f8ea025103e37d62ffd63fec8e9e636246b89c341aeda8a67c934"},
|
||||
{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"},
|
||||
{file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"},
|
||||
{file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"},
|
||||
{file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"},
|
||||
{file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"},
|
||||
{file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"},
|
||||
{file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"},
|
||||
{file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"},
|
||||
{file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"},
|
||||
{file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"},
|
||||
{file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"},
|
||||
{file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"},
|
||||
{file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"},
|
||||
{file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"},
|
||||
{file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"},
|
||||
{file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"},
|
||||
{file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"},
|
||||
{file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"},
|
||||
{file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"},
|
||||
{file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"},
|
||||
{file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"},
|
||||
{file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"},
|
||||
{file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"},
|
||||
{file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"},
|
||||
{file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
@ -727,8 +872,8 @@ pyflakes = [
|
||||
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
|
||||
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
|
||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
|
||||
@ -797,44 +942,56 @@ tomli = [
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
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"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
|
||||
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
|
||||
]
|
||||
types-python-dateutil = [
|
||||
{file = "types-python-dateutil-2.8.17.tar.gz", hash = "sha256:6c54265a221681dd87f61df6743bd5eab060cf1b4086ff65c1a8fd763ed6370e"},
|
||||
{file = "types_python_dateutil-2.8.17-py3-none-any.whl", hash = "sha256:0be7435b4d382d1cd00b8c55a8a90f4e515aaad8a96f8f0bc20c22df046792e5"},
|
||||
]
|
||||
types-pytz = [
|
||||
{file = "types-pytz-2021.3.8.tar.gz", hash = "sha256:41253a3a2bf028b6a3f17b58749a692d955af0f74e975de94f6f4d2d3cd01dbd"},
|
||||
{file = "types_pytz-2021.3.8-py3-none-any.whl", hash = "sha256:aef4a917ab28c585d3f474bfce4f4b44b91e95d9d47d4de29dd845e0db8e3910"},
|
||||
]
|
||||
types-pyyaml = [
|
||||
{file = "types-PyYAML-6.0.8.tar.gz", hash = "sha256:d9495d377bb4f9c5387ac278776403eb3b4bb376851025d913eea4c22b4c6438"},
|
||||
{file = "types_PyYAML-6.0.8-py3-none-any.whl", hash = "sha256:56a7b0e8109602785f942a11ebfbd16e97d5d0e79f5fbb077ec4e6a0004837ff"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
|
||||
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
|
||||
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
|
||||
{file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
|
||||
]
|
||||
uritemplate = [
|
||||
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
|
||||
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
|
||||
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
|
||||
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
|
||||
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"},
|
||||
{file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"},
|
||||
{file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"},
|
||||
{file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
|
||||
]
|
||||
|
@ -11,14 +11,13 @@ keywords = ["icalendar", "sync", "google", "calendar"]
|
||||
classifiers = [
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
python = "^3.8"
|
||||
google-auth = "2.6.6"
|
||||
google-api-python-client = "2.49.0"
|
||||
icalendar = "4.0.9"
|
||||
@ -30,6 +29,11 @@ fire = "0.4.0"
|
||||
pytest = "^7.1.2"
|
||||
flake8 = "^4.0.1"
|
||||
black = "^22.3.0"
|
||||
mypy = ">=0.960"
|
||||
types-python-dateutil = "^2.8.17"
|
||||
types-pytz = ">=2021.3.8"
|
||||
types-PyYAML = "^6.0.8"
|
||||
lxml = "^4.9.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
sync-ics2gcal = "sync_ics2gcal.sync_calendar:main"
|
||||
@ -38,3 +42,12 @@ manage-ics2gcal = "sync_ics2gcal.manage_calendars:main"
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
'icalendar',
|
||||
'google.*',
|
||||
'googleapiclient',
|
||||
'fire'
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
|
@ -6,6 +6,39 @@ from .gcal import (
|
||||
EventData,
|
||||
EventList,
|
||||
EventTuple,
|
||||
EventDataKey,
|
||||
EventDateOrDateTime,
|
||||
EventDate,
|
||||
EventDateTime,
|
||||
EventsSearchResults,
|
||||
ACLRule,
|
||||
ACLScope,
|
||||
CalendarData,
|
||||
BatchRequestCallback,
|
||||
)
|
||||
|
||||
from .sync import CalendarSync
|
||||
from .sync import CalendarSync, ComparedEvents
|
||||
|
||||
__all__ = [
|
||||
"ical",
|
||||
"gcal",
|
||||
"sync",
|
||||
"CalendarConverter",
|
||||
"EventConverter",
|
||||
"DateDateTime",
|
||||
"GoogleCalendarService",
|
||||
"GoogleCalendar",
|
||||
"EventData",
|
||||
"EventList",
|
||||
"EventTuple",
|
||||
"EventDataKey",
|
||||
"EventDateOrDateTime",
|
||||
"EventDate",
|
||||
"EventDateTime",
|
||||
"EventsSearchResults",
|
||||
"ACLRule",
|
||||
"ACLScope",
|
||||
"CalendarData",
|
||||
"CalendarSync",
|
||||
"ComparedEvents",
|
||||
]
|
||||
|
@ -1,17 +1,97 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any, Callable, Tuple, Optional, Union
|
||||
from typing import (
|
||||
List,
|
||||
Dict,
|
||||
Any,
|
||||
Callable,
|
||||
Tuple,
|
||||
Optional,
|
||||
Union,
|
||||
TypedDict,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
)
|
||||
|
||||
import google.auth
|
||||
from google.oauth2 import service_account
|
||||
from googleapiclient import discovery
|
||||
from pytz import utc
|
||||
|
||||
EventData = Dict[str, Union[str, "EventData", None]]
|
||||
|
||||
class EventDate(TypedDict, total=False):
|
||||
date: str
|
||||
timeZone: str
|
||||
|
||||
|
||||
class EventDateTime(TypedDict, total=False):
|
||||
dateTime: str
|
||||
timeZone: str
|
||||
|
||||
|
||||
EventDateOrDateTime = Union[EventDate, EventDateTime]
|
||||
|
||||
|
||||
class ACLScope(TypedDict, total=False):
|
||||
type: str
|
||||
value: str
|
||||
|
||||
|
||||
class ACLRule(TypedDict, total=False):
|
||||
scope: ACLScope
|
||||
role: str
|
||||
|
||||
|
||||
class CalendarData(TypedDict, total=False):
|
||||
id: str
|
||||
summary: str
|
||||
description: str
|
||||
timeZone: str
|
||||
|
||||
|
||||
class EventData(TypedDict, total=False):
|
||||
id: str
|
||||
summary: str
|
||||
description: str
|
||||
start: EventDateOrDateTime
|
||||
end: EventDateOrDateTime
|
||||
iCalUID: str
|
||||
location: str
|
||||
status: str
|
||||
created: str
|
||||
updated: str
|
||||
sequence: int
|
||||
transparency: str
|
||||
visibility: str
|
||||
|
||||
|
||||
EventDataKey = Union[
|
||||
Literal["id"],
|
||||
Literal["summary"],
|
||||
Literal["description"],
|
||||
Literal["start"],
|
||||
Literal["end"],
|
||||
Literal["iCalUID"],
|
||||
Literal["location"],
|
||||
Literal["status"],
|
||||
Literal["created"],
|
||||
Literal["updated"],
|
||||
Literal["sequence"],
|
||||
Literal["transparency"],
|
||||
Literal["visibility"],
|
||||
]
|
||||
EventList = List[EventData]
|
||||
EventTuple = Tuple[EventData, EventData]
|
||||
|
||||
|
||||
class EventsSearchResults(NamedTuple):
|
||||
exists: List[EventTuple]
|
||||
new: List[EventData]
|
||||
|
||||
|
||||
BatchRequestCallback = Callable[[str, Any, Optional[Exception]], None]
|
||||
|
||||
|
||||
class GoogleCalendarService:
|
||||
"""class for make google calendar service Resource
|
||||
|
||||
@ -20,7 +100,7 @@ class GoogleCalendarService:
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def default():
|
||||
def default() -> discovery.Resource:
|
||||
"""make service Resource from default credentials (authorize)
|
||||
( https://developers.google.com/identity/protocols/application-default-credentials )
|
||||
( https://googleapis.dev/python/google-auth/latest/reference/google.auth.html#google.auth.default )
|
||||
@ -34,7 +114,7 @@ class GoogleCalendarService:
|
||||
return service
|
||||
|
||||
@staticmethod
|
||||
def from_srv_acc_file(service_account_file: str):
|
||||
def from_srv_acc_file(service_account_file: str) -> discovery.Resource:
|
||||
"""make service Resource from service account filename (authorize)"""
|
||||
|
||||
scopes = ["https://www.googleapis.com/auth/calendar"]
|
||||
@ -48,7 +128,7 @@ class GoogleCalendarService:
|
||||
return service
|
||||
|
||||
@staticmethod
|
||||
def from_config(config: Optional[Dict[str, Optional[str]]] = None):
|
||||
def from_config(config: Optional[Dict[str, str]] = None) -> discovery.Resource:
|
||||
"""make service Resource from config dict
|
||||
|
||||
Arguments:
|
||||
@ -60,7 +140,8 @@ class GoogleCalendarService:
|
||||
"""
|
||||
|
||||
if config is not None and "service_account" in config:
|
||||
service = GoogleCalendarService.from_srv_acc_file(config["service_account"])
|
||||
service_account_filename: str = config["service_account"]
|
||||
service = GoogleCalendarService.from_srv_acc_file(service_account_filename)
|
||||
else:
|
||||
service = GoogleCalendarService.default()
|
||||
return service
|
||||
@ -76,7 +157,7 @@ def select_event_key(event: EventData) -> Optional[str]:
|
||||
key name or None if no key found
|
||||
"""
|
||||
|
||||
key = None
|
||||
key: Optional[str] = None
|
||||
if "iCalUID" in event:
|
||||
key = "iCalUID"
|
||||
elif "id" in event:
|
||||
@ -91,9 +172,11 @@ class GoogleCalendar:
|
||||
|
||||
def __init__(self, service: discovery.Resource, calendar_id: Optional[str]):
|
||||
self.service: discovery.Resource = service
|
||||
self.calendar_id: str = calendar_id
|
||||
self.calendar_id: str = str(calendar_id)
|
||||
|
||||
def _make_request_callback(self, action: str, events_by_req: EventList) -> Callable:
|
||||
def _make_request_callback(
|
||||
self, action: str, events_by_req: EventList
|
||||
) -> BatchRequestCallback:
|
||||
"""make callback for log result of batch request
|
||||
|
||||
Arguments:
|
||||
@ -104,9 +187,12 @@ class GoogleCalendar:
|
||||
callback function
|
||||
"""
|
||||
|
||||
def callback(request_id, response, exception):
|
||||
event = events_by_req[int(request_id)]
|
||||
key = select_event_key(event)
|
||||
def callback(
|
||||
request_id: str, response: Any, exception: Optional[Exception]
|
||||
) -> None:
|
||||
event: EventData = events_by_req[int(request_id)]
|
||||
event_key: Optional[str] = select_event_key(event)
|
||||
key: str = event_key if event_key is not None else ""
|
||||
|
||||
if exception is not None:
|
||||
self.logger.error(
|
||||
@ -117,7 +203,7 @@ class GoogleCalendar:
|
||||
str(exception),
|
||||
)
|
||||
else:
|
||||
resp_key = select_event_key(response)
|
||||
resp_key: Optional[str] = select_event_key(response)
|
||||
if resp_key is not None:
|
||||
event = response
|
||||
key = resp_key
|
||||
@ -127,10 +213,10 @@ class GoogleCalendar:
|
||||
|
||||
def list_events_from(self, start: datetime) -> EventList:
|
||||
"""list events from calendar, where start date >= start"""
|
||||
fields = "nextPageToken,items(id,iCalUID,updated)"
|
||||
events = []
|
||||
page_token = None
|
||||
time_min = (
|
||||
fields: str = "nextPageToken,items(id,iCalUID,updated)"
|
||||
events: EventList = []
|
||||
page_token: Optional[str] = None
|
||||
time_min: str = (
|
||||
utc.normalize(start.astimezone(utc)).replace(tzinfo=None).isoformat() + "Z"
|
||||
)
|
||||
while True:
|
||||
@ -153,25 +239,27 @@ class GoogleCalendar:
|
||||
self.logger.info("%d events listed", len(events))
|
||||
return events
|
||||
|
||||
def find_exists(self, events: List) -> Tuple[List[EventTuple], EventList]:
|
||||
def find_exists(self, events: EventList) -> EventsSearchResults:
|
||||
"""find existing events from list, by 'iCalUID' field
|
||||
|
||||
Arguments:
|
||||
events {list} -- list of events
|
||||
|
||||
Returns:
|
||||
tuple -- (events_exist, events_not_found)
|
||||
EventsSearchResults -- (events_exist, events_not_found)
|
||||
events_exist - list of tuples: (new_event, exists_event)
|
||||
"""
|
||||
|
||||
fields = "items(id,iCalUID,updated)"
|
||||
events_by_req = []
|
||||
exists = []
|
||||
not_found = []
|
||||
fields: str = "items(id,iCalUID,updated)"
|
||||
events_by_req: EventList = []
|
||||
exists: List[EventTuple] = []
|
||||
not_found: EventList = []
|
||||
|
||||
def list_callback(request_id, response, exception):
|
||||
found = False
|
||||
cur_event = events_by_req[int(request_id)]
|
||||
def list_callback(
|
||||
request_id: str, response: Any, exception: Optional[Exception]
|
||||
) -> None:
|
||||
found: bool = False
|
||||
cur_event: EventData = events_by_req[int(request_id)]
|
||||
if exception is None:
|
||||
found = [] != response["items"]
|
||||
else:
|
||||
@ -186,7 +274,7 @@ class GoogleCalendar:
|
||||
not_found.append(events_by_req[int(request_id)])
|
||||
|
||||
batch = self.service.new_batch_http_request(callback=list_callback)
|
||||
i = 0
|
||||
i: int = 0
|
||||
for event in events:
|
||||
events_by_req.append(event)
|
||||
batch.add(
|
||||
@ -201,21 +289,21 @@ class GoogleCalendar:
|
||||
i += 1
|
||||
batch.execute()
|
||||
self.logger.info("%d events exists, %d not found", len(exists), len(not_found))
|
||||
return exists, not_found
|
||||
return EventsSearchResults(exists, not_found)
|
||||
|
||||
def insert_events(self, events: EventList):
|
||||
def insert_events(self, events: EventList) -> None:
|
||||
"""insert list of events
|
||||
|
||||
Arguments:
|
||||
events - events list
|
||||
"""
|
||||
|
||||
fields = "id"
|
||||
events_by_req = []
|
||||
fields: str = "id"
|
||||
events_by_req: EventList = []
|
||||
|
||||
insert_callback = self._make_request_callback("insert", events_by_req)
|
||||
batch = self.service.new_batch_http_request(callback=insert_callback)
|
||||
i = 0
|
||||
i: int = 0
|
||||
for event in events:
|
||||
events_by_req.append(event)
|
||||
batch.add(
|
||||
@ -227,19 +315,19 @@ class GoogleCalendar:
|
||||
i += 1
|
||||
batch.execute()
|
||||
|
||||
def patch_events(self, event_tuples: List[EventTuple]):
|
||||
def patch_events(self, event_tuples: List[EventTuple]) -> None:
|
||||
"""patch (update) events
|
||||
|
||||
Arguments:
|
||||
event_tuples -- list of tuples: (new_event, exists_event)
|
||||
"""
|
||||
|
||||
fields = "id"
|
||||
events_by_req = []
|
||||
fields: str = "id"
|
||||
events_by_req: EventList = []
|
||||
|
||||
patch_callback = self._make_request_callback("patch", events_by_req)
|
||||
batch = self.service.new_batch_http_request(callback=patch_callback)
|
||||
i = 0
|
||||
i: int = 0
|
||||
for event_new, event_old in event_tuples:
|
||||
if "id" not in event_old:
|
||||
continue
|
||||
@ -254,19 +342,19 @@ class GoogleCalendar:
|
||||
i += 1
|
||||
batch.execute()
|
||||
|
||||
def update_events(self, event_tuples: List[EventTuple]):
|
||||
def update_events(self, event_tuples: List[EventTuple]) -> None:
|
||||
"""update events
|
||||
|
||||
Arguments:
|
||||
event_tuples -- list of tuples: (new_event, exists_event)
|
||||
"""
|
||||
|
||||
fields = "id"
|
||||
events_by_req = []
|
||||
fields: str = "id"
|
||||
events_by_req: EventList = []
|
||||
|
||||
update_callback = self._make_request_callback("update", events_by_req)
|
||||
batch = self.service.new_batch_http_request(callback=update_callback)
|
||||
i = 0
|
||||
i: int = 0
|
||||
for event_new, event_old in event_tuples:
|
||||
if "id" not in event_old:
|
||||
continue
|
||||
@ -283,18 +371,18 @@ class GoogleCalendar:
|
||||
i += 1
|
||||
batch.execute()
|
||||
|
||||
def delete_events(self, events: EventList):
|
||||
def delete_events(self, events: EventList) -> None:
|
||||
"""delete events
|
||||
|
||||
Arguments:
|
||||
events -- list of events
|
||||
"""
|
||||
|
||||
events_by_req = []
|
||||
events_by_req: EventList = []
|
||||
|
||||
delete_callback = self._make_request_callback("delete", events_by_req)
|
||||
batch = self.service.new_batch_http_request(callback=delete_callback)
|
||||
i = 0
|
||||
i: int = 0
|
||||
for event in events:
|
||||
events_by_req.append(event)
|
||||
batch.add(
|
||||
@ -319,7 +407,7 @@ class GoogleCalendar:
|
||||
calendar Resource
|
||||
"""
|
||||
|
||||
calendar = {"summary": summary}
|
||||
calendar = CalendarData(summary=summary)
|
||||
if time_zone is not None:
|
||||
calendar["timeZone"] = time_zone
|
||||
|
||||
@ -327,42 +415,27 @@ class GoogleCalendar:
|
||||
self.calendar_id = created_calendar["id"]
|
||||
return created_calendar
|
||||
|
||||
def delete(self):
|
||||
def delete(self) -> None:
|
||||
"""delete calendar"""
|
||||
|
||||
self.service.calendars().delete(calendarId=self.calendar_id).execute()
|
||||
|
||||
def make_public(self):
|
||||
def make_public(self) -> None:
|
||||
"""make calendar public"""
|
||||
|
||||
rule_public = {
|
||||
"scope": {
|
||||
"type": "default",
|
||||
},
|
||||
"role": "reader",
|
||||
}
|
||||
return (
|
||||
self.service.acl()
|
||||
.insert(calendarId=self.calendar_id, body=rule_public)
|
||||
.execute()
|
||||
)
|
||||
rule_public = ACLRule(scope=ACLScope(type="default"), role="reader")
|
||||
self.service.acl().insert(
|
||||
calendarId=self.calendar_id, body=rule_public
|
||||
).execute()
|
||||
|
||||
def add_owner(self, email: str):
|
||||
def add_owner(self, email: str) -> None:
|
||||
"""add calendar owner by email
|
||||
|
||||
Arguments:
|
||||
email -- email to add
|
||||
"""
|
||||
|
||||
rule_owner = {
|
||||
"scope": {
|
||||
"type": "user",
|
||||
"value": email,
|
||||
},
|
||||
"role": "owner",
|
||||
}
|
||||
return (
|
||||
self.service.acl()
|
||||
.insert(calendarId=self.calendar_id, body=rule_owner)
|
||||
.execute()
|
||||
)
|
||||
rule_owner = ACLRule(scope=ACLScope(type="user", value=email), role="owner")
|
||||
self.service.acl().insert(
|
||||
calendarId=self.calendar_id, body=rule_owner
|
||||
).execute()
|
||||
|
@ -1,11 +1,18 @@
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Union, Dict, Callable, Optional
|
||||
from typing import Union, Dict, Callable, Optional, Mapping, TypedDict
|
||||
|
||||
from icalendar import Calendar, Event
|
||||
from pytz import utc
|
||||
|
||||
from .gcal import EventData, EventList
|
||||
from .gcal import (
|
||||
EventData,
|
||||
EventList,
|
||||
EventDateOrDateTime,
|
||||
EventDateTime,
|
||||
EventDate,
|
||||
EventDataKey,
|
||||
)
|
||||
|
||||
DateDateTime = Union[datetime.date, datetime.datetime]
|
||||
|
||||
@ -28,7 +35,7 @@ def format_datetime_utc(value: DateDateTime) -> str:
|
||||
|
||||
def gcal_date_or_datetime(
|
||||
value: DateDateTime, check_value: Optional[DateDateTime] = None
|
||||
) -> Dict[str, str]:
|
||||
) -> EventDateOrDateTime:
|
||||
"""date or datetime to gcal (start or end dict)
|
||||
|
||||
Arguments:
|
||||
@ -42,18 +49,18 @@ def gcal_date_or_datetime(
|
||||
if check_value is None:
|
||||
check_value = value
|
||||
|
||||
result: Dict[str, str] = {}
|
||||
result: EventDateOrDateTime
|
||||
if isinstance(check_value, datetime.datetime):
|
||||
result["dateTime"] = format_datetime_utc(value)
|
||||
result = EventDateTime(dateTime=format_datetime_utc(value))
|
||||
else:
|
||||
if isinstance(check_value, datetime.date):
|
||||
if isinstance(value, datetime.datetime):
|
||||
value = datetime.date(value.year, value.month, value.day)
|
||||
result["date"] = value.isoformat()
|
||||
result = EventDate(date=value.isoformat())
|
||||
return result
|
||||
|
||||
|
||||
class EventConverter(Event):
|
||||
class EventConverter(Event): # type: ignore
|
||||
"""Convert icalendar event to google calendar resource
|
||||
( https://developers.google.com/calendar/v3/reference/events#resource-representations )
|
||||
"""
|
||||
@ -68,7 +75,7 @@ class EventConverter(Event):
|
||||
string value
|
||||
"""
|
||||
|
||||
return self.decoded(prop).decode(encoding="utf-8")
|
||||
return str(self.decoded(prop).decode(encoding="utf-8"))
|
||||
|
||||
def _datetime_str_prop(self, prop: str) -> str:
|
||||
"""utc datetime as string from property
|
||||
@ -82,7 +89,7 @@ class EventConverter(Event):
|
||||
|
||||
return format_datetime_utc(self.decoded(prop))
|
||||
|
||||
def _gcal_start(self) -> Dict[str, str]:
|
||||
def _gcal_start(self) -> EventDateOrDateTime:
|
||||
"""event start dict from icalendar event
|
||||
|
||||
Raises:
|
||||
@ -95,7 +102,7 @@ class EventConverter(Event):
|
||||
value = self.decoded("DTSTART")
|
||||
return gcal_date_or_datetime(value)
|
||||
|
||||
def _gcal_end(self) -> Dict[str, str]:
|
||||
def _gcal_end(self) -> EventDateOrDateTime:
|
||||
"""event end dict from icalendar event
|
||||
|
||||
Raises:
|
||||
@ -104,7 +111,7 @@ class EventConverter(Event):
|
||||
dict
|
||||
"""
|
||||
|
||||
result: Dict[str, str]
|
||||
result: EventDateOrDateTime
|
||||
if "DTEND" in self:
|
||||
value = self.decoded("DTEND")
|
||||
result = gcal_date_or_datetime(value)
|
||||
@ -121,10 +128,10 @@ class EventConverter(Event):
|
||||
def _put_to_gcal(
|
||||
self,
|
||||
gcal_event: EventData,
|
||||
prop: str,
|
||||
prop: EventDataKey,
|
||||
func: Callable[[str], str],
|
||||
ics_prop: Optional[str] = None,
|
||||
):
|
||||
) -> None:
|
||||
"""get property from ical event if existed, and put to gcal event
|
||||
|
||||
Arguments:
|
||||
@ -139,18 +146,18 @@ class EventConverter(Event):
|
||||
if ics_prop in self:
|
||||
gcal_event[prop] = func(ics_prop)
|
||||
|
||||
def to_gcal(self) -> EventData:
|
||||
def convert(self) -> EventData:
|
||||
"""Convert
|
||||
|
||||
Returns:
|
||||
dict - google calendar#event resource
|
||||
"""
|
||||
|
||||
event = {
|
||||
"iCalUID": self._str_prop("UID"),
|
||||
"start": self._gcal_start(),
|
||||
"end": self._gcal_end(),
|
||||
}
|
||||
event: EventData = EventData(
|
||||
iCalUID=self._str_prop("UID"),
|
||||
start=self._gcal_start(),
|
||||
end=self._gcal_end(),
|
||||
)
|
||||
|
||||
self._put_to_gcal(event, "summary", self._str_prop)
|
||||
self._put_to_gcal(event, "description", self._str_prop)
|
||||
@ -172,22 +179,23 @@ class CalendarConverter:
|
||||
def __init__(self, calendar: Optional[Calendar] = None):
|
||||
self.calendar: Optional[Calendar] = calendar
|
||||
|
||||
def load(self, filename: str):
|
||||
def load(self, filename: str) -> None:
|
||||
"""load calendar from ics file"""
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
self.calendar = Calendar.from_ical(f.read())
|
||||
self.logger.info("%s loaded", filename)
|
||||
|
||||
def loads(self, string: str):
|
||||
def loads(self, string: str) -> None:
|
||||
"""load calendar from ics string"""
|
||||
self.calendar = Calendar.from_ical(string)
|
||||
|
||||
def events_to_gcal(self) -> EventList:
|
||||
"""Convert events to google calendar resources"""
|
||||
|
||||
ics_events = self.calendar.walk(name="VEVENT")
|
||||
calendar: Calendar = self.calendar
|
||||
ics_events = calendar.walk(name="VEVENT")
|
||||
self.logger.info("%d events read", len(ics_events))
|
||||
|
||||
result = list(map(lambda event: EventConverter(event).to_gcal(), ics_events))
|
||||
result = list(map(lambda event: EventConverter(event).convert(), ics_events))
|
||||
self.logger.info("%d events converted", len(result))
|
||||
return result
|
||||
|
@ -8,7 +8,7 @@ from . import GoogleCalendar, GoogleCalendarService
|
||||
|
||||
|
||||
def load_config(filename: str) -> Optional[Dict[str, Any]]:
|
||||
result = None
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
try:
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
result = yaml.safe_load(f)
|
||||
@ -21,7 +21,7 @@ def load_config(filename: str) -> Optional[Dict[str, Any]]:
|
||||
class PropertyCommands:
|
||||
"""get/set google calendar properties"""
|
||||
|
||||
def __init__(self, _service):
|
||||
def __init__(self, _service: Any) -> None:
|
||||
self._service = _service
|
||||
|
||||
def get(self, calendar_id: str, property_name: str) -> None:
|
||||
@ -146,7 +146,7 @@ class Commands:
|
||||
print("{}: {}".format(summary, calendar_id))
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
fire.Fire(Commands, name="manage-ics2gcal")
|
||||
|
||||
|
||||
|
@ -1,15 +1,31 @@
|
||||
import datetime
|
||||
import logging
|
||||
import operator
|
||||
from typing import List, Dict, Set, Tuple, Union, Callable
|
||||
from typing import List, Dict, Set, Tuple, Union, Callable, NamedTuple
|
||||
|
||||
import dateutil.parser
|
||||
from pytz import utc
|
||||
|
||||
from .gcal import GoogleCalendar, EventData, EventList, EventTuple
|
||||
from .gcal import (
|
||||
GoogleCalendar,
|
||||
EventData,
|
||||
EventList,
|
||||
EventTuple,
|
||||
EventDataKey,
|
||||
EventDateOrDateTime,
|
||||
EventDate,
|
||||
)
|
||||
from .ical import CalendarConverter, DateDateTime
|
||||
|
||||
|
||||
class ComparedEvents(NamedTuple):
|
||||
"""Compared events"""
|
||||
|
||||
added: EventList
|
||||
changed: List[EventTuple]
|
||||
deleted: EventList
|
||||
|
||||
|
||||
class CalendarSync:
|
||||
"""class for synchronize calendar with Google"""
|
||||
|
||||
@ -24,8 +40,8 @@ class CalendarSync:
|
||||
|
||||
@staticmethod
|
||||
def _events_list_compare(
|
||||
items_src: EventList, items_dst: EventList, key: str = "iCalUID"
|
||||
) -> Tuple[EventList, List[EventTuple], EventList]:
|
||||
items_src: EventList, items_dst: EventList, key: EventDataKey = "iCalUID"
|
||||
) -> ComparedEvents:
|
||||
"""compare list of events by key
|
||||
|
||||
Arguments:
|
||||
@ -34,13 +50,11 @@ class CalendarSync:
|
||||
key {str} -- name of key to compare (default: {'iCalUID'})
|
||||
|
||||
Returns:
|
||||
tuple -- (items_to_insert,
|
||||
items_to_update,
|
||||
items_to_delete)
|
||||
ComparedEvents -- (added, changed, deleted)
|
||||
"""
|
||||
|
||||
def get_key(item: EventData) -> str:
|
||||
return item[key]
|
||||
return str(item[key])
|
||||
|
||||
keys_src: Set[str] = set(map(get_key, items_src))
|
||||
keys_dst: Set[str] = set(map(get_key, items_dst))
|
||||
@ -49,21 +63,21 @@ class CalendarSync:
|
||||
keys_to_update = keys_src & keys_dst
|
||||
keys_to_delete = keys_dst - keys_src
|
||||
|
||||
def items_by_keys(items: EventList, key_name: str, keys: Set[str]) -> EventList:
|
||||
return list(filter(lambda item: item[key_name] in keys, items))
|
||||
def items_by_keys(items: EventList, keys: Set[str]) -> EventList:
|
||||
return list(filter(lambda item: get_key(item) in keys, items))
|
||||
|
||||
items_to_insert = items_by_keys(items_src, key, keys_to_insert)
|
||||
items_to_delete = items_by_keys(items_dst, key, keys_to_delete)
|
||||
items_to_insert = items_by_keys(items_src, keys_to_insert)
|
||||
items_to_delete = items_by_keys(items_dst, keys_to_delete)
|
||||
|
||||
to_upd_src = items_by_keys(items_src, key, keys_to_update)
|
||||
to_upd_dst = items_by_keys(items_dst, key, keys_to_update)
|
||||
to_upd_src = items_by_keys(items_src, keys_to_update)
|
||||
to_upd_dst = items_by_keys(items_dst, keys_to_update)
|
||||
to_upd_src.sort(key=get_key)
|
||||
to_upd_dst.sort(key=get_key)
|
||||
items_to_update = list(zip(to_upd_src, to_upd_dst))
|
||||
|
||||
return items_to_insert, items_to_update, items_to_delete
|
||||
return ComparedEvents(items_to_insert, items_to_update, items_to_delete)
|
||||
|
||||
def _filter_events_to_update(self):
|
||||
def _filter_events_to_update(self) -> None:
|
||||
"""filter 'to_update' events by 'updated' datetime"""
|
||||
|
||||
def filter_updated(event_tuple: EventTuple) -> bool:
|
||||
@ -95,17 +109,17 @@ class CalendarSync:
|
||||
|
||||
def filter_by_date(event: EventData) -> bool:
|
||||
date_cmp = date
|
||||
event_start: Dict[str, str] = event["start"]
|
||||
event_start: EventDateOrDateTime = event["start"]
|
||||
event_date: Union[DateDateTime, str, None] = None
|
||||
compare_dates = False
|
||||
|
||||
if "date" in event_start:
|
||||
event_date = event_start["date"]
|
||||
event_date = event_start["date"] # type: ignore
|
||||
compare_dates = True
|
||||
elif "dateTime" in event_start:
|
||||
event_date = event_start["dateTime"]
|
||||
event_date = event_start["dateTime"] # type: ignore
|
||||
|
||||
event_date = dateutil.parser.parse(event_date)
|
||||
event_date = dateutil.parser.parse(str(event_date))
|
||||
if compare_dates:
|
||||
date_cmp = datetime.date(date.year, date.month, date.day)
|
||||
event_date = datetime.date(
|
||||
|
@ -14,7 +14,7 @@ ConfigDate = Union[str, datetime.datetime]
|
||||
def load_config() -> Dict[str, Any]:
|
||||
with open("config.yml", "r", encoding="utf-8") as f:
|
||||
result = yaml.safe_load(f)
|
||||
return result
|
||||
return result # type: ignore
|
||||
|
||||
|
||||
def get_start_date(date: ConfigDate) -> datetime.datetime:
|
||||
@ -27,7 +27,7 @@ def get_start_date(date: ConfigDate) -> datetime.datetime:
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
config = load_config()
|
||||
|
||||
if "logging" in config:
|
||||
|
@ -1,5 +1,5 @@
|
||||
import datetime
|
||||
from typing import Tuple
|
||||
from typing import Tuple, Any
|
||||
|
||||
import pytest
|
||||
from pytz import timezone, utc
|
||||
@ -57,21 +57,21 @@ def ics_test_event(content: str) -> str:
|
||||
return ics_test_cal("BEGIN:VEVENT\r\n{}END:VEVENT\r\n".format(content))
|
||||
|
||||
|
||||
def test_empty_calendar():
|
||||
def test_empty_calendar() -> None:
|
||||
converter = CalendarConverter()
|
||||
converter.loads(ics_test_cal(""))
|
||||
evnts = converter.events_to_gcal()
|
||||
assert evnts == []
|
||||
|
||||
|
||||
def test_empty_event():
|
||||
def test_empty_event() -> None:
|
||||
converter = CalendarConverter()
|
||||
converter.loads(ics_test_event(""))
|
||||
with pytest.raises(KeyError):
|
||||
converter.events_to_gcal()
|
||||
|
||||
|
||||
def test_event_no_end():
|
||||
def test_event_no_end() -> None:
|
||||
converter = CalendarConverter()
|
||||
converter.loads(ics_test_event(only_start_date))
|
||||
with pytest.raises(ValueError):
|
||||
@ -102,11 +102,11 @@ def test_event_no_end():
|
||||
"datetime utc duration",
|
||||
],
|
||||
)
|
||||
def param_events_start_end(request):
|
||||
def param_events_start_end(request: Any) -> Any:
|
||||
return request.param
|
||||
|
||||
|
||||
def test_event_start_end(param_events_start_end: Tuple[str, str, str, str]):
|
||||
def test_event_start_end(param_events_start_end: Tuple[str, str, str, str]) -> None:
|
||||
(date_type, ics_str, start, end) = param_events_start_end
|
||||
converter = CalendarConverter()
|
||||
converter.loads(ics_str)
|
||||
@ -117,7 +117,7 @@ def test_event_start_end(param_events_start_end: Tuple[str, str, str, str]):
|
||||
assert event["end"] == {date_type: end}
|
||||
|
||||
|
||||
def test_event_created_updated():
|
||||
def test_event_created_updated() -> None:
|
||||
converter = CalendarConverter()
|
||||
converter.loads(ics_test_event(created_updated))
|
||||
events = converter.events_to_gcal()
|
||||
@ -142,5 +142,5 @@ def test_event_created_updated():
|
||||
],
|
||||
ids=["utc", "with timezone", "date"],
|
||||
)
|
||||
def test_format_datetime_utc(value: datetime.datetime, expected_str: str):
|
||||
def test_format_datetime_utc(value: datetime.datetime, expected_str: str) -> None:
|
||||
assert format_datetime_utc(value) == expected_str
|
||||
|
@ -3,95 +3,93 @@ import hashlib
|
||||
import operator
|
||||
from copy import deepcopy
|
||||
from random import shuffle
|
||||
from typing import Union, List, Dict, Optional
|
||||
from typing import Union, List, Dict, Optional, AnyStr
|
||||
|
||||
import dateutil.parser
|
||||
import pytest
|
||||
from pytz import timezone, utc
|
||||
|
||||
from sync_ics2gcal import CalendarSync
|
||||
from sync_ics2gcal import CalendarSync, DateDateTime
|
||||
from sync_ics2gcal.gcal import EventDateOrDateTime, EventData, EventList
|
||||
|
||||
|
||||
def sha1(string: Union[str, bytes]) -> str:
|
||||
if isinstance(string, str):
|
||||
string = string.encode("utf8")
|
||||
def sha1(s: AnyStr) -> str:
|
||||
h = hashlib.sha1()
|
||||
h.update(string)
|
||||
h.update(str(s).encode("utf8") if isinstance(s, str) else s)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def gen_events(
|
||||
start: int,
|
||||
stop: int,
|
||||
start_time: Union[datetime.datetime, datetime.date],
|
||||
start_time: DateDateTime,
|
||||
no_time: bool = False,
|
||||
) -> List[Dict[str, Union[str, Dict[str, str]]]]:
|
||||
) -> EventList:
|
||||
duration: datetime.timedelta
|
||||
date_key: str
|
||||
date_end: str
|
||||
if no_time:
|
||||
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)
|
||||
date_key: str = "date"
|
||||
date_end: str = ""
|
||||
duration = datetime.date(1, 1, 2) - datetime.date(1, 1, 1)
|
||||
date_key = "date"
|
||||
date_end = ""
|
||||
else:
|
||||
start_time = utc.normalize(start_time.astimezone(utc)).replace(tzinfo=None)
|
||||
duration: datetime.timedelta = datetime.datetime(
|
||||
1, 1, 1, 2
|
||||
) - datetime.datetime(1, 1, 1, 1)
|
||||
date_key: str = "dateTime"
|
||||
date_end: str = "Z"
|
||||
start_time = utc.normalize(start_time.astimezone(utc)).replace(tzinfo=None) # type: ignore
|
||||
duration = datetime.datetime(1, 1, 1, 2) - datetime.datetime(1, 1, 1, 1)
|
||||
date_key = "dateTime"
|
||||
date_end = "Z"
|
||||
|
||||
result: List[Dict[str, Union[str, Dict[str, str]]]] = []
|
||||
result: EventList = []
|
||||
for i in range(start, stop):
|
||||
event_start = start_time + (duration * i)
|
||||
event_end = event_start + duration
|
||||
|
||||
updated: Union[datetime.datetime, datetime.date] = event_start
|
||||
updated: DateDateTime = event_start
|
||||
if no_time:
|
||||
updated = datetime.datetime(
|
||||
updated.year, updated.month, updated.day, 0, 0, 0, 1, tzinfo=utc
|
||||
)
|
||||
|
||||
event: Dict[str, Union[str, Dict[str, str]]] = {
|
||||
event: EventData = {
|
||||
"summary": "test event __ {}".format(i),
|
||||
"location": "la la la {}".format(i),
|
||||
"description": "test TEST -- test event {}".format(i),
|
||||
"iCalUID": "{}@test.com".format(sha1("test - event {}".format(i))),
|
||||
"updated": updated.isoformat() + "Z",
|
||||
"created": updated.isoformat() + "Z",
|
||||
"start": {date_key: event_start.isoformat() + date_end},
|
||||
"end": {date_key: event_end.isoformat() + date_end},
|
||||
"start": {date_key: event_start.isoformat() + date_end}, # type: ignore
|
||||
"end": {date_key: event_end.isoformat() + date_end}, # type: ignore
|
||||
}
|
||||
result.append(event)
|
||||
return result
|
||||
|
||||
|
||||
def gen_list_to_compare(start: int, stop: int) -> List[Dict[str, str]]:
|
||||
result: List[Dict[str, str]] = []
|
||||
def gen_list_to_compare(start: int, stop: int) -> EventList:
|
||||
result: EventList = []
|
||||
for i in range(start, stop):
|
||||
result.append({"iCalUID": "test{:06d}".format(i)})
|
||||
return result
|
||||
|
||||
|
||||
def get_start_date(
|
||||
event: Dict[str, Union[str, Dict[str, str]]]
|
||||
) -> Union[datetime.datetime, datetime.date]:
|
||||
event_start: Dict[str, str] = event["start"]
|
||||
def get_start_date(event: EventData) -> DateDateTime:
|
||||
event_start: EventDateOrDateTime = event["start"]
|
||||
start_date: Optional[str] = None
|
||||
is_date = False
|
||||
if "date" in event_start:
|
||||
start_date = event_start["date"]
|
||||
start_date = event_start["date"] # type: ignore
|
||||
is_date = True
|
||||
if "dateTime" in event_start:
|
||||
start_date = event_start["dateTime"]
|
||||
start_date = event_start["dateTime"] # type: ignore
|
||||
|
||||
result = dateutil.parser.parse(start_date)
|
||||
result: DateDateTime = dateutil.parser.parse(str(start_date))
|
||||
if is_date:
|
||||
result = datetime.date(result.year, result.month, result.day)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def test_compare():
|
||||
part_len = 20
|
||||
def test_compare() -> None:
|
||||
part_len: int = 20
|
||||
# [1..2n]
|
||||
lst_src = gen_list_to_compare(1, 1 + part_len * 2)
|
||||
# [n..3n]
|
||||
@ -119,7 +117,7 @@ def test_compare():
|
||||
|
||||
|
||||
@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) -> None:
|
||||
msk = timezone("Europe/Moscow")
|
||||
now = utc.localize(datetime.datetime.utcnow())
|
||||
msk_now = msk.normalize(now.astimezone(msk))
|
||||
@ -131,7 +129,7 @@ def test_filter_events_by_date(no_time: bool):
|
||||
else:
|
||||
duration = datetime.datetime(1, 1, 1, 2) - datetime.datetime(1, 1, 1, 1)
|
||||
|
||||
date_cmp = msk_now + (duration * part_len)
|
||||
date_cmp: DateDateTime = msk_now + (duration * part_len)
|
||||
|
||||
if no_time:
|
||||
date_cmp = datetime.date(date_cmp.year, date_cmp.month, date_cmp.day)
|
||||
@ -152,7 +150,7 @@ def test_filter_events_by_date(no_time: bool):
|
||||
assert get_start_date(event) < date_cmp
|
||||
|
||||
|
||||
def test_filter_events_to_update():
|
||||
def test_filter_events_to_update() -> None:
|
||||
msk = timezone("Europe/Moscow")
|
||||
now = utc.localize(datetime.datetime.utcnow())
|
||||
msk_now = msk.normalize(now.astimezone(msk))
|
||||
@ -164,11 +162,11 @@ def test_filter_events_to_update():
|
||||
events_old = gen_events(1, 1 + count, msk_now)
|
||||
events_new = gen_events(1, 1 + count, date_upd)
|
||||
|
||||
sync1 = CalendarSync(None, None)
|
||||
sync1 = CalendarSync(None, None) # type: ignore
|
||||
sync1.to_update = list(zip(events_new, events_old))
|
||||
sync1._filter_events_to_update()
|
||||
|
||||
sync2 = CalendarSync(None, None)
|
||||
sync2 = CalendarSync(None, None) # type: ignore
|
||||
sync2.to_update = list(zip(events_old, events_new))
|
||||
sync2._filter_events_to_update()
|
||||
|
||||
@ -176,7 +174,7 @@ def test_filter_events_to_update():
|
||||
assert sync2.to_update == []
|
||||
|
||||
|
||||
def test_filter_events_no_updated():
|
||||
def test_filter_events_no_updated() -> None:
|
||||
"""
|
||||
test filtering events that not have 'updated' field
|
||||
such events should always pass the filter
|
||||
@ -197,7 +195,7 @@ def test_filter_events_no_updated():
|
||||
del event["updated"]
|
||||
i += 1
|
||||
|
||||
sync = CalendarSync(None, None)
|
||||
sync = CalendarSync(None, None) # type: ignore
|
||||
sync.to_update = list(zip(events_old, events_new))
|
||||
sync._filter_events_to_update()
|
||||
assert len(sync.to_update) == count // 2
|
||||
|
Loading…
x
Reference in New Issue
Block a user