1
0
mirror of https://github.com/b4tman/sync_ics2gcal synced 2026-02-04 07:14:59 +00:00

89 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-01 09:13:31 +03:00
052ba440d0 type aliases 2021-05-19 12:55:54 +03:00
5e01b6dd01 add fire to setup requires 2021-05-01 18:26:02 +03:00
Dmitry Belyaev
8b2f35b3a9 Merge pull request #54 from b4tman/feature/fire_cli
feature/fire cli
2021-05-01 18:06:52 +03:00
c3bdd25d5a cli group for property commands 2021-05-01 17:58:30 +03:00
3b0de9d636 fire instead of argparse 2021-05-01 17:31:14 +03:00
dependabot[bot]
97614ae21d Bump google-api-python-client from 2.1.0 to 2.3.0
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.1.0 to 2.3.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.1.0...v2.3.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-01 15:14:42 +03:00
dependabot[bot]
1cdf1da6ee Bump google-auth from 1.28.0 to 1.30.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.28.0 to 1.30.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.28.0...v1.30.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-01 15:07:52 +03:00
Dmitry Belyaev
bd6bd65719 Merge pull request #51 from b4tman/feature/type_annotations
Type annotations
2021-05-01 15:00:52 +03:00
c41b3a4dbd type annotations - tests 2021-05-01 13:49:26 +03:00
9dab3c5709 type annotations - scripts 2021-04-30 11:46:10 +03:00
e5064eeaed cfg optional 2021-04-30 11:45:05 +03:00
19192d1641 fix _tz_aware_datetime return type 2021-04-30 11:08:56 +03:00
6c571df7bc type annotations - sync 2021-04-29 17:10:35 +03:00
a6474ee984 type annotations - ical 2021-04-29 16:19:41 +03:00
8669aefabe type annotations - gcal 2021-04-29 15:24:23 +03:00
77e2cdba36 ignore ide files and virtualenv 2021-04-29 15:22:27 +03:00
dependabot[bot]
18224ad5b4 Bump google-api-python-client from 1.12.8 to 2.1.0
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 1.12.8 to 2.1.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v1.12.8...v2.1.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-01 15:57:56 +03:00
dependabot[bot]
41c2973646 Bump google-auth from 1.27.0 to 1.28.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.27.0 to 1.28.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.27.0...v1.28.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-01 09:06:03 +03:00
dependabot[bot]
a02775110d Bump pytz from 2020.5 to 2021.1
Bumps [pytz](https://github.com/stub42/pytz) from 2020.5 to 2021.1.
- [Release notes](https://github.com/stub42/pytz/releases)
- [Commits](https://github.com/stub42/pytz/compare/release_2020.5...release_2021.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-01 10:58:20 +03:00
dependabot[bot]
12653df1bf Bump google-auth from 1.24.0 to 1.27.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.24.0 to 1.27.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.24.0...v1.27.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-01 09:02:30 +03:00
dependabot[bot]
fef3586146 Bump pyyaml from 5.3.1 to 5.4.1
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.3.1 to 5.4.1.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/5.3.1...5.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 14:52:05 +03:00
b7cd3847bc remove Dependabot and FOSSA badges 2021-01-01 15:14:24 +03:00
3ddc486614 add Python 3.9 support 2021-01-01 13:38:14 +03:00
dependabot[bot]
1beff774bc Bump google-auth from 1.23.0 to 1.24.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.23.0 to 1.24.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.23.0...v1.24.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-01 13:25:26 +03:00
e0b4e6c28a drop Python 3.5 support 2021-01-01 13:17:08 +03:00
dependabot[bot]
50f90925b8 Bump pytz from 2020.4 to 2020.5
Bumps [pytz](https://github.com/stub42/pytz) from 2020.4 to 2020.5.
- [Release notes](https://github.com/stub42/pytz/releases)
- [Commits](https://github.com/stub42/pytz/compare/release_2020.4...release_2020.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-01 13:07:00 +03:00
dependabot[bot]
c2a3a54783 Bump pytz from 2020.1 to 2020.4
Bumps [pytz](https://github.com/stub42/pytz) from 2020.1 to 2020.4.
- [Release notes](https://github.com/stub42/pytz/releases)
- [Commits](https://github.com/stub42/pytz/compare/release_2020.1...release_2020.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-01 10:37:35 +03:00
dependabot[bot]
38dd853436 Bump google-api-python-client from 1.12.5 to 1.12.8
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 1.12.5 to 1.12.8.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v1.12.5...v1.12.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-01 09:56:31 +03:00
dependabot[bot]
a10e62f806 Bump google-api-python-client from 1.12.3 to 1.12.5
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 1.12.3 to 1.12.5.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v1.12.3...v1.12.5)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-01 12:31:38 +03:00
dependabot[bot]
eca648ee56 Bump google-auth from 1.22.0 to 1.23.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.22.0 to 1.23.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.22.0...v1.23.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-01 12:26:07 +03:00
dependabot[bot]
55ee5002cc Bump google-api-python-client from 1.11.0 to 1.12.3
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 1.11.0 to 1.12.3.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v1.11.0...v1.12.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-01 10:29:44 +03:00
dependabot[bot]
1bec98a53e Bump google-auth from 1.21.0 to 1.22.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.21.0 to 1.22.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.21.0...v1.22.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-01 10:14:15 +03:00
dependabot[bot]
5649a71da2 Bump icalendar from 4.0.6 to 4.0.7
Bumps [icalendar](https://github.com/collective/icalendar) from 4.0.6 to 4.0.7.
- [Release notes](https://github.com/collective/icalendar/releases)
- [Changelog](https://github.com/collective/icalendar/blob/master/CHANGES.rst)
- [Commits](https://github.com/collective/icalendar/compare/4.0.6...4.0.7)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-01 10:08:55 +03:00
dependabot[bot]
fc490dcefe Bump google-auth from 1.20.1 to 1.21.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.20.1 to 1.21.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.20.1...v1.21.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 09:48:30 +03:00
dependabot[bot]
ecb2f5a3d1 Bump google-api-python-client from 1.10.0 to 1.11.0
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v1.10.0...v1.11.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 09:31:32 +03:00
8f56ad426e Enabling code scanning
https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/enabling-code-scanning
2020-08-11 00:22:53 +03:00
dependabot[bot]
a5739cb64c Bump google-auth from 1.18.0 to 1.20.1
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.18.0 to 1.20.1.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.18.0...v1.20.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-08 13:53:24 +03:00
dependabot[bot]
fff533c0a8 Bump google-api-python-client from 1.9.3 to 1.10.0
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 1.9.3 to 1.10.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v1.9.3...v1.10.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-08 13:31:16 +03:00
dependabot[bot]
d8cb345550 Bump google-auth from 1.17.2 to 1.18.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.17.2 to 1.18.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.17.2...v1.18.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-01 10:58:29 +03:00
dependabot[bot]
05a4770071 Bump google-auth from 1.16.0 to 1.17.2
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.16.0 to 1.17.2.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.16.0...v1.17.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-18 10:18:57 +03:00
dependabot[bot]
7664ca9e55 Bump google-api-python-client from 1.8.4 to 1.9.3
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 1.8.4 to 1.9.3.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v1.8.4...v1.9.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-18 10:12:42 +03:00
dependabot-preview[bot]
66f9de3980 Create Dependabot config file 2020-06-18 09:52:40 +03:00
dependabot-preview[bot]
a7164abb24 Bump icalendar from 4.0.5 to 4.0.6
Bumps [icalendar](https://github.com/collective/icalendar) from 4.0.5 to 4.0.6.
- [Release notes](https://github.com/collective/icalendar/releases)
- [Changelog](https://github.com/collective/icalendar/blob/master/CHANGES.rst)
- [Commits](https://github.com/collective/icalendar/compare/4.0.5...4.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-01 10:58:44 +03:00
dependabot-preview[bot]
38f7403b40 Bump google-api-python-client from 1.8.2 to 1.8.4
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 1.8.2 to 1.8.4.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v1.8.2...v1.8.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-01 10:37:23 +03:00
dependabot-preview[bot]
9ad544971e Bump google-auth from 1.14.1 to 1.16.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.14.1 to 1.16.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.14.1...v1.16.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-01 09:00:12 +03:00
dependabot-preview[bot]
2d00ae77c9 Bump google-api-python-client from 1.8.0 to 1.8.2
Bumps [google-api-python-client](https://github.com/google/google-api-python-client) from 1.8.0 to 1.8.2.
- [Release notes](https://github.com/google/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/google-api-python-client/compare/v1.8.0...v1.8.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-01 12:47:13 +03:00
dependabot-preview[bot]
283b164723 Bump google-auth from 1.12.0 to 1.14.1
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.12.0 to 1.14.1.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.12.0...v1.14.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-01 11:51:09 +03:00
dependabot-preview[bot]
6058a3e592 Bump pytz from 2019.3 to 2020.1
Bumps [pytz](https://github.com/stub42/pytz) from 2019.3 to 2020.1.
- [Release notes](https://github.com/stub42/pytz/releases)
- [Commits](https://github.com/stub42/pytz/compare/release_2019.3...release_2020.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-01 11:00:32 +03:00
dependabot-preview[bot]
9c08630931 Bump google-api-python-client from 1.7.11 to 1.8.0
Bumps [google-api-python-client](https://github.com/google/google-api-python-client) from 1.7.11 to 1.8.0.
- [Release notes](https://github.com/google/google-api-python-client/releases)
- [Changelog](https://github.com/googleapis/google-api-python-client/blob/master/CHANGELOG)
- [Commits](https://github.com/google/google-api-python-client/compare/v1.7.11...v1.8.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-01 09:32:53 +03:00
dependabot-preview[bot]
9e027df349 Bump google-auth from 1.11.2 to 1.12.0
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.11.2 to 1.12.0.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.11.2...v1.12.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-01 09:27:44 +03:00
dependabot-preview[bot]
c1d148c3f3 Bump icalendar from 4.0.4 to 4.0.5
Bumps [icalendar](https://github.com/collective/icalendar) from 4.0.4 to 4.0.5.
- [Release notes](https://github.com/collective/icalendar/releases)
- [Changelog](https://github.com/collective/icalendar/blob/master/CHANGES.rst)
- [Commits](https://github.com/collective/icalendar/compare/4.0.4...4.0.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-01 09:20:30 +03:00
dependabot-preview[bot]
694b91798e Bump pyyaml from 5.3 to 5.3.1
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.3 to 5.3.1.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/5.3...5.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-01 09:15:29 +03:00
8712b81a53 + get/set commands in README 2020-03-08 13:29:57 +03:00
51005fb29e fmt README 2020-03-08 13:17:54 +03:00
6e7c3cb7b2 fmt sync_calendar 2020-03-08 13:10:42 +03:00
0f9a8d7a74 lint sync 2020-03-07 18:34:31 +03:00
0161d65c16 lint ical 2020-03-07 18:29:35 +03:00
9e74772852 lint gcal 2020-03-07 18:26:37 +03:00
8d64869f06 lint manage_calendars 2020-03-07 18:14:34 +03:00
ab00cb09c8 + manage_calendars: list hidden & deleted 2020-03-07 18:05:51 +03:00
b0a39a1b8c manage_calendars: use nextPageToken for list
By default maximum number of entries returned on one result page is 100
2020-03-07 17:54:16 +03:00
55b67469be + manage_calendars: get\set calendar properties 2020-03-07 17:41:05 +03:00
c17d3cd0ea manage_calendars: no config file required 2020-03-07 16:21:48 +03:00
9aad7e1910 make service even when config is None 2020-03-07 16:14:47 +03:00
dependabot-preview[bot]
41cc6b4159 Bump google-auth from 1.11.0 to 1.11.2
Bumps [google-auth](https://github.com/googleapis/google-auth-library-python) from 1.11.0 to 1.11.2.
- [Release notes](https://github.com/googleapis/google-auth-library-python/releases)
- [Changelog](https://github.com/googleapis/google-auth-library-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-auth-library-python/compare/v1.11.0...v1.11.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-01 11:45:06 +03:00
5d37aa2a33 disable cache_discovery
to suppress errors in logs:
file_cache is unavailable when using oauth2client >= 4.0.0

https://github.com/googleapis/google-api-python-client/issues/299
https://github.com/googleapis/google-api-python-client/issues/325
2020-02-25 22:43:45 +03:00
24 changed files with 1451 additions and 356 deletions

View File

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

1
.gitattributes vendored
View File

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

9
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: monthly
time: '02:00'
open-pull-requests-limit: 10
target-branch: develop

33
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: "CodeQL"
on:
push:
branches: [develop, ]
pull_request:
# The branches below must be a subset of the branches above
branches: [develop]
schedule:
- cron: '0 12 10 * *'
jobs:
analyse:
name: Analyse
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

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

View File

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

4
.gitignore vendored
View File

@@ -2,8 +2,10 @@ config.yml
service-account.json
*.pyc
my-test*.ics
.vscode/*
.vscode/
.idea/
/dist/
/*.egg-info/
/build/
/.eggs/
venv/

View File

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

View File

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

View File

@@ -2,8 +2,6 @@
[![PyPI version](https://badge.fury.io/py/sync-ics2gcal.svg)](https://badge.fury.io/py/sync-ics2gcal)
[![Build Status](https://travis-ci.org/b4tman/sync_ics2gcal.svg?branch=master)](https://travis-ci.org/b4tman/sync_ics2gcal)
[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=b4tman/sync_ics2gcal)](https://dependabot.com)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fb4tman%2Fsync_ics2gcal.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fb4tman%2Fsync_ics2gcal?ref=badge_shield)
![Python package status](https://github.com/b4tman/sync_ics2gcal/workflows/Python%20package/badge.svg)
Python scripts for sync .ics file with Google calendar
@@ -12,21 +10,26 @@ Python scripts for sync .ics file with Google calendar
To install from [PyPI](https://pypi.org/project/sync-ics2gcal/) with [pip](https://pypi.python.org/pypi/pip), run:
```
```sh
pip install sync-ics2gcal
```
Or download source code and install using poetry:
Or download source code and install:
```
python setup.py install
```sh
# install poetry
pip install poetry
# install project and deps to virtualenv
poetry install
# run
poetry run sync-ics2gcal
```
## Configuration
### Create application in Google API Console
1. Create a new project: https://console.developers.google.com/project
1. Create a new project: [console.developers.google.com/project](https://console.developers.google.com/project)
2. Choose the new project from the top right project dropdown (only if another project is selected)
3. In the project Dashboard, choose "Library"
4. Find and Enable "Google Calendar API"
@@ -38,15 +41,20 @@ python setup.py install
10. Edit service account and click "Create key", choose JSON and download key file.
### Create working directory
For example: `/home/user/myfolder`.
1. Save service account key in file `service-account.json`.
2. Download [sample config](https://github.com/b4tman/sync_ics2gcal/blob/develop/sample-config.yml) and save to file `config.yml`. For example:
```
```sh
wget https://raw.githubusercontent.com/b4tman/sync_ics2gcal/develop/sample-config.yml -O config.yml
```
3. *(Optional)* Place source `.ics` file, `my-calendar.ics` for example.
### Configuration parameters
* `start_from` - start date:
* full format datetime, `2018-04-03T13:23:25.000001Z` for example
* or just `now`
@@ -55,36 +63,39 @@ wget https://raw.githubusercontent.com/b4tman/sync_ics2gcal/develop/sample-confi
* `google_id` - target google calendar id, `my-calendar@group.calendar.google.com` for example
* `source` - source `.ics` filename, `my-calendar.ics` for example
## Usage
### Manage calendars
```
manage-ics2gcal <subcommand> [-h] [options]
```sh
manage-ics2gcal GROUP | COMMAND
```
subcomands:
**GROUPS**:
* **property** - get/set properties (see [CalendarList resource](https://developers.google.com/calendar/v3/reference/calendarList#resource)), subcommands:
- **get** - get calendar property
- **set** - set calendar property
**COMMANDS**:
* **list** - list calendars
* **create** - create calendar
* **add_owner** - add owner to calendar
* **remove** - remove calendar
* **rename** - rename calendar
Use **-h** for more info.
### Sync calendar
just type:
```
```sh
sync-ics2gcal
```
## How it works
![How it works](how-it-works.png)
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fb4tman%2Fsync_ics2gcal.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fb4tman%2Fsync_ics2gcal?ref=badge_large)

759
poetry.lock generated Normal file
View File

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

View File

@@ -1,3 +1,42 @@
[tool.poetry]
name = "sync_ics2gcal"
version = "0.1.3"
description = "Sync ics file with Google calendar"
authors = ["Dmitry Belyaev <b4tm4n@mail.ru>"]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/b4tman/sync_ics2gcal"
repository = "https://github.com/b4tman/sync_ics2gcal"
keywords = ["icalendar", "sync", "google", "calendar"]
classifiers = [
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
]
[tool.poetry.dependencies]
python = ">=3.6.1"
google-auth = "2.2.1"
google-api-python-client = "2.23.0"
icalendar = "4.0.7"
pytz = "2021.1"
PyYAML = "5.4.1"
fire = "0.4.0"
pydantic = "^1.8.2"
[tool.poetry.dev-dependencies]
pytest = "^6.2.5"
flake8 = "^3.9.2"
autopep8 = "^1.5.7"
[tool.poetry.scripts]
sync-ics2gcal = "sync_ics2gcal.sync_calendar:main"
manage-ics2gcal = "sync_ics2gcal.manage_calendars:main"
[build-system]
requires = ["setuptools>=40.8.0", "wheel", "setuptools_scm"]
build-backend = "setuptools.build_meta"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,5 +1,6 @@
google-auth==1.11.0
google-api-python-client==1.7.11
icalendar==4.0.4
pytz==2019.3
PyYAML==5.3
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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,18 @@
import logging
import sys
from datetime import datetime
from typing import List, Dict, Any, Callable, Tuple, Optional, Union
import google.auth
from google.oauth2 import service_account
from googleapiclient import discovery
from pytz import utc
EventData = Dict[str, Union[str, 'EventData', None]]
EventList = List[EventData]
EventTuple = Tuple[EventData, EventData]
class GoogleCalendarService():
class GoogleCalendarService:
"""class for make google calendar service Resource
Returns:
@@ -19,56 +24,54 @@ class GoogleCalendarService():
"""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 )
Returns:
service Resource
"""
scopes = ['https://www.googleapis.com/auth/calendar']
credentials, _ = google.auth.default(scopes=scopes)
service = discovery.build('calendar', 'v3', credentials=credentials)
service = discovery.build(
'calendar', 'v3', credentials=credentials, cache_discovery=False)
return service
@staticmethod
def from_srv_acc_file(service_account_file):
"""make service Resource from service account filename (authorize)
Returns:
service Resource
@staticmethod
def from_srv_acc_file(service_account_file: str):
"""make service Resource from service account filename (authorize)
"""
scopes = ['https://www.googleapis.com/auth/calendar']
credentials = service_account.Credentials.from_service_account_file(service_account_file)
credentials = service_account.Credentials.from_service_account_file(
service_account_file)
scoped_credentials = credentials.with_scopes(scopes)
service = discovery.build('calendar', 'v3', credentials=scoped_credentials)
service = discovery.build(
'calendar', 'v3', credentials=scoped_credentials,
cache_discovery=False)
return service
@staticmethod
def from_config(config):
def from_config(config: Optional[Dict[str, Optional[str]]] = None):
"""make service Resource from config dict
Arguments:
config -- dict() config with keys:
config -- config with keys:
(optional) service_account: - service account filename
if key not in dict then default credentials will be used
( https://developers.google.com/identity/protocols/application-default-credentials )
Returns:
service Resource
-- None: default credentials will be used
"""
if 'service_account' in config:
service = GoogleCalendarService.from_srv_acc_file(config['service_account'])
if config is not None and 'service_account' in config:
service = GoogleCalendarService.from_srv_acc_file(
config['service_account'])
else:
service = GoogleCalendarService.default()
return service
def select_event_key(event):
def select_event_key(event: EventData) -> Optional[str]:
"""select event key for logging
Arguments:
event -- event resource
Returns:
key name or None if no key found
"""
@@ -81,23 +84,23 @@ def select_event_key(event):
return key
class GoogleCalendar():
class GoogleCalendar:
"""class to interact with calendar on google
"""
logger = logging.getLogger('GoogleCalendar')
def __init__(self, service, calendarId):
self.service = service
self.calendarId = calendarId
def __init__(self, service: discovery.Resource, calendarId: Optional[str]):
self.service: discovery.Resource = service
self.calendarId: str = calendarId
def _make_request_callback(self, action, events_by_req):
def _make_request_callback(self, action: str, events_by_req: EventList) -> Callable:
"""make callback for log result of batch request
Arguments:
action -- action name
events_by_req -- list of events ordered by request id
Returns:
callback function
"""
@@ -107,8 +110,10 @@ class GoogleCalendar():
key = select_event_key(event)
if exception is not None:
self.logger.error('failed to %s event with %s: %s, exception: %s',
action, key, event.get(key), str(exception))
self.logger.error(
'failed to %s event with %s: %s, exception: %s',
action, key, event.get(key), str(exception)
)
else:
resp_key = select_event_key(response)
if resp_key is not None:
@@ -116,9 +121,10 @@ class GoogleCalendar():
key = resp_key
self.logger.info('event %s ok, %s: %s',
action, key, event.get(key))
return callback
def list_events_from(self, start):
def list_events_from(self, start: datetime) -> EventList:
""" list events from calendar, where start date >= start
"""
fields = 'nextPageToken,items(id,iCalUID,updated)'
@@ -127,8 +133,11 @@ class GoogleCalendar():
timeMin = utc.normalize(start.astimezone(utc)).replace(
tzinfo=None).isoformat() + 'Z'
while True:
response = self.service.events().list(calendarId=self.calendarId, pageToken=page_token,
singleEvents=True, timeMin=timeMin, fields=fields).execute()
response = self.service.events().list(calendarId=self.calendarId,
pageToken=page_token,
singleEvents=True,
timeMin=timeMin,
fields=fields).execute()
if 'items' in response:
events.extend(response['items'])
page_token = response.get('nextPageToken')
@@ -137,7 +146,7 @@ class GoogleCalendar():
self.logger.info('%d events listed', len(events))
return events
def find_exists(self, events):
def find_exists(self, events: List) -> Tuple[List[EventTuple], EventList]:
""" find existing events from list, by 'iCalUID' field
Arguments:
@@ -155,15 +164,16 @@ class GoogleCalendar():
def list_callback(request_id, response, exception):
found = False
event = events_by_req[int(request_id)]
cur_event = events_by_req[int(request_id)]
if exception is None:
found = ([] != response['items'])
else:
self.logger.error('exception %s, while listing event with UID: %s', str(
exception), event['iCalUID'])
self.logger.error(
'exception %s, while listing event with UID: %s',
str(exception), cur_event['iCalUID'])
if found:
exists.append(
(event, response['items'][0]))
(cur_event, response['items'][0]))
else:
not_found.append(events_by_req[int(request_id)])
@@ -172,14 +182,19 @@ class GoogleCalendar():
for event in events:
events_by_req.append(event)
batch.add(self.service.events().list(calendarId=self.calendarId,
iCalUID=event['iCalUID'], showDeleted=True, fields=fields), request_id=str(i))
iCalUID=event['iCalUID'],
showDeleted=True,
fields=fields
),
request_id=str(i)
)
i += 1
batch.execute()
self.logger.info('%d events exists, %d not found',
len(exists), len(not_found))
return exists, not_found
def insert_events(self, events):
def insert_events(self, events: EventList):
""" insert list of events
Arguments:
@@ -195,11 +210,13 @@ class GoogleCalendar():
for event in events:
events_by_req.append(event)
batch.add(self.service.events().insert(
calendarId=self.calendarId, body=event, fields=fields), request_id=str(i))
calendarId=self.calendarId, body=event, fields=fields),
request_id=str(i)
)
i += 1
batch.execute()
def patch_events(self, event_tuples):
def patch_events(self, event_tuples: List[EventTuple]):
""" patch (update) events
Arguments:
@@ -217,11 +234,12 @@ class GoogleCalendar():
continue
events_by_req.append(event_new)
batch.add(self.service.events().patch(
calendarId=self.calendarId, eventId=event_old['id'], body=event_new), fields=fields, request_id=str(i))
calendarId=self.calendarId, eventId=event_old['id'],
body=event_new), fields=fields, request_id=str(i))
i += 1
batch.execute()
def update_events(self, event_tuples):
def update_events(self, event_tuples: List[EventTuple]):
""" update events
Arguments:
@@ -239,11 +257,12 @@ class GoogleCalendar():
continue
events_by_req.append(event_new)
batch.add(self.service.events().update(
calendarId=self.calendarId, eventId=event_old['id'], body=event_new, fields=fields), request_id=str(i))
calendarId=self.calendarId, eventId=event_old['id'],
body=event_new, fields=fields), request_id=str(i))
i += 1
batch.execute()
def delete_events(self, events):
def delete_events(self, events: EventList):
""" delete events
Arguments:
@@ -258,11 +277,12 @@ class GoogleCalendar():
for event in events:
events_by_req.append(event)
batch.add(self.service.events().delete(
calendarId=self.calendarId, eventId=event['id']), request_id=str(i))
calendarId=self.calendarId,
eventId=event['id']), request_id=str(i))
i += 1
batch.execute()
def create(self, summary, timeZone=None):
def create(self, summary: str, timeZone: Optional[str] = None) -> Any:
"""create calendar
Arguments:
@@ -279,7 +299,9 @@ class GoogleCalendar():
if timeZone is not None:
calendar['timeZone'] = timeZone
created_calendar = self.service.calendars().insert(body=calendar).execute()
created_calendar = self.service.calendars().insert(
body=calendar
).execute()
self.calendarId = created_calendar['id']
return created_calendar
@@ -299,9 +321,12 @@ class GoogleCalendar():
},
'role': 'reader'
}
return self.service.acl().insert(calendarId=self.calendarId, body=rule_public).execute()
return self.service.acl().insert(
calendarId=self.calendarId,
body=rule_public
).execute()
def add_owner(self, email):
def add_owner(self, email: str):
"""add calendar owner by email
Arguments:
@@ -315,4 +340,7 @@ class GoogleCalendar():
},
'role': 'owner'
}
return self.service.acl().insert(calendarId=self.calendarId, body=rule_owner).execute()
return self.service.acl().insert(
calendarId=self.calendarId,
body=rule_owner
).execute()

View File

@@ -1,39 +1,96 @@
import datetime
import logging
from typing import Union, Dict, Callable, Optional
from icalendar import Calendar, Event
from pytz import utc
import pydantic
def format_datetime_utc(value):
from .gcal import EventData, EventList
DateDateTime = Union[datetime.date, datetime.datetime]
class GCal_DateDateTime(pydantic.BaseModel):
date: Optional[str] = pydantic.Field(default=None)
date_time: Optional[str] = pydantic.Field(alias='dateTime', default=None)
timezone: Optional[str] = pydantic.Field(alias='timeZone', default=None)
@pydantic.root_validator(allow_reuse=True)
def check_only_date_or_datetime(cls, values):
date = values.get('date', None)
date_time = values.get('date_time', None)
assert (date is None) != (date_time is None), \
'only date or date_time must be provided'
return values
@classmethod
def create_from(cls, value: DateDateTime) -> 'GCal_DateDateTime':
key: str = 'date'
str_value: str = ''
if type(value) is datetime.datetime:
key = 'date_time'
str_value = format_datetime_utc(value)
else:
str_value = value.isoformat()
return cls(**{key: str_value})
class Config:
allow_population_by_field_name = True
class GCal_Event(pydantic.BaseModel):
created: Optional[str] = None
updated: Optional[str] = None
summary: Optional[str] = None
description: Optional[str] = None
location: Optional[str] = None
start: GCal_DateDateTime
end: GCal_DateDateTime
transparency: Optional[str] = None
ical_uid: str = pydantic.Field(alias='iCalUID')
class Config:
allow_population_by_field_name = True
def format_datetime_utc(value: DateDateTime) -> str:
"""utc datetime as string from date or datetime value
Arguments:
value -- date or datetime value
Returns:
utc datetime value as string in iso format
"""
if not isinstance(value, datetime.datetime):
if type(value) is datetime.date:
value = datetime.datetime(
value.year, value.month, value.day, tzinfo=utc)
value = value.replace(microsecond=1)
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(value, check_value=None):
def gcal_date_or_dateTime(value: DateDateTime,
check_value: Optional[DateDateTime] = None) \
-> Dict[str, str]:
"""date or dateTime to gcal (start or end dict)
Arguments:
value -- date or datetime value
check_value - date or datetime to choise result type (if not None)
value: date or datetime
check_value: optional for choose result type
Returns:
dict { 'date': ... } or { 'dateTime': ... }
{ 'date': ... } or { 'dateTime': ... }
"""
if check_value is None:
check_value = value
result = {}
result: Dict[str, str] = {}
if isinstance(check_value, datetime.datetime):
result['dateTime'] = format_datetime_utc(value)
else:
@@ -49,7 +106,7 @@ class EventConverter(Event):
( https://developers.google.com/calendar/v3/reference/events#resource-representations )
"""
def _str_prop(self, prop):
def _str_prop(self, prop: str) -> str:
"""decoded string property
Arguments:
@@ -61,7 +118,7 @@ class EventConverter(Event):
return self.decoded(prop).decode(encoding='utf-8')
def _datetime_str_prop(self, prop):
def _datetime_str_prop(self, prop: str) -> str:
"""utc datetime as string from property
Arguments:
@@ -73,7 +130,7 @@ class EventConverter(Event):
return format_datetime_utc(self.decoded(prop))
def _gcal_start(self):
def _gcal_start(self) -> GCal_DateDateTime:
""" event start dict from icalendar event
Raises:
@@ -84,9 +141,9 @@ class EventConverter(Event):
"""
value = self.decoded('DTSTART')
return gcal_date_or_dateTime(value)
return GCal_DateDateTime.create_from(value)
def _gcal_end(self):
def _gcal_end(self) -> GCal_DateDateTime:
"""event end dict from icalendar event
Raises:
@@ -98,18 +155,24 @@ class EventConverter(Event):
result = None
if 'DTEND' in self:
value = self.decoded('DTEND')
result = gcal_date_or_dateTime(value)
result = GCal_DateDateTime.create_from(value)
elif 'DURATION' in self:
start_val = self.decoded('DTSTART')
duration = self.decoded('DURATION')
end_val = start_val + duration
if type(start_val) is datetime.date:
if type(end_val) is datetime.datetime:
end_val = datetime.date(
end_val.year, end_val.month, end_val.day)
result = gcal_date_or_dateTime(end_val, check_value=start_val)
result = GCal_DateDateTime.create_from(end_val)
else:
raise ValueError('no DTEND or DURATION')
return result
def _put_to_gcal(self, gcal_event, prop, func, ics_prop=None):
def _put_to_gcal(self, gcal_event: EventData,
prop: str, func: Callable[[str], str],
ics_prop: Optional[str] = None):
"""get property from ical event if exist, and put to gcal event
Arguments:
@@ -124,54 +187,62 @@ class EventConverter(Event):
if ics_prop in self:
gcal_event[prop] = func(ics_prop)
def to_gcal(self):
def _get_prop(self, prop: str, func: Callable[[str], str]):
"""get property from ical event if exist else None
Arguments:
prop -- property name
func -- function to convert
"""
if prop not in self:
return None
return func(prop)
def to_gcal(self) -> EventData:
"""Convert
Returns:
dict - google calendar#event resource
"""
event = {
'iCalUID': self._str_prop('UID')
kwargs = {
'ical_uid': self._str_prop('UID'),
'start': self._gcal_start(),
'end': self._gcal_end(),
'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()),
}
event['start'] = self._gcal_start()
event['end'] = self._gcal_end()
self._put_to_gcal(event, 'summary', self._str_prop)
self._put_to_gcal(event, 'description', self._str_prop)
self._put_to_gcal(event, 'location', self._str_prop)
self._put_to_gcal(event, 'created', self._datetime_str_prop)
self._put_to_gcal(
event, 'updated', self._datetime_str_prop, 'LAST-MODIFIED')
self._put_to_gcal(
event, 'transparency', lambda prop: self._str_prop(prop).lower(), 'TRANSP')
return event
return GCal_Event(**kwargs).dict(by_alias=True, exclude_defaults=True)
class CalendarConverter():
class CalendarConverter:
"""Convert icalendar events to google calendar resources
"""
logger = logging.getLogger('CalendarConverter')
def __init__(self, calendar=None):
self.calendar = calendar
def __init__(self, calendar: Optional[Calendar] = None):
self.calendar: Optional[Calendar] = calendar
def load(self, filename):
""" load calendar from ics file
def load(self, filename: str):
""" 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):
def loads(self, string: str):
""" load calendar from ics string
"""
self.calendar = Calendar.from_ical(string)
def events_to_gcal(self):
def events_to_gcal(self) -> EventList:
"""Convert events to google calendar resources
"""

View File

@@ -1,103 +1,144 @@
import argparse
import datetime
import logging.config
from typing import Optional, Dict, Any, List
import fire
import yaml
from pytz import utc
from . import GoogleCalendar, GoogleCalendarService
def parse_args():
parser = argparse.ArgumentParser(
description="manage google calendars in service account")
command_subparsers = parser.add_subparsers(help='command', dest='command')
command_subparsers.add_parser('list', help='list calendars')
parser_create = command_subparsers.add_parser(
'create', help='create calendar')
parser_create.add_argument(
'summary', action='store', help='new calendar summary')
parser_create.add_argument('--timezone', action='store',
default=None, required=False, help='new calendar timezone')
parser_create.add_argument(
'--public', default=False, action='store_true', help='make calendar public')
parser_add_owner = command_subparsers.add_parser(
'add_owner', help='add owner to calendar')
parser_add_owner.add_argument('id', action='store', help='calendar id')
parser_add_owner.add_argument(
'owner_email', action='store', help='new owner email')
parser_remove = command_subparsers.add_parser(
'remove', help='remove calendar')
parser_remove.add_argument(
'id', action='store', help='calendar id to remove')
parser_rename = command_subparsers.add_parser(
'rename', help='rename calendar')
parser_rename.add_argument(
'id', action='store', help='calendar id')
parser_rename.add_argument(
'summary', action='store', help='new summary')
args = parser.parse_args()
if args.command is None:
parser.print_usage()
return args
def load_config(filename: str) -> Optional[Dict[str, Any]]:
result = None
try:
with open(filename, 'r', encoding='utf-8') as f:
result = yaml.safe_load(f)
except FileNotFoundError:
pass
def load_config():
with open('config.yml', 'r', encoding='utf-8') as f:
result = yaml.safe_load(f)
return result
def list_calendars(service):
response = service.calendarList().list(fields='items(id,summary)').execute()
for calendar in response.get('items'):
print('{summary}: {id}'.format_map(calendar))
class PropertyCommands:
""" get/set google calendar properties """
def __init__(self, _service):
self._service = _service
def get(self, calendar_id: str, property_name: str) -> None:
""" get calendar property
Args:
calendar_id: calendar id
property_name: property key
"""
response = self._service.calendarList().get(calendarId=calendar_id,
fields=property_name).execute()
print(response.get(property_name))
def set(self, calendar_id: str, property_name: str, property_value: str) -> None:
""" set calendar property
Args:
calendar_id: calendar id
property_name: property key
property_value: property value
"""
body = {property_name: property_value}
response = self._service.calendarList().patch(body=body, calendarId=calendar_id).execute()
print(response)
def create_calendar(service, summary, timezone, public):
calendar = GoogleCalendar(service, None)
calendar.create(summary, timezone)
if public:
calendar.make_public()
print('{}: {}'.format(summary, calendar.calendarId))
class Commands:
""" manage google calendars in service account """
def __init__(self, config: str = 'config.yml'):
"""
def add_owner(service, id, owner_email):
calendar = GoogleCalendar(service, id)
calendar.add_owner(owner_email)
print('to {} added owner: {}'.format(id, owner_email))
Args:
config(str): config filename
"""
self._config: Optional[Dict[str, Any]] = load_config(config)
if self._config is not None and 'logging' in self._config:
logging.config.dictConfig(self._config['logging'])
self._service = GoogleCalendarService.from_config(self._config)
self.property = PropertyCommands(self._service)
def list(self, show_hidden: bool = False, show_deleted: bool = False) -> None:
""" list calendars
def remove_calendar(service, id):
calendar = GoogleCalendar(service, id)
calendar.delete()
print('removed: {}'.format(id))
Args:
show_hidden: show hidden calendars
show_deleted: show deleted calendars
"""
fields: str = 'nextPageToken,items(id,summary)'
calendars: List[Dict[str, Any]] = []
page_token: Optional[str] = None
while True:
calendars_api = self._service.calendarList()
response = calendars_api.list(fields=fields,
pageToken=page_token,
showHidden=show_hidden,
showDeleted=show_deleted
).execute()
if 'items' in response:
calendars.extend(response['items'])
page_token = response.get('nextPageToken')
if page_token is None:
break
for calendar in calendars:
print('{summary}: {id}'.format_map(calendar))
def create(self, summary: str, timezone: Optional[str] = None, public: bool = False) -> None:
""" create calendar
Args:
summary: new calendar summary
timezone: new calendar timezone
public: make calendar public
"""
calendar = GoogleCalendar(self._service, None)
calendar.create(summary, timezone)
if public:
calendar.make_public()
print('{}: {}'.format(summary, calendar.calendarId))
def add_owner(self, calendar_id: str, email: str) -> None:
""" add owner to calendar
Args:
calendar_id: calendar id
email: new owner email
"""
calendar = GoogleCalendar(self._service, calendar_id)
calendar.add_owner(email)
print('to {} added owner: {}'.format(calendar_id, email))
def remove(self, calendar_id: str) -> None:
""" remove calendar
Args:
calendar_id: calendar id
"""
calendar = GoogleCalendar(self._service, calendar_id)
calendar.delete()
print('removed: {}'.format(calendar_id))
def rename(self, calendar_id: str, summary: str) -> None:
""" rename calendar
Args:
calendar_id: calendar id
summary:
"""
calendar = {'summary': summary}
self._service.calendars().patch(body=calendar, calendarId=calendar_id).execute()
print('{}: {}'.format(summary, calendar_id))
def rename_calendar(service, id, summary):
calendar = {'summary': summary}
service.calendars().patch(body=calendar, calendarId=id).execute()
print('{}: {}'.format(summary, id))
def main():
args = parse_args()
config = load_config()
fire.Fire(Commands, name='manage-ics2gcal')
if 'logging' in config:
logging.config.dictConfig(config['logging'])
service = GoogleCalendarService.from_config(config)
if 'list' == args.command:
list_calendars(service)
elif 'create' == args.command:
create_calendar(service, args.summary, args.timezone, args.public)
elif 'add_owner' == args.command:
add_owner(service, args.id, args.owner_email)
elif 'remove' == args.command:
remove_calendar(service, args.id)
elif 'rename' == args.command:
rename_calendar(service, args.id, args.summary)
if __name__ == '__main__':
main()

View File

@@ -1,22 +1,33 @@
import datetime
import dateutil.parser
import logging
import operator
from typing import List, Dict, Set, Tuple, Union, Callable
import dateutil.parser
from pytz import utc
from .gcal import GoogleCalendar, EventData, EventList, EventTuple
from .ical import CalendarConverter, DateDateTime
class CalendarSync():
class CalendarSync:
"""class for syncronize calendar with google
"""
logger = logging.getLogger('CalendarSync')
def __init__(self, gcalendar, converter):
self.gcalendar = gcalendar
self.converter = converter
def __init__(self, gcalendar: GoogleCalendar, converter: CalendarConverter):
self.gcalendar: GoogleCalendar = gcalendar
self.converter: CalendarConverter = converter
self.to_insert: EventList = []
self.to_update: List[EventTuple] = []
self.to_delete: EventList = []
@staticmethod
def _events_list_compare(items_src, items_dst, key='iCalUID'):
def _events_list_compare(items_src: EventList,
items_dst: EventList,
key: str = 'iCalUID') \
-> Tuple[EventList, List[EventTuple], EventList]:
""" compare list of events by key
Arguments:
@@ -25,21 +36,23 @@ class CalendarSync():
key {str} -- name of key to compare (default: {'iCalUID'})
Returns:
tuple -- (items_to_insert,
items_to_update,
tuple -- (items_to_insert,
items_to_update,
items_to_delete)
"""
def get_key(item): return item[key]
def get_key(item: EventData) -> str: return item[key]
keys_src = set(map(get_key, items_src))
keys_dst = set(map(get_key, items_dst))
keys_src: Set[str] = set(map(get_key, items_src))
keys_dst: Set[str] = set(map(get_key, items_dst))
keys_to_insert = keys_src - keys_dst
keys_to_update = keys_src & keys_dst
keys_to_delete = keys_dst - keys_src
def items_by_keys(items, key_name, keys):
def items_by_keys(items: EventList,
key_name: str,
keys: Set[str]) -> EventList:
return list(filter(lambda item: item[key_name] in keys, items))
items_to_insert = items_by_keys(items_src, key, keys_to_insert)
@@ -57,14 +70,19 @@ class CalendarSync():
""" filter 'to_update' events by 'updated' datetime
"""
def filter_updated(event_tuple):
def filter_updated(event_tuple: EventTuple) -> bool:
new, old = event_tuple
return dateutil.parser.parse(new['updated']) > dateutil.parser.parse(old['updated'])
new_date = dateutil.parser.parse(new['updated'])
old_date = dateutil.parser.parse(old['updated'])
return new_date > old_date
self.to_update = list(filter(filter_updated, self.to_update))
@staticmethod
def _filter_events_by_date(events, date, op):
def _filter_events_by_date(events: EventList,
date: DateDateTime,
op: Callable[[DateDateTime,
DateDateTime], bool]) -> EventList:
""" filter events by start datetime
Arguments:
@@ -76,10 +94,10 @@ class CalendarSync():
list of filtred events
"""
def filter_by_date(event):
def filter_by_date(event: EventData) -> bool:
date_cmp = date
event_start = event['start']
event_date = None
event_start: Dict[str, str] = event['start']
event_date: Union[DateDateTime, str, None] = None
compare_dates = False
if 'date' in event_start:
@@ -87,18 +105,19 @@ class CalendarSync():
compare_dates = True
elif 'dateTime' in event_start:
event_date = event_start['dateTime']
event_date = dateutil.parser.parse(event_date)
if compare_dates:
date_cmp = datetime.date(date.year, date.month, date.day)
event_date = datetime.date(event_date.year, event_date.month, event_date.day)
event_date = datetime.date(
event_date.year, event_date.month, event_date.day)
return op(event_date, date_cmp)
return list(filter(filter_by_date, events))
@staticmethod
def _tz_aware_datetime(date):
def _tz_aware_datetime(date: DateDateTime) -> datetime.datetime:
"""make tz aware datetime from datetime/date (utc if no tzinfo)
Arguments:
@@ -114,7 +133,7 @@ class CalendarSync():
date = date.replace(tzinfo=utc)
return date
def prepare_sync(self, start_date):
def prepare_sync(self, start_date: DateDateTime) -> None:
"""prepare sync lists by comparsion of events
Arguments:
@@ -132,42 +151,45 @@ class CalendarSync():
events_src_past = CalendarSync._filter_events_by_date(
events_src, start_date, operator.lt)
events_src = None
# first events comparsion
self.to_insert, self.to_update, self.to_delete = CalendarSync._events_list_compare(
events_src_pending, events_dst)
events_src_pending, events_dst = None, None
# find in events 'to_delete' past events from source, for update (move to past)
_, add_to_update, self.to_delete = CalendarSync._events_list_compare(
events_src_past, self.to_delete)
self.to_update.extend(add_to_update)
events_src_past = None
# find if events 'to_insert' exists in gcalendar, for update them
add_to_update, self.to_insert = self.gcalendar.find_exists(
self.to_insert)
self.to_update.extend(add_to_update)
add_to_update = None
# exclude outdated events from 'to_update' list, by 'updated' field
self._filter_events_to_update()
self.logger.info('prepared to sync: ( insert: %d, update: %d, delete: %d )',
len(self.to_insert), len(self.to_update), len(self.to_delete))
self.logger.info(
'prepared to sync: ( insert: %d, update: %d, delete: %d )',
len(self.to_insert),
len(self.to_update),
len(self.to_delete)
)
def apply(self):
"""apply sync (insert, update, delete), using prepared lists of events
def clear(self) -> None:
""" clear prepared sync lists (insert, update, delete)
"""
self.to_insert.clear()
self.to_update.clear()
self.to_delete.clear()
def apply(self) -> None:
""" apply sync (insert, update, delete), using prepared lists of events
"""
self.gcalendar.insert_events(self.to_insert)
self.gcalendar.update_events(self.to_update)
self.gcalendar.delete_events(self.to_delete)
self.logger.info('sync done')
self.clear()
self.to_insert, self.to_update, self.to_delete = [], [], []
self.logger.info('sync done')

View File

@@ -1,3 +1,5 @@
from typing import Dict, Any
import yaml
import dateutil.parser
@@ -11,14 +13,14 @@ from . import (
CalendarSync
)
def load_config():
def load_config() -> Dict[str, Any]:
with open('config.yml', 'r', encoding='utf-8') as f:
result = yaml.safe_load(f)
return result
def get_start_date(date_str):
result = datetime.datetime(1,1,1)
def get_start_date(date_str: str) -> datetime.datetime:
if 'now' == date_str:
result = datetime.datetime.utcnow()
else:
@@ -32,8 +34,8 @@ def main():
if 'logging' in config:
logging.config.dictConfig(config['logging'])
calendarId = config['calendar']['google_id']
ics_filepath = config['calendar']['source']
calendarId: str = config['calendar']['google_id']
ics_filepath: str = config['calendar']['source']
start = get_start_date(config['start_from'])
@@ -47,5 +49,6 @@ def main():
sync.prepare_sync(start)
sync.apply()
if __name__ == '__main__':
main()

105
tests/bench_converter.py Normal file
View File

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

View File

@@ -1,3 +1,5 @@
from typing import Tuple
import pytest
from sync_ics2gcal import CalendarConverter
@@ -26,11 +28,11 @@ LAST-MODIFIED:20180326T120235Z
"""
def ics_test_cal(content):
def ics_test_cal(content: str) -> str:
return "BEGIN:VCALENDAR\r\n{}END:VCALENDAR\r\n".format(content)
def ics_test_event(content):
def ics_test_event(content: str) -> str:
return ics_test_cal("BEGIN:VEVENT\r\n{}END:VEVENT\r\n".format(content))
@@ -68,7 +70,7 @@ def param_events_start_end(request):
return request.param
def test_event_start_end(param_events_start_end):
def test_event_start_end(param_events_start_end: Tuple[str, str, str, str]):
(date_type, ics_str, start, end) = param_events_start_end
converter = CalendarConverter()
converter.loads(ics_str)

View File

@@ -3,6 +3,7 @@ import hashlib
import operator
from copy import deepcopy
from random import shuffle
from typing import Union, List, Dict, Optional
import dateutil.parser
import pytest
@@ -11,7 +12,7 @@ from pytz import timezone, utc
from sync_ics2gcal import CalendarSync
def sha1(string):
def sha1(string: Union[str, bytes]) -> str:
if isinstance(string, str):
string = string.encode('utf8')
h = hashlib.sha1()
@@ -19,55 +20,57 @@ def sha1(string):
return h.hexdigest()
def gen_events(start, stop, start_time, no_time=False):
def gen_events(start: int,
stop: int,
start_time: Union[datetime.datetime, datetime.date],
no_time: bool = False) -> List[Dict[str, Union[str, Dict[str, str]]]]:
if no_time:
start_time = datetime.date(
start_time.year, start_time.month, start_time.day)
duration = datetime.date(1, 1, 2) - datetime.date(1, 1, 1)
date_key = "date"
suff = ''
duration: datetime.timedelta = datetime.date(1, 1, 2) - datetime.date(1, 1, 1)
date_key: str = "date"
date_end: str = ''
else:
start_time = utc.normalize(
start_time.astimezone(utc)).replace(tzinfo=None)
duration = datetime.datetime(
1, 1, 1, 2) - datetime.datetime(1, 1, 1, 1)
date_key = "dateTime"
suff = 'Z'
duration: datetime.timedelta = datetime.datetime(1, 1, 1, 2) - datetime.datetime(1, 1, 1, 1)
date_key: str = "dateTime"
date_end: str = 'Z'
result = []
result: List[Dict[str, Union[str, Dict[str, str]]]] = []
for i in range(start, stop):
event_start = start_time + (duration * i)
event_end = event_start + duration
updated = event_start
updated: Union[datetime.datetime, datetime.date] = event_start
if no_time:
updated = datetime.datetime(
updated.year, updated.month, updated.day, 0, 0, 0, 1, tzinfo=utc)
event = {
event: Dict[str, Union[str, Dict[str, str]]] = {
'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'
"created": updated.isoformat() + 'Z',
'start': {date_key: event_start.isoformat() + date_end},
'end': {date_key: event_end.isoformat() + date_end}
}
event['start'] = {date_key: event_start.isoformat() + suff}
event['end'] = {date_key: event_end.isoformat() + suff}
result.append(event)
return result
def gen_list_to_compare(start, stop):
result = []
def gen_list_to_compare(start: int, stop: int) -> List[Dict[str, str]]:
result: List[Dict[str, str]] = []
for i in range(start, stop):
result.append({'iCalUID': 'test{:06d}'.format(i)})
return result
def get_start_date(event):
event_start = event['start']
start_date = None
def get_start_date(event: Dict[str, Union[str, Dict[str, str]]]) -> Union[datetime.datetime, datetime.date]:
event_start: Dict[str, str] = event['start']
start_date: Optional[str] = None
is_date = False
if 'date' in event_start:
start_date = event_start['date']
@@ -113,7 +116,7 @@ def test_compare():
@pytest.mark.parametrize("no_time", [True, False], ids=['date', 'dateTime'])
def test_filter_events_by_date(no_time):
def test_filter_events_by_date(no_time: bool):
msk = timezone('Europe/Moscow')
now = utc.localize(datetime.datetime.utcnow())
msk_now = msk.normalize(now.astimezone(msk))