faori commited on
Commit
550665c
1 Parent(s): a59db17

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +135 -0
  2. README.md +95 -12
  3. app.py +115 -0
  4. app1.py +45 -0
  5. configs/inference_electra_base.yaml +41 -0
  6. configs/inference_en_electra_base.yaml +43 -0
  7. configs/inference_en_electra_large.yaml +43 -0
  8. configs/train_distilbert.yaml +54 -0
  9. configs/train_en_electra_base_finetune.yaml +54 -0
  10. configs/train_en_electra_base_transfer.yaml +53 -0
  11. configs/train_en_electra_large_finetune.yaml +53 -0
  12. configs/train_en_electra_large_transfer.yaml +54 -0
  13. configs/train_roberta_base_finetune.yaml +53 -0
  14. configs/train_roberta_base_transfer.yaml +53 -0
  15. configs/train_roberta_large_finetune.yaml +53 -0
  16. configs/train_roberta_large_transfer.yaml +53 -0
  17. evaluate_squad_v2.py +136 -0
  18. evaluation_scriptv2.0.py +276 -0
  19. google-calendar-simple-api/.github/CODE_OF_CONDUCT.md +128 -0
  20. google-calendar-simple-api/.github/ISSUE_TEMPLATE/bug_report.md +57 -0
  21. google-calendar-simple-api/.github/workflows/code-cov.yml +31 -0
  22. google-calendar-simple-api/.github/workflows/tests.yml +31 -0
  23. google-calendar-simple-api/.gitignore +18 -0
  24. google-calendar-simple-api/.readthedocs.yml +16 -0
  25. google-calendar-simple-api/CONTRIBUTING.md +32 -0
  26. google-calendar-simple-api/LICENSE +21 -0
  27. google-calendar-simple-api/README.rst +92 -0
  28. google-calendar-simple-api/build/lib/gcsa/__init__.py +0 -0
  29. google-calendar-simple-api/build/lib/gcsa/_resource.py +8 -0
  30. google-calendar-simple-api/build/lib/gcsa/_services/__init__.py +0 -0
  31. google-calendar-simple-api/build/lib/gcsa/_services/acl_service.py +143 -0
  32. google-calendar-simple-api/build/lib/gcsa/_services/authentication.py +144 -0
  33. google-calendar-simple-api/build/lib/gcsa/_services/base_service.py +61 -0
  34. google-calendar-simple-api/build/lib/gcsa/_services/calendar_lists_service.py +123 -0
  35. google-calendar-simple-api/build/lib/gcsa/_services/calendars_service.py +102 -0
  36. google-calendar-simple-api/build/lib/gcsa/_services/colors_service.py +15 -0
  37. google-calendar-simple-api/build/lib/gcsa/_services/events_service.py +427 -0
  38. google-calendar-simple-api/build/lib/gcsa/_services/free_busy_service.py +87 -0
  39. google-calendar-simple-api/build/lib/gcsa/_services/settings_service.py +13 -0
  40. google-calendar-simple-api/build/lib/gcsa/acl.py +70 -0
  41. google-calendar-simple-api/build/lib/gcsa/attachment.py +72 -0
  42. google-calendar-simple-api/build/lib/gcsa/attendee.py +79 -0
  43. google-calendar-simple-api/build/lib/gcsa/calendar.py +267 -0
  44. google-calendar-simple-api/build/lib/gcsa/conference.py +416 -0
  45. google-calendar-simple-api/build/lib/gcsa/event.py +330 -0
  46. google-calendar-simple-api/build/lib/gcsa/free_busy.py +98 -0
  47. google-calendar-simple-api/build/lib/gcsa/google_calendar.py +81 -0
  48. google-calendar-simple-api/build/lib/gcsa/person.py +41 -0
  49. google-calendar-simple-api/build/lib/gcsa/recurrence.py +570 -0
  50. google-calendar-simple-api/build/lib/gcsa/reminders.py +137 -0
.gitignore ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ # Test notebook
132
+ test.ipynb
133
+
134
+ # Wandb
135
+ wandb/
README.md CHANGED
@@ -1,12 +1,95 @@
1
- ---
2
- title: HTK
3
- emoji: 🏢
4
- colorFrom: purple
5
- colorTo: purple
6
- sdk: streamlit
7
- sdk_version: 1.36.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: HTK
3
+ app_file: app1.py
4
+ sdk: gradio
5
+ sdk_version: 4.36.1
6
+ ---
7
+ # MRC-RetroReader
8
+
9
+ ## Introduction
10
+
11
+ MRC-RetroReader is a machine reading comprehension (MRC) model designed for reading comprehension tasks. The model leverages advanced neural network architectures to provide high accuracy in understanding and responding to textual queries.
12
+
13
+ ## Table of Contents
14
+
15
+ - [Introduction](#introduction)
16
+ - [Table of Contents](#table-of-contents)
17
+ - [Installation](#installation)
18
+ - [Usage](#usage)
19
+ - [Features](#features)
20
+ - [Dependencies](#dependencies)
21
+ - [Configuration](#configuration)
22
+ - [Documentation](#documentation)
23
+ - [Examples](#examples)
24
+ - [Troubleshooting](#troubleshooting)
25
+ - [Contributors](#contributors)
26
+ - [License](#license)
27
+
28
+ ## Installation
29
+
30
+ 1. Clone the repository:
31
+ ```
32
+ git clone https://github.com/phanhoang1803/MRC-RetroReader.git
33
+ cd MRC-RetroReader
34
+ ```
35
+ 2. Install the required dependencies:
36
+ ```
37
+ pip install -r requirements.txt
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ - For notebooks: to running automatically, turn off wandb, warning if necessary:
43
+ ```
44
+ wandb off
45
+ import warnings
46
+ warnings.filterwarnings('ignore')
47
+ ```
48
+ - To train the model using the SQuAD v2 dataset:
49
+ ```
50
+ python train_squad_v2.py --config path-to-yaml-file --module intensive --batch_size batch_size
51
+ ```
52
+
53
+ ## Features
54
+
55
+ - High accuracy MRC model
56
+ - Easy to train on custom datasets
57
+ - Configurable parameters for model tuning
58
+
59
+ ## Dependencies
60
+
61
+ - Python 3.x
62
+ - PyTorch
63
+ - Transformers
64
+ - Tokenizers
65
+
66
+ For a full list of dependencies, see `requirements.txt`.
67
+
68
+ ## Configuration
69
+
70
+ Configuration files can be found in the `configs` directory. Adjust the parameters in these files to customize the model training and evaluation.
71
+
72
+ ## Documentation
73
+
74
+ For detailed documentation, refer to the `documentation` directory. This includes:
75
+ - Model architecture
76
+ - Training procedures
77
+ - Evaluation metrics
78
+
79
+ ## Examples
80
+
81
+ Example training and evaluation scripts are provided in the repository. To train on the SQuAD v2 dataset:
82
+
83
+
84
+ ## Troubleshooting
85
+
86
+ For common issues and their solutions, refer to the `troubleshooting guide`.
87
+
88
+ ## Contributors
89
+
90
+ - phanhoang1803
91
+
92
+ ## License
93
+
94
+ This project is licensed under the MIT License. See the `LICENSE` file for details.
95
+
app.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import io
3
+ import os
4
+ import yaml
5
+ import pyarrow
6
+ import tokenizers
7
+
8
+ os.environ["TOKENIZERS_PARALLELISM"] = "true"
9
+
10
+ # Setting page config to wide mode
11
+ st.set_page_config(layout="wide")
12
+
13
+ @st.cache_resource
14
+ def from_library():
15
+ from retro_reader import RetroReader
16
+ from retro_reader import constants as C
17
+ return C, RetroReader
18
+
19
+ C, RetroReader = from_library()
20
+
21
+ my_hash_func = {
22
+ io.TextIOWrapper: lambda _: None,
23
+ pyarrow.lib.Buffer: lambda _: 0,
24
+ tokenizers.Tokenizer: lambda _: None,
25
+ tokenizers.AddedToken: lambda _: None
26
+ }
27
+
28
+ @st.cache_resource(hash_funcs=my_hash_func)
29
+ def load_en_electra_base_model():
30
+ config_file = "configs/inference_en_electra_base.yaml"
31
+ return RetroReader.load(config_file=config_file)
32
+
33
+ @st.cache_resource(hash_funcs=my_hash_func)
34
+ def load_en_electra_large_model():
35
+ config_file = "configs/inference_en_electra_large.yaml"
36
+ return RetroReader.load(config_file=config_file)
37
+
38
+ RETRO_READER_HOST = {
39
+ "google/electra-base-discriminator": load_en_electra_base_model(),
40
+ "google/electra-large-discriminator": load_en_electra_large_model(),
41
+ }
42
+
43
+ def display_top_predictions(nbest_preds, top_k=10):
44
+ # Assuming nbest_preds might be a dictionary with a key that contains the list
45
+ if not isinstance(nbest_preds, list):
46
+ nbest_preds = nbest_preds['id-01'] # Adjust key as per actual structure
47
+
48
+ sorted_preds = sorted(nbest_preds, key=lambda x: x['probability'], reverse=True)[:top_k]
49
+ st.markdown("### Top Predictions")
50
+ for i, pred in enumerate(sorted_preds, 1):
51
+ st.markdown(f"**{i}. {pred['text']}** - Probability: {pred['probability']*100:.2f}%")
52
+
53
+ def main():
54
+ # Sidebar Introduction
55
+ st.sidebar.title("📝 Welcome to Retro Reader")
56
+ st.sidebar.write("""
57
+ MRC-RetroReader is a machine reading comprehension (MRC) model designed for reading comprehension tasks. The model leverages advanced neural network architectures to provide high accuracy in understanding and responding to textual queries.
58
+ """)
59
+ image_url = "img.jpg" # Replace this URL with your actual image URL or local path
60
+ st.sidebar.image(image_url, use_column_width=True)
61
+ st.sidebar.title("Contributors")
62
+ st.sidebar.write("""
63
+ - Phan Van Hoang
64
+ - Pham Long Khanh
65
+ """)
66
+
67
+ st.title("Retrospective Reader Demo")
68
+ st.markdown("## Model name🚨")
69
+ option = st.selectbox(
70
+ label="Choose the model used in retro reader",
71
+ options=(
72
+ "[1] google/electra-base-discriminator",
73
+ "[2] google/electra-large-discriminator"
74
+ ),
75
+ index=1,
76
+ )
77
+ lang_code, model_name = option.split(" ")
78
+ retro_reader = RETRO_READER_HOST[model_name]
79
+
80
+ lang_prefix = "EN"
81
+ height = 200
82
+ return_submodule_outputs = True
83
+
84
+ with st.form(key="my_form"):
85
+ st.markdown("## Type your query ❓")
86
+ query = st.text_input(
87
+ label="",
88
+ value=getattr(C, f"{lang_prefix}_EXAMPLE_QUERY"),
89
+ max_chars=None,
90
+ help=getattr(C, f"{lang_prefix}_QUERY_HELP_TEXT"),
91
+ )
92
+ st.markdown("## Type your query 💬")
93
+ context = st.text_area(
94
+ label="",
95
+ value=getattr(C, f"{lang_prefix}_EXAMPLE_CONTEXTS"),
96
+ height=height,
97
+ max_chars=None,
98
+ help=getattr(C, f"{lang_prefix}_CONTEXT_HELP_TEXT"),
99
+ )
100
+ submit_button = st.form_submit_button(label="Submit")
101
+
102
+ if submit_button:
103
+ with st.spinner("🕒 Please wait.."):
104
+ outputs = retro_reader(query=query, context=context, return_submodule_outputs=return_submodule_outputs)
105
+ answer, score = outputs[0]["id-01"], outputs[1]
106
+ if not answer:
107
+ answer = "No answer"
108
+ st.markdown("## 📜 Results")
109
+ st.write(answer)
110
+ if return_submodule_outputs:
111
+ score_ext, nbest_preds, score_diff = outputs[2:]
112
+ display_top_predictions(nbest_preds)
113
+
114
+ if __name__ == "__main__":
115
+ main()
app1.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import io
3
+ import os
4
+ import yaml
5
+ import pyarrow
6
+ import tokenizers
7
+ from retro_reader import RetroReader
8
+
9
+ os.environ["TOKENIZERS_PARALLELISM"] = "true"
10
+
11
+ def from_library():
12
+ from retro_reader import constants as C
13
+ return C, RetroReader
14
+
15
+ C, RetroReader = from_library()
16
+
17
+ # Assuming RetroReader.load is a method from your imports
18
+ def load_model(config_path):
19
+ return RetroReader.load(config_file=config_path)
20
+
21
+ # Loading models
22
+ model_base = load_model("configs/inference_en_electra_base.yaml")
23
+ model_large = load_model("configs/inference_en_electra_large.yaml")
24
+
25
+ def retro_reader_demo(query, context, model_choice):
26
+ model = model_base if model_choice == "Base" else model_large
27
+ outputs = model(query=query, context=context, return_submodule_outputs=True)
28
+ answer = outputs[0]["id-01"] if outputs[0]["id-01"] else "No answer found"
29
+ return answer
30
+
31
+ # Gradio app interface
32
+ iface = gr.Interface(
33
+ fn=retro_reader_demo,
34
+ inputs=[
35
+ gr.Textbox(label="Query", placeholder="Type your query here..."),
36
+ gr.Textbox(label="Context", placeholder="Provide the context here...", lines=10),
37
+ gr.Radio(choices=["Base", "Large"], label="Model Choice")
38
+ ],
39
+ outputs=gr.Textbox(label="Answer"),
40
+ title="Retrospective Reader Demo",
41
+ description="This interface uses the RetroReader model to perform reading comprehension tasks."
42
+ )
43
+
44
+ if __name__ == "__main__":
45
+ iface.launch(share=True)
configs/inference_electra_base.yaml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # ModelArguments
23
+ use_auth_token: False
24
+
25
+ # SketchModelArguments
26
+ sketch_revision: en-electra-large-sketch
27
+ sketch_model_name: jinmang2/retro-reader
28
+ sketch_architectures: ElectraForSequenceClassification
29
+
30
+ # IntensiveModelArguments
31
+ intensive_revision: en-electra-large-intensive
32
+ intensive_model_name: jinmang2/retro-reader
33
+ intensive_architectures: ElectraForQuestionAnsweringAVPool
34
+
35
+
36
+ TrainingArguments:
37
+ output_dir: outputs
38
+ no_cuda: True # If you want to use cuda,
39
+ # change `no_cuda` to False and `fp16` to True
40
+ per_device_train_batch_size: 1
41
+ per_device_eval_batch_size: 12
configs/inference_en_electra_base.yaml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # ModelArguments
23
+ use_auth_token: False
24
+
25
+ # SketchModelArguments
26
+ sketch_revision: en-electra-base-sketch
27
+ sketch_model_name: faori/retro_reeader
28
+ # sketch_model_mode: transfer
29
+ sketch_architectures: ElectraForSequenceClassification
30
+
31
+ # IntensiveModelArguments
32
+ intensive_revision: en-electra-base-intensive
33
+ intensive_model_name: faori/retro_reeader
34
+ # intensive_model_mode: transfer
35
+ intensive_architectures: ElectraForQuestionAnsweringAVPool
36
+
37
+
38
+ TrainingArguments:
39
+ output_dir: outputs
40
+ no_cuda: True # If you want to use cuda,
41
+ # change `no_cuda` to False and `fp16` to True
42
+ per_device_train_batch_size: 1
43
+ per_device_eval_batch_size: 12
configs/inference_en_electra_large.yaml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # ModelArguments
23
+ use_auth_token: False
24
+
25
+ # SketchModelArguments
26
+ sketch_revision: en-electra-large-sketch
27
+ sketch_model_name: jinmang2/retro-reader
28
+ # sketch_model_mode: transfer
29
+ sketch_architectures: ElectraForSequenceClassification
30
+
31
+ # IntensiveModelArguments
32
+ intensive_revision: en-electra-large-intensive
33
+ intensive_model_name: jinmang2/retro-reader
34
+ # intensive_model_mode: transfer
35
+ intensive_architectures: ElectraForQuestionAnsweringAVPool
36
+
37
+
38
+ TrainingArguments:
39
+ output_dir: outputs
40
+ no_cuda: True # If you want to use cuda,
41
+ # change `no_cuda` to False and `fp16` to True
42
+ per_device_train_batch_size: 1
43
+ per_device_eval_batch_size: 12
configs/train_distilbert.yaml ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # SketchModelArguments
23
+ sketch_model_name: distilbert/distilbert-base-uncased
24
+ # sketch_model_mode: transfer
25
+ sketch_architectures: DistilBertForSequenceClassification
26
+
27
+ # IntensiveModelArguments
28
+ intensive_model_name: distilbert-base-uncased
29
+ intensive_model_mode: transfer
30
+ intensive_architectures: DistilBertForQuestionAnsweringAVPool
31
+
32
+
33
+ TrainingArguments:
34
+ # report_to: wandb
35
+ run_name: squadv2-distilbert-base-sketch,squadv2-distilbert-base-intensive
36
+ output_dir: outputs
37
+ overwrite_output_dir: False
38
+ learning_rate: 2e-5
39
+ evaluation_strategy: epoch
40
+ save_strategy: steps # Save checkpoints every specified number of steps
41
+ # save_steps: 5000 # Save model checkpoints every 5000 steps
42
+ save_steps: 5000
43
+ save_total_limit: 2 # Maximum number of checkpoints to keep
44
+ # load_best_model_at_end: True # Disable to avoid loading the best model at the end
45
+ # no need to specify checkpoint_dir, it defaults to output_dir
46
+ # no need to specify logging_dir, it defaults to output_dir
47
+ per_device_train_batch_size: 64
48
+ per_device_eval_batch_size: 64
49
+ num_train_epochs: 10.0
50
+ # no need to specify metric_for_best_model for resuming from checkpoints
51
+ no_cuda: False
52
+ fp16: True
53
+ warmup_ratio: 0.1
54
+ weight_decay: 0.01
configs/train_en_electra_base_finetune.yaml ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # SketchModelArguments
23
+ sketch_model_name: google/electra-base-discriminator
24
+ sketch_architectures: ElectraForSequenceClassification
25
+
26
+ # IntensiveModelArguments
27
+ intensive_model_name: google/electra-base-discriminator
28
+ intensive_model_mode: finetune
29
+ intensive_architectures: ElectraForQuestionAnsweringAVPool
30
+
31
+
32
+ TrainingArguments:
33
+ # report_to: wandb
34
+ run_name: squadv2-electra-base-sketch,squadv2-electra-base-intensive
35
+ output_dir: outputs
36
+ overwrite_output_dir: False
37
+ learning_rate: 2e-5
38
+ evaluation_strategy: epoch
39
+ save_strategy: steps # Save checkpoints every specified number of steps
40
+ # save_steps: 5000 # Save model checkpoints every 5000 steps
41
+ save_steps: 5000
42
+ save_total_limit: 2 # Maximum number of checkpoints to keep
43
+ # load_best_model_at_end: True # Disable to avoid loading the best model at the end
44
+ # no need to specify checkpoint_dir, it defaults to output_dir
45
+ # no need to specify logging_dir, it defaults to output_dir
46
+ per_device_train_batch_size: 64
47
+ per_device_eval_batch_size: 64
48
+ num_train_epochs: 10.0
49
+ # no need to specify metric_for_best_model for resuming from checkpoints
50
+ no_cuda: False
51
+ fp16: True
52
+ warmup_ratio: 0.1
53
+ weight_decay: 0.01
54
+
configs/train_en_electra_base_transfer.yaml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # SketchModelArguments
23
+ sketch_model_name: google/electra-base-discriminator
24
+ sketch_architectures: ElectraForSequenceClassification
25
+
26
+ # IntensiveModelArguments
27
+ intensive_model_name: google/electra-base-discriminator
28
+ intensive_model_mode: transfer
29
+ intensive_architectures: ElectraForQuestionAnsweringAVPool
30
+
31
+
32
+ TrainingArguments:
33
+ # report_to: wandb
34
+ run_name: squadv2-electra-base-sketch,squadv2-electra-base-intensive
35
+ output_dir: outputs
36
+ overwrite_output_dir: False
37
+ learning_rate: 2e-5
38
+ evaluation_strategy: epoch
39
+ save_strategy: steps # Save checkpoints every specified number of steps
40
+ # save_steps: 5000 # Save model checkpoints every 5000 steps
41
+ save_steps: 5000
42
+ save_total_limit: 2 # Maximum number of checkpoints to keep
43
+ # load_best_model_at_end: True # Disable to avoid loading the best model at the end
44
+ # no need to specify checkpoint_dir, it defaults to output_dir
45
+ # no need to specify logging_dir, it defaults to output_dir
46
+ per_device_train_batch_size: 512
47
+ per_device_eval_batch_size: 512
48
+ num_train_epochs: 10.0
49
+ # no need to specify metric_for_best_model for resuming from checkpoints
50
+ no_cuda: False
51
+ fp16: True
52
+ warmup_ratio: 0.1
53
+ weight_decay: 0.01
configs/train_en_electra_large_finetune.yaml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # SketchModelArguments
23
+ sketch_model_name: google/electra-large-discriminator
24
+ sketch_architectures: ElectraForSequenceClassification
25
+
26
+ # IntensiveModelArguments
27
+ intensive_model_name: google/electra-large-discriminator
28
+ intensive_model_mode: finetune
29
+ intensive_architectures: ElectraForQuestionAnsweringAVPool
30
+
31
+
32
+ TrainingArguments:
33
+ # report_to: wandb
34
+ run_name: squadv2-electra-base-sketch,squadv2-electra-base-intensive
35
+ output_dir: outputs
36
+ overwrite_output_dir: False
37
+ learning_rate: 2e-5
38
+ evaluation_strategy: epoch
39
+ save_strategy: steps # Save checkpoints every specified number of steps
40
+ # save_steps: 5000 # Save model checkpoints every 5000 steps
41
+ save_steps: 5000
42
+ save_total_limit: 2 # Maximum number of checkpoints to keep
43
+ # load_best_model_at_end: True # Disable to avoid loading the best model at the end
44
+ # no need to specify checkpoint_dir, it defaults to output_dir
45
+ # no need to specify logging_dir, it defaults to output_dir
46
+ per_device_train_batch_size: 8
47
+ per_device_eval_batch_size: 8
48
+ num_train_epochs: 10.0
49
+ # no need to specify metric_for_best_model for resuming from checkpoints
50
+ no_cuda: False
51
+ fp16: True
52
+ warmup_ratio: 0.1
53
+ weight_decay: 0.01
configs/train_en_electra_large_transfer.yaml ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # SketchModelArguments
23
+ sketch_model_name: google/electra-large-discriminator
24
+ # sketch_model_mode: transfer
25
+ sketch_architectures: ElectraForSequenceClassification
26
+
27
+ # IntensiveModelArguments
28
+ intensive_model_name: google/electra-large-discriminator
29
+ intensive_model_mode: transfer
30
+ intensive_architectures: ElectraForQuestionAnsweringAVPool
31
+
32
+
33
+ TrainingArguments:
34
+ # report_to: wandb
35
+ run_name: squadv2-electra-base-sketch,squadv2-electra-base-intensive
36
+ output_dir: outputs
37
+ overwrite_output_dir: False
38
+ learning_rate: 2e-5
39
+ evaluation_strategy: epoch
40
+ save_strategy: steps # Save checkpoints every specified number of steps
41
+ # save_steps: 5000 # Save model checkpoints every 5000 steps
42
+ save_steps: 5000
43
+ save_total_limit: 2 # Maximum number of checkpoints to keep
44
+ # load_best_model_at_end: True # Disable to avoid loading the best model at the end
45
+ # no need to specify checkpoint_dir, it defaults to output_dir
46
+ # no need to specify logging_dir, it defaults to output_dir
47
+ per_device_train_batch_size: 512
48
+ per_device_eval_batch_size: 512
49
+ num_train_epochs: 10.0
50
+ # no need to specify metric_for_best_model for resuming from checkpoints
51
+ no_cuda: False
52
+ fp16: True
53
+ warmup_ratio: 0.1
54
+ weight_decay: 0.01
configs/train_roberta_base_finetune.yaml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # SketchModelArguments
23
+ sketch_model_name: FacebookAI/roberta-base
24
+ sketch_architectures: RobertaForSequenceClassification
25
+
26
+ # IntensiveModelArguments
27
+ intensive_model_name: FacebookAI/roberta-base
28
+ intensive_model_mode: finetune
29
+ intensive_architectures: RobertaForQuestionAnsweringAVPool
30
+
31
+
32
+ TrainingArguments:
33
+ # report_to: wandb
34
+ run_name: squadv2-roberta-base-sketch,squadv2-roberta-base-intensive
35
+ output_dir: outputs
36
+ overwrite_output_dir: False
37
+ learning_rate: 2e-5
38
+ evaluation_strategy: epoch
39
+ save_strategy: steps # Save checkpoints every specified number of steps
40
+ # save_steps: 5000 # Save model checkpoints every 5000 steps
41
+ save_steps: 5000
42
+ save_total_limit: 2 # Maximum number of checkpoints to keep
43
+ # load_best_model_at_end: True # Disable to avoid loading the best model at the end
44
+ # no need to specify checkpoint_dir, it defaults to output_dir
45
+ # no need to specify logging_dir, it defaults to output_dir
46
+ per_device_train_batch_size: 64
47
+ per_device_eval_batch_size: 64
48
+ num_train_epochs: 10.0
49
+ # no need to specify metric_for_best_model for resuming from checkpoints
50
+ no_cuda: False
51
+ fp16: True
52
+ warmup_ratio: 0.1
53
+ weight_decay: 0.01
configs/train_roberta_base_transfer.yaml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # SketchModelArguments
23
+ sketch_model_name: FacebookAI/roberta-base
24
+ sketch_architectures: RobertaForSequenceClassification
25
+
26
+ # IntensiveModelArguments
27
+ intensive_model_name: FacebookAI/roberta-base
28
+ intensive_model_mode: transfer
29
+ intensive_architectures: RobertaForQuestionAnsweringAVPool
30
+
31
+
32
+ TrainingArguments:
33
+ # report_to: wandb
34
+ run_name: squadv2-roberta-base-sketch,squadv2-roberta-base-intensive
35
+ output_dir: outputs
36
+ overwrite_output_dir: False
37
+ learning_rate: 2e-5
38
+ evaluation_strategy: epoch
39
+ save_strategy: steps # Save checkpoints every specified number of steps
40
+ # save_steps: 5000 # Save model checkpoints every 5000 steps
41
+ save_steps: 5000
42
+ save_total_limit: 2 # Maximum number of checkpoints to keep
43
+ # load_best_model_at_end: True # Disable to avoid loading the best model at the end
44
+ # no need to specify checkpoint_dir, it defaults to output_dir
45
+ # no need to specify logging_dir, it defaults to output_dir
46
+ per_device_train_batch_size: 512
47
+ per_device_eval_batch_size: 512
48
+ num_train_epochs: 10.0
49
+ # no need to specify metric_for_best_model for resuming from checkpoints
50
+ no_cuda: False
51
+ fp16: True
52
+ warmup_ratio: 0.1
53
+ weight_decay: 0.01
configs/train_roberta_large_finetune.yaml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # SketchModelArguments
23
+ sketch_model_name: FacebookAI/roberta-large
24
+ sketch_architectures: RobertaForSequenceClassification
25
+
26
+ # IntensiveModelArguments
27
+ intensive_model_name: FacebookAI/roberta-large
28
+ intensive_model_mode: finetune
29
+ intensive_architectures: RobertaForQuestionAnsweringAVPool
30
+
31
+
32
+ TrainingArguments:
33
+ # report_to: wandb
34
+ run_name: squadv2-roberta-base-sketch,squadv2-roberta-base-intensive
35
+ output_dir: outputs
36
+ overwrite_output_dir: False
37
+ learning_rate: 2e-5
38
+ evaluation_strategy: epoch
39
+ save_strategy: steps # Save checkpoints every specified number of steps
40
+ # save_steps: 5000 # Save model checkpoints every 5000 steps
41
+ save_steps: 5000
42
+ save_total_limit: 2 # Maximum number of checkpoints to keep
43
+ # load_best_model_at_end: True # Disable to avoid loading the best model at the end
44
+ # no need to specify checkpoint_dir, it defaults to output_dir
45
+ # no need to specify logging_dir, it defaults to output_dir
46
+ per_device_train_batch_size: 512
47
+ per_device_eval_batch_size: 512
48
+ num_train_epochs: 10.0
49
+ # no need to specify metric_for_best_model for resuming from checkpoints
50
+ no_cuda: False
51
+ fp16: True
52
+ warmup_ratio: 0.1
53
+ weight_decay: 0.01
configs/train_roberta_large_transfer.yaml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RetroDataModelArguments:
2
+
3
+ # DataArguments
4
+ max_seq_length: 512
5
+ max_answer_length: 30
6
+ doc_stride: 128
7
+ return_token_type_ids: True
8
+ pad_to_max_length: True
9
+ preprocessing_num_workers: 5
10
+ overwrite_cache: False
11
+ version_2_with_negative: True
12
+ null_score_diff_threshold: 0.0
13
+ rear_threshold: 0.0
14
+ n_best_size: 20
15
+ use_choice_logits: False
16
+ start_n_top: -1
17
+ end_n_top: -1
18
+ beta1: 1
19
+ beta2: 1
20
+ best_cof: 1
21
+
22
+ # SketchModelArguments
23
+ sketch_model_name: FacebookAI/roberta-large
24
+ sketch_architectures: RobertaForSequenceClassification
25
+
26
+ # IntensiveModelArguments
27
+ intensive_model_name: FacebookAI/roberta-large
28
+ intensive_model_mode: transfer
29
+ intensive_architectures: RobertaForQuestionAnsweringAVPool
30
+
31
+
32
+ TrainingArguments:
33
+ # report_to: wandb
34
+ run_name: squadv2-roberta-base-sketch,squadv2-roberta-base-intensive
35
+ output_dir: outputs
36
+ overwrite_output_dir: False
37
+ learning_rate: 2e-5
38
+ evaluation_strategy: epoch
39
+ save_strategy: steps # Save checkpoints every specified number of steps
40
+ # save_steps: 5000 # Save model checkpoints every 5000 steps
41
+ save_steps: 5000
42
+ save_total_limit: 2 # Maximum number of checkpoints to keep
43
+ # load_best_model_at_end: True # Disable to avoid loading the best model at the end
44
+ # no need to specify checkpoint_dir, it defaults to output_dir
45
+ # no need to specify logging_dir, it defaults to output_dir
46
+ per_device_train_batch_size: 512
47
+ per_device_eval_batch_size: 512
48
+ num_train_epochs: 10.0
49
+ # no need to specify metric_for_best_model for resuming from checkpoints
50
+ no_cuda: False
51
+ fp16: True
52
+ warmup_ratio: 0.1
53
+ weight_decay: 0.01
evaluate_squad_v2.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ["TF_ENABLE_ONEDNN_OPTS"] = '0'
3
+
4
+ from huggingface_hub import login
5
+
6
+
7
+ from typing import Union, Any, Dict
8
+ # from datasets.arrow_dataset import Batch
9
+
10
+ import argparse
11
+ import datasets
12
+ from transformers.utils import logging, check_min_version
13
+ from transformers.utils.versions import require_version
14
+
15
+ from retro_reader import RetroReader
16
+ from retro_reader.constants import EXAMPLE_FEATURES
17
+ import torch
18
+
19
+ # Will error if the minimal version of Transformers is not installed. Remove at your own risks.
20
+ check_min_version("4.13.0.dev0")
21
+
22
+ require_version("datasets>=1.8.0")
23
+
24
+ logger = logging.get_logger(__name__)
25
+
26
+
27
+ def schema_integrate(example) -> Union[Dict, Any]:
28
+ title = example["title"]
29
+ question = example["question"]
30
+ context = example["context"]
31
+ guid = example["id"]
32
+ classtype = [""] * len(title)
33
+ dataset_name = source = ["squad_v2"] * len(title)
34
+ answers, is_impossible = [], []
35
+ for answer_examples in example["answers"]:
36
+ if answer_examples["text"]:
37
+ answers.append(answer_examples)
38
+ is_impossible.append(False)
39
+ else:
40
+ answers.append({"text": [""], "answer_start": [-1]})
41
+ is_impossible.append(True)
42
+ # The feature names must be sorted.
43
+ return {
44
+ "guid": guid,
45
+ "question": question,
46
+ "context": context,
47
+ "answers": answers,
48
+ "title": title,
49
+ "classtype": classtype,
50
+ "source": source,
51
+ "is_impossible": is_impossible,
52
+ "dataset": dataset_name,
53
+ }
54
+
55
+
56
+ # data augmentation for multiple answers
57
+ def data_aug_for_multiple_answers(examples) -> Union[Dict, Any]:
58
+ result = {key: [] for key in examples.keys()}
59
+
60
+ def update(i, answers=None):
61
+ for key in result.keys():
62
+ if key == "answers" and answers is not None:
63
+ result[key].append(answers)
64
+ else:
65
+ result[key].append(examples[key][i])
66
+
67
+ for i, (answers, unanswerable) in enumerate(
68
+ zip(examples["answers"], examples["is_impossible"])
69
+ ):
70
+ answerable = not unanswerable
71
+ assert (
72
+ len(answers["text"]) == len(answers["answer_start"]) or
73
+ answers["answer_start"][0] == -1
74
+ )
75
+ if answerable and len(answers["text"]) > 1:
76
+ for n_ans in range(len(answers["text"])):
77
+ ans = {
78
+ "text": [answers["text"][n_ans]],
79
+ "answer_start": [answers["answer_start"][n_ans]],
80
+ }
81
+ update(i, ans)
82
+ elif not answerable:
83
+ update(i, {"text": [], "answer_start": []})
84
+ else:
85
+ update(i)
86
+
87
+ return result
88
+
89
+
90
+ def main(args):
91
+ # Load SQuAD V2.0 dataset
92
+ print("Loading SQuAD v2.0 dataset ...")
93
+ squad_v2 = datasets.load_dataset("squad_v2")
94
+
95
+ # TODO: Visualize a sample from the dataset
96
+
97
+ # Integrate into the schema used in this library
98
+ # Note: The columns used for preprocessing are `question`, `context`, `answers`
99
+ # and `is_impossible`. The remaining columns are columns that exist to
100
+ # process other types of data.
101
+
102
+ # Minize the dataset for debugging
103
+ if args.debug:
104
+ squad_v2["validation"] = squad_v2["validation"].select(range(5))
105
+
106
+ print("Integrating into the schema used in this library ...")
107
+ squad_v2 = squad_v2.map(
108
+ schema_integrate,
109
+ batched=True,
110
+ remove_columns=squad_v2.column_names["train"],
111
+ features=EXAMPLE_FEATURES,
112
+ )
113
+ # Load Retro Reader
114
+ # features: parse arguments
115
+ # make train/eval dataset from examples
116
+ # load model from 🤗 hub
117
+ # set sketch/intensive reader and rear verifier
118
+ print("Loading Retro Reader ...")
119
+ retro_reader = RetroReader.load(
120
+ config_file=args.configs,
121
+ device="cuda" if torch.cuda.is_available() else "cpu",
122
+ )
123
+
124
+ # Train
125
+ res = retro_reader.evaluate(squad_v2["validation"])
126
+ print(res)
127
+ logger.warning("Train retrospective reader Done.")
128
+
129
+
130
+ if __name__ == "__main__":
131
+ parser = argparse.ArgumentParser()
132
+ parser.add_argument("--configs", "-c", type=str, default="configs/inference_electra_base.yaml", help="config file path")
133
+ parser.add_argument("--batch_size", "-b", type=int, default=1024, help="batch size")
134
+ parser.add_argument("--debug", "-d", action="store_true", help="debug mode")
135
+ args = parser.parse_args()
136
+ main(args)
evaluation_scriptv2.0.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Official evaluation script for SQuAD version 2.0.
2
+
3
+ In addition to basic functionality, we also compute additional statistics and
4
+ plot precision-recall curves if an additional na_prob.json file is provided.
5
+ This file is expected to map question ID's to the model's predicted probability
6
+ that a question is unanswerable.
7
+ """
8
+ import argparse
9
+ import collections
10
+ import json
11
+ import numpy as np
12
+ import os
13
+ import re
14
+ import string
15
+ import sys
16
+
17
+ OPTS = None
18
+
19
+ def parse_args():
20
+ parser = argparse.ArgumentParser('Official evaluation script for SQuAD version 2.0.')
21
+ parser.add_argument('data_file', metavar='data.json', help='Input data JSON file.')
22
+ parser.add_argument('pred_file', metavar='pred.json', help='Model predictions.')
23
+ parser.add_argument('--out-file', '-o', metavar='eval.json',
24
+ help='Write accuracy metrics to file (default is stdout).')
25
+ parser.add_argument('--na-prob-file', '-n', metavar='na_prob.json',
26
+ help='Model estimates of probability of no answer.')
27
+ parser.add_argument('--na-prob-thresh', '-t', type=float, default=1.0,
28
+ help='Predict "" if no-answer probability exceeds this (default = 1.0).')
29
+ parser.add_argument('--out-image-dir', '-p', metavar='out_images', default=None,
30
+ help='Save precision-recall curves to directory.')
31
+ parser.add_argument('--verbose', '-v', action='store_true')
32
+ if len(sys.argv) == 1:
33
+ parser.print_help()
34
+ sys.exit(1)
35
+ return parser.parse_args()
36
+
37
+ def make_qid_to_has_ans(dataset):
38
+ qid_to_has_ans = {}
39
+ for article in dataset:
40
+ for p in article['paragraphs']:
41
+ for qa in p['qas']:
42
+ qid_to_has_ans[qa['id']] = bool(qa['answers'])
43
+ return qid_to_has_ans
44
+
45
+ def normalize_answer(s):
46
+ """Lower text and remove punctuation, articles and extra whitespace."""
47
+ def remove_articles(text):
48
+ regex = re.compile(r'\b(a|an|the)\b', re.UNICODE)
49
+ return re.sub(regex, ' ', text)
50
+ def white_space_fix(text):
51
+ return ' '.join(text.split())
52
+ def remove_punc(text):
53
+ exclude = set(string.punctuation)
54
+ return ''.join(ch for ch in text if ch not in exclude)
55
+ def lower(text):
56
+ return text.lower()
57
+ return white_space_fix(remove_articles(remove_punc(lower(s))))
58
+
59
+ def get_tokens(s):
60
+ if not s: return []
61
+ return normalize_answer(s).split()
62
+
63
+ def compute_exact(a_gold, a_pred):
64
+ return int(normalize_answer(a_gold) == normalize_answer(a_pred))
65
+
66
+ def compute_f1(a_gold, a_pred):
67
+ gold_toks = get_tokens(a_gold)
68
+ pred_toks = get_tokens(a_pred)
69
+ common = collections.Counter(gold_toks) & collections.Counter(pred_toks)
70
+ num_same = sum(common.values())
71
+ if len(gold_toks) == 0 or len(pred_toks) == 0:
72
+ # If either is no-answer, then F1 is 1 if they agree, 0 otherwise
73
+ return int(gold_toks == pred_toks)
74
+ if num_same == 0:
75
+ return 0
76
+ precision = 1.0 * num_same / len(pred_toks)
77
+ recall = 1.0 * num_same / len(gold_toks)
78
+ f1 = (2 * precision * recall) / (precision + recall)
79
+ return f1
80
+
81
+ def get_raw_scores(dataset, preds):
82
+ exact_scores = {}
83
+ f1_scores = {}
84
+ for article in dataset:
85
+ for p in article['paragraphs']:
86
+ for qa in p['qas']:
87
+ qid = qa['id']
88
+ gold_answers = [a['text'] for a in qa['answers']
89
+ if normalize_answer(a['text'])]
90
+ if not gold_answers:
91
+ # For unanswerable questions, only correct answer is empty string
92
+ gold_answers = ['']
93
+ if qid not in preds:
94
+ print('Missing prediction for %s' % qid)
95
+ continue
96
+ a_pred = preds[qid]
97
+ # Take max over all gold answers
98
+ exact_scores[qid] = max(compute_exact(a, a_pred) for a in gold_answers)
99
+ f1_scores[qid] = max(compute_f1(a, a_pred) for a in gold_answers)
100
+ return exact_scores, f1_scores
101
+
102
+ def apply_no_ans_threshold(scores, na_probs, qid_to_has_ans, na_prob_thresh):
103
+ new_scores = {}
104
+ for qid, s in scores.items():
105
+ pred_na = na_probs[qid] > na_prob_thresh
106
+ if pred_na:
107
+ new_scores[qid] = float(not qid_to_has_ans[qid])
108
+ else:
109
+ new_scores[qid] = s
110
+ return new_scores
111
+
112
+ def make_eval_dict(exact_scores, f1_scores, qid_list=None):
113
+ if not qid_list:
114
+ total = len(exact_scores)
115
+ return collections.OrderedDict([
116
+ ('exact', 100.0 * sum(exact_scores.values()) / total),
117
+ ('f1', 100.0 * sum(f1_scores.values()) / total),
118
+ ('total', total),
119
+ ])
120
+ else:
121
+ total = len(qid_list)
122
+ return collections.OrderedDict([
123
+ ('exact', 100.0 * sum(exact_scores[k] for k in qid_list) / total),
124
+ ('f1', 100.0 * sum(f1_scores[k] for k in qid_list) / total),
125
+ ('total', total),
126
+ ])
127
+
128
+ def merge_eval(main_eval, new_eval, prefix):
129
+ for k in new_eval:
130
+ main_eval['%s_%s' % (prefix, k)] = new_eval[k]
131
+
132
+ def plot_pr_curve(precisions, recalls, out_image, title):
133
+ plt.step(recalls, precisions, color='b', alpha=0.2, where='post')
134
+ plt.fill_between(recalls, precisions, step='post', alpha=0.2, color='b')
135
+ plt.xlabel('Recall')
136
+ plt.ylabel('Precision')
137
+ plt.xlim([0.0, 1.05])
138
+ plt.ylim([0.0, 1.05])
139
+ plt.title(title)
140
+ plt.savefig(out_image)
141
+ plt.clf()
142
+
143
+ def make_precision_recall_eval(scores, na_probs, num_true_pos, qid_to_has_ans,
144
+ out_image=None, title=None):
145
+ qid_list = sorted(na_probs, key=lambda k: na_probs[k])
146
+ true_pos = 0.0
147
+ cur_p = 1.0
148
+ cur_r = 0.0
149
+ precisions = [1.0]
150
+ recalls = [0.0]
151
+ avg_prec = 0.0
152
+ for i, qid in enumerate(qid_list):
153
+ if qid_to_has_ans[qid]:
154
+ true_pos += scores[qid]
155
+ cur_p = true_pos / float(i+1)
156
+ cur_r = true_pos / float(num_true_pos)
157
+ if i == len(qid_list) - 1 or na_probs[qid] != na_probs[qid_list[i+1]]:
158
+ # i.e., if we can put a threshold after this point
159
+ avg_prec += cur_p * (cur_r - recalls[-1])
160
+ precisions.append(cur_p)
161
+ recalls.append(cur_r)
162
+ if out_image:
163
+ plot_pr_curve(precisions, recalls, out_image, title)
164
+ return {'ap': 100.0 * avg_prec}
165
+
166
+ def run_precision_recall_analysis(main_eval, exact_raw, f1_raw, na_probs,
167
+ qid_to_has_ans, out_image_dir):
168
+ if out_image_dir and not os.path.exists(out_image_dir):
169
+ os.makedirs(out_image_dir)
170
+ num_true_pos = sum(1 for v in qid_to_has_ans.values() if v)
171
+ if num_true_pos == 0:
172
+ return
173
+ pr_exact = make_precision_recall_eval(
174
+ exact_raw, na_probs, num_true_pos, qid_to_has_ans,
175
+ out_image=os.path.join(out_image_dir, 'pr_exact.png'),
176
+ title='Precision-Recall curve for Exact Match score')
177
+ pr_f1 = make_precision_recall_eval(
178
+ f1_raw, na_probs, num_true_pos, qid_to_has_ans,
179
+ out_image=os.path.join(out_image_dir, 'pr_f1.png'),
180
+ title='Precision-Recall curve for F1 score')
181
+ oracle_scores = {k: float(v) for k, v in qid_to_has_ans.items()}
182
+ pr_oracle = make_precision_recall_eval(
183
+ oracle_scores, na_probs, num_true_pos, qid_to_has_ans,
184
+ out_image=os.path.join(out_image_dir, 'pr_oracle.png'),
185
+ title='Oracle Precision-Recall curve (binary task of HasAns vs. NoAns)')
186
+ merge_eval(main_eval, pr_exact, 'pr_exact')
187
+ merge_eval(main_eval, pr_f1, 'pr_f1')
188
+ merge_eval(main_eval, pr_oracle, 'pr_oracle')
189
+
190
+ def histogram_na_prob(na_probs, qid_list, image_dir, name):
191
+ if not qid_list:
192
+ return
193
+ x = [na_probs[k] for k in qid_list]
194
+ weights = np.ones_like(x) / float(len(x))
195
+ plt.hist(x, weights=weights, bins=20, range=(0.0, 1.0))
196
+ plt.xlabel('Model probability of no-answer')
197
+ plt.ylabel('Proportion of dataset')
198
+ plt.title('Histogram of no-answer probability: %s' % name)
199
+ plt.savefig(os.path.join(image_dir, 'na_prob_hist_%s.png' % name))
200
+ plt.clf()
201
+
202
+ def find_best_thresh(preds, scores, na_probs, qid_to_has_ans):
203
+ num_no_ans = sum(1 for k in qid_to_has_ans if not qid_to_has_ans[k])
204
+ cur_score = num_no_ans
205
+ best_score = cur_score
206
+ best_thresh = 0.0
207
+ qid_list = sorted(na_probs, key=lambda k: na_probs[k])
208
+ for i, qid in enumerate(qid_list):
209
+ if qid not in scores: continue
210
+ if qid_to_has_ans[qid]:
211
+ diff = scores[qid]
212
+ else:
213
+ if preds[qid]:
214
+ diff = -1
215
+ else:
216
+ diff = 0
217
+ cur_score += diff
218
+ if cur_score > best_score:
219
+ best_score = cur_score
220
+ best_thresh = na_probs[qid]
221
+ return 100.0 * best_score / len(scores), best_thresh
222
+
223
+ def find_all_best_thresh(main_eval, preds, exact_raw, f1_raw, na_probs, qid_to_has_ans):
224
+ best_exact, exact_thresh = find_best_thresh(preds, exact_raw, na_probs, qid_to_has_ans)
225
+ best_f1, f1_thresh = find_best_thresh(preds, f1_raw, na_probs, qid_to_has_ans)
226
+ main_eval['best_exact'] = best_exact
227
+ main_eval['best_exact_thresh'] = exact_thresh
228
+ main_eval['best_f1'] = best_f1
229
+ main_eval['best_f1_thresh'] = f1_thresh
230
+
231
+ def main():
232
+ with open(OPTS.data_file) as f:
233
+ dataset_json = json.load(f)
234
+ dataset = dataset_json['data']
235
+ with open(OPTS.pred_file) as f:
236
+ preds = json.load(f)
237
+ if OPTS.na_prob_file:
238
+ with open(OPTS.na_prob_file) as f:
239
+ na_probs = json.load(f)
240
+ else:
241
+ na_probs = {k: 0.0 for k in preds}
242
+ qid_to_has_ans = make_qid_to_has_ans(dataset) # maps qid to True/False
243
+ has_ans_qids = [k for k, v in qid_to_has_ans.items() if v]
244
+ no_ans_qids = [k for k, v in qid_to_has_ans.items() if not v]
245
+ exact_raw, f1_raw = get_raw_scores(dataset, preds)
246
+ exact_thresh = apply_no_ans_threshold(exact_raw, na_probs, qid_to_has_ans,
247
+ OPTS.na_prob_thresh)
248
+ f1_thresh = apply_no_ans_threshold(f1_raw, na_probs, qid_to_has_ans,
249
+ OPTS.na_prob_thresh)
250
+ out_eval = make_eval_dict(exact_thresh, f1_thresh)
251
+ if has_ans_qids:
252
+ has_ans_eval = make_eval_dict(exact_thresh, f1_thresh, qid_list=has_ans_qids)
253
+ merge_eval(out_eval, has_ans_eval, 'HasAns')
254
+ if no_ans_qids:
255
+ no_ans_eval = make_eval_dict(exact_thresh, f1_thresh, qid_list=no_ans_qids)
256
+ merge_eval(out_eval, no_ans_eval, 'NoAns')
257
+ if OPTS.na_prob_file:
258
+ find_all_best_thresh(out_eval, preds, exact_raw, f1_raw, na_probs, qid_to_has_ans)
259
+ if OPTS.na_prob_file and OPTS.out_image_dir:
260
+ run_precision_recall_analysis(out_eval, exact_raw, f1_raw, na_probs,
261
+ qid_to_has_ans, OPTS.out_image_dir)
262
+ histogram_na_prob(na_probs, has_ans_qids, OPTS.out_image_dir, 'hasAns')
263
+ histogram_na_prob(na_probs, no_ans_qids, OPTS.out_image_dir, 'noAns')
264
+ if OPTS.out_file:
265
+ with open(OPTS.out_file, 'w') as f:
266
+ json.dump(out_eval, f)
267
+ else:
268
+ print(json.dumps(out_eval, indent=2))
269
+
270
+ if __name__ == '__main__':
271
+ OPTS = parse_args()
272
+ if OPTS.out_image_dir:
273
+ import matplotlib
274
+ matplotlib.use('Agg')
275
+ import matplotlib.pyplot as plt
276
+ main()
google-calendar-simple-api/.github/CODE_OF_CONDUCT.md ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity
10
+ and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the
26
+ overall community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or
31
+ advances of any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email
35
+ address, without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official e-mail address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ kuzmovich.goog@gmail.com.
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series
86
+ of actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or
93
+ permanent ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within
113
+ the community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.0, available at
119
+ https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120
+
121
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct
122
+ enforcement ladder](https://github.com/mozilla/diversity).
123
+
124
+ [homepage]: https://www.contributor-covenant.org
125
+
126
+ For answers to common questions about this code of conduct, see the FAQ at
127
+ https://www.contributor-covenant.org/faq. Translations are available at
128
+ https://www.contributor-covenant.org/translations.
google-calendar-simple-api/.github/ISSUE_TEMPLATE/bug_report.md ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ [READ and REMOVE: Please create an issue only if you think that it's something that needs a fix or you have a
11
+ suggestion/request for improvement. If you have a question or issue not caused by gcsa itself, please use
12
+ the [discussions page](https://github.com/kuzmoyev/google-calendar-simple-api/discussions).]
13
+
14
+ ## Bug description
15
+
16
+ A clear and concise description of what the bug is.
17
+
18
+ ## To Reproduce
19
+
20
+ Steps to reproduce the behavior:
21
+
22
+ 1. Installed the latest version with `pip install gcsa`
23
+ 2. ...
24
+
25
+ Code used:
26
+
27
+ ```python
28
+ from gcsa.google_calendar import GoogleCalendar
29
+
30
+ ...
31
+ ```
32
+
33
+ ## Error or unexpected output
34
+
35
+ The whole traceback in case of an error:
36
+ ```
37
+ Traceback (most recent call last):
38
+ ...
39
+ ```
40
+
41
+ ## Expected behavior
42
+
43
+ A clear and concise description of what you expected to happen.
44
+
45
+ ## Screenshots
46
+
47
+ If applicable, add screenshots to help explain your problem.
48
+
49
+ ## Tech:
50
+
51
+ - OS: [e.g. Linux/Windows/MacOS]
52
+ - GCSA version: [e.g. 2.0.1]
53
+ - Python version: [e.g. 3.12]
54
+
55
+ ## Additional context
56
+
57
+ Add any other context about the problem here.
google-calendar-simple-api/.github/workflows/code-cov.yml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Code coverage
2
+
3
+ on: [pull_request]
4
+
5
+ jobs:
6
+ run:
7
+ # Don't run on PRs from forks
8
+ if: github.event.pull_request.head.repo.full_name == github.repository
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - name: Set up Python
14
+ uses: actions/setup-python@v2
15
+ with:
16
+ python-version: '3.12'
17
+
18
+ - name: Install dependencies
19
+ run: pip install tox
20
+
21
+ - name: Generate code coverage
22
+ run: tox -e coverage
23
+
24
+
25
+ - name: Post to GitHub
26
+ uses: 5monkeys/cobertura-action@master
27
+ with:
28
+ path: coverage.xml
29
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
30
+ minimum_coverage: 75
31
+ skip_covered: false
google-calendar-simple-api/.github/workflows/tests.yml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+
9
+ jobs:
10
+ run:
11
+
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11', '3.12' ]
16
+ include:
17
+ - python-version: '3.12'
18
+ note: with-style-and-docs-checks
19
+
20
+ steps:
21
+ - uses: actions/checkout@v2
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ uses: actions/setup-python@v2
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+
27
+ - name: Install tox
28
+ run: pip install tox tox-gh-actions
29
+
30
+ - name: Running tests
31
+ run: tox
google-calendar-simple-api/.gitignore ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Created by .ignore support plugin (hsz.mobi)
2
+ .idea/
3
+ credentials.json
4
+ token.pickle
5
+ venv/
6
+ __pycache__
7
+
8
+ build
9
+ dist
10
+ .eggs
11
+ gcsa.egg-info
12
+ docs/html
13
+
14
+ example.py
15
+ coverage.xml
16
+ .coverage
17
+ .DS_Store
18
+ .tox
google-calendar-simple-api/.readthedocs.yml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-22.04
5
+ tools:
6
+ python: "3.12"
7
+
8
+ sphinx:
9
+ configuration: docs/source/conf.py
10
+
11
+ python:
12
+ install:
13
+ - method: pip
14
+ path: .
15
+ extra_requirements:
16
+ - docs
google-calendar-simple-api/CONTRIBUTING.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to GCSA
2
+
3
+ Welcome and thank you for considering contributing to *Google Calendar Simple API* open source project!
4
+
5
+ Before contributing to this repository, please first discuss the change you wish to make via
6
+ [Issue](https://github.com/kuzmoyev/google-calendar-simple-api/issues),
7
+ [GitHub Discussion](https://github.com/kuzmoyev/google-calendar-simple-api/discussions), or [Discord](https://discord.gg/mRAegbwYKS). Don’t hesitate to ask!
8
+ Issue submissions, discussions, suggestions are as welcomed contributions as pull requests.
9
+
10
+ ## Steps to contribute changes
11
+
12
+ 1. [Fork](https://github.com/kuzmoyev/google-calendar-simple-api/fork) the repository
13
+ 2. Clone it with `git clone git@github.com:{your_username}/google-calendar-simple-api.git`
14
+ 3. Install dependencies if needed with `pip install -e .` (or `pip install -e ".[dev]"` if you want to run tests, compile documentation, etc.).
15
+ Use [virtualenv](https://virtualenv.pypa.io/en/latest/) to avoid polluting your global python
16
+ 4. Make and commit the changes. Add `closes #{issue_number}` to commit message if applies
17
+ 5. Run the tests with `tox` (these will be run on pull request):
18
+ * `tox` - all the tests
19
+ * `tox -e pytest` - unit tests
20
+ * `tox -e flake8` - style check
21
+ * `tox -e sphinx` - docs compilation test
22
+ 6. Push
23
+ 7. Create pull request
24
+ * towards `dev` branch if the changes require a new GCSA version (i.e. changes in [gcsa](https://github.com/kuzmoyev/google-calendar-simple-api/tree/master/gcsa) module)
25
+ * towards `master` branch if they don't (e.x. changes in README, docs, tests)
26
+
27
+ ## While contributing
28
+
29
+ * Follow the [Code of conduct](https://github.com/kuzmoyev/google-calendar-simple-api/blob/master/.github/CODE_OF_CONDUCT.md)
30
+ * Follow the [pep8](https://peps.python.org/pep-0008/) and the code style of the project (use your best judgement)
31
+ * Add documentation of your changes to code and/or to [read-the-docs](https://github.com/kuzmoyev/google-calendar-simple-api/tree/master/docs/source) if needed (use your best judgement)
32
+ * Add [tests](https://github.com/kuzmoyev/google-calendar-simple-api/tree/master/tests) if needed (use your best judgement)
google-calendar-simple-api/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2017
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
google-calendar-simple-api/README.rst ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Google Calendar Simple API
2
+ ==========================
3
+
4
+ .. image:: https://badge.fury.io/py/gcsa.svg
5
+ :target: https://badge.fury.io/py/gcsa
6
+ :alt: PyPi Package
7
+
8
+ .. image:: https://readthedocs.org/projects/google-calendar-simple-api/badge/?version=latest
9
+ :target: https://google-calendar-simple-api.readthedocs.io/en/latest/?badge=latest
10
+ :alt: Documentation Status
11
+
12
+ .. image:: https://github.com/kuzmoyev/Google-Calendar-Simple-API/workflows/Tests/badge.svg
13
+ :target: https://github.com/kuzmoyev/Google-Calendar-Simple-API/actions
14
+ :alt: Tests
15
+
16
+ .. image:: https://badgen.net/badge/icon/discord?icon=discord&label
17
+ :target: https://discord.gg/mRAegbwYKS
18
+ :alt: Discord
19
+
20
+
21
+ `Google Calendar Simple API` or `gcsa` is a library that simplifies event and calendar management in Google Calendars.
22
+ It is a Pythonic object oriented adapter for the official API. See the full `documentation`_.
23
+
24
+ Installation
25
+ ------------
26
+
27
+ .. code-block:: bash
28
+
29
+ pip install gcsa
30
+
31
+ See `Getting started page`_ for more details and installation options.
32
+
33
+ Example usage
34
+ -------------
35
+
36
+ List events
37
+ ~~~~~~~~~~~
38
+
39
+ .. code-block:: python
40
+
41
+ from gcsa.google_calendar import GoogleCalendar
42
+
43
+ calendar = GoogleCalendar('your_email@gmail.com')
44
+ for event in calendar:
45
+ print(event)
46
+
47
+
48
+ Create event
49
+ ~~~~~~~~~~~~
50
+
51
+ .. code-block:: python
52
+
53
+ from gcsa.event import Event
54
+
55
+ event = Event(
56
+ 'The Glass Menagerie',
57
+ start=datetime(2020, 7, 10, 19, 0),
58
+ location='Záhřebská 468/21',
59
+ minutes_before_popup_reminder=15
60
+ )
61
+ calendar.add_event(event)
62
+
63
+
64
+ Create recurring event
65
+ ~~~~~~~~~~~~~~~~~~~~~~
66
+
67
+ .. code-block:: python
68
+
69
+ from gcsa.recurrence import Recurrence, DAILY
70
+
71
+ event = Event(
72
+ 'Breakfast',
73
+ start=date(2020, 7, 16),
74
+ recurrence=Recurrence.rule(freq=DAILY)
75
+ )
76
+ calendar.add_event(event)
77
+
78
+
79
+ **Suggestion**: use beautiful_date_ to create `date` and `datetime` objects in your
80
+ projects (*because its beautiful... just like you*).
81
+
82
+
83
+ References
84
+ ----------
85
+
86
+ Template for `setup.py` was taken from `kennethreitz/setup.py`_
87
+
88
+
89
+ .. _documentation: https://google-calendar-simple-api.readthedocs.io/en/latest/?badge=latest
90
+ .. _`Getting started page`: https://google-calendar-simple-api.readthedocs.io/en/latest/getting_started.html
91
+ .. _beautiful_date: https://github.com/kuzmoyev/beautiful-date
92
+ .. _`kennethreitz/setup.py`: https://github.com/kennethreitz/setup.py
google-calendar-simple-api/build/lib/gcsa/__init__.py ADDED
File without changes
google-calendar-simple-api/build/lib/gcsa/_resource.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class Resource(ABC):
5
+ @property
6
+ @abstractmethod
7
+ def id(self):
8
+ pass
google-calendar-simple-api/build/lib/gcsa/_services/__init__.py ADDED
File without changes
google-calendar-simple-api/build/lib/gcsa/_services/acl_service.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Iterable, Union
2
+
3
+ from gcsa._services.base_service import BaseService
4
+ from gcsa.acl import AccessControlRule
5
+ from gcsa.serializers.acl_rule_serializer import ACLRuleSerializer
6
+
7
+
8
+ class ACLService(BaseService):
9
+ """Access Control List management methods of the `GoogleCalendar`"""
10
+
11
+ def get_acl_rules(
12
+ self,
13
+ calendar_id: str = None,
14
+ show_deleted: bool = False
15
+ ) -> Iterable[AccessControlRule]:
16
+ """Returns the rules in the access control list for the calendar.
17
+
18
+ :param calendar_id:
19
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
20
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
21
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
22
+ :param show_deleted:
23
+ Whether to include deleted ACLs in the result. Deleted ACLs are represented by role equal to "none".
24
+ Deleted ACLs will always be included if syncToken is provided. Optional. The default is False.
25
+
26
+ :return:
27
+ Iterable of `AccessControlRule` objects
28
+ """
29
+ calendar_id = calendar_id or self.default_calendar
30
+ yield from self._list_paginated(
31
+ self.service.acl().list,
32
+ serializer_cls=ACLRuleSerializer,
33
+ calendarId=calendar_id,
34
+ **{
35
+ 'showDeleted': show_deleted,
36
+ }
37
+ )
38
+
39
+ def get_acl_rule(
40
+ self,
41
+ rule_id: str,
42
+ calendar_id: str = None
43
+ ) -> AccessControlRule:
44
+ """Returns an access control rule
45
+
46
+ :param rule_id:
47
+ ACL rule identifier.
48
+ :param calendar_id:
49
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
50
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
51
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
52
+
53
+ :return:
54
+ The corresponding `AccessControlRule` object
55
+ """
56
+ calendar_id = calendar_id or self.default_calendar
57
+ acl_rule_resource = self.service.acl().get(
58
+ calendarId=calendar_id,
59
+ ruleId=rule_id
60
+ ).execute()
61
+ return ACLRuleSerializer.to_object(acl_rule_resource)
62
+
63
+ def add_acl_rule(
64
+ self,
65
+ acl_rule: AccessControlRule,
66
+ send_notifications: bool = True,
67
+ calendar_id: str = None
68
+ ):
69
+ """Adds access control rule
70
+
71
+ :param acl_rule:
72
+ AccessControlRule object.
73
+ :param send_notifications:
74
+ Whether to send notifications about the calendar sharing change. The default is True.
75
+ :param calendar_id:
76
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
77
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
78
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
79
+
80
+ :return:
81
+ Created access control rule with id.
82
+ """
83
+ calendar_id = calendar_id or self.default_calendar
84
+ body = ACLRuleSerializer.to_json(acl_rule)
85
+ acl_rule_json = self.service.acl().insert(
86
+ calendarId=calendar_id,
87
+ body=body,
88
+ sendNotifications=send_notifications
89
+ ).execute()
90
+ return ACLRuleSerializer.to_object(acl_rule_json)
91
+
92
+ def update_acl_rule(
93
+ self,
94
+ acl_rule: AccessControlRule,
95
+ send_notifications: bool = True,
96
+ calendar_id: str = None
97
+ ):
98
+ """Updates given access control rule
99
+
100
+ :param acl_rule:
101
+ AccessControlRule object.
102
+ :param send_notifications:
103
+ Whether to send notifications about the calendar sharing change. The default is True.
104
+ :param calendar_id:
105
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
106
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
107
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
108
+
109
+ :return:
110
+ Updated access control rule.
111
+ """
112
+ calendar_id = calendar_id or self.default_calendar
113
+ acl_id = self._get_resource_id(acl_rule)
114
+ body = ACLRuleSerializer.to_json(acl_rule)
115
+ acl_json = self.service.acl().update(
116
+ calendarId=calendar_id,
117
+ ruleId=acl_id,
118
+ body=body,
119
+ sendNotifications=send_notifications
120
+ ).execute()
121
+ return ACLRuleSerializer.to_object(acl_json)
122
+
123
+ def delete_acl_rule(
124
+ self,
125
+ acl_rule: Union[AccessControlRule, str],
126
+ calendar_id: str = None
127
+ ):
128
+ """Deletes access control rule.
129
+
130
+ :param acl_rule:
131
+ Access control rule's ID or `AccessControlRule` object with set `acl_id`.
132
+ :param calendar_id:
133
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
134
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
135
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
136
+ """
137
+ calendar_id = calendar_id or self.default_calendar
138
+ acl_id = self._get_resource_id(acl_rule)
139
+
140
+ self.service.acl().delete(
141
+ calendarId=calendar_id,
142
+ ruleId=acl_id
143
+ ).execute()
google-calendar-simple-api/build/lib/gcsa/_services/authentication.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ import os.path
3
+ import glob
4
+ from typing import List
5
+
6
+ from googleapiclient import discovery
7
+ from google_auth_oauthlib.flow import InstalledAppFlow
8
+ from google.auth.transport.requests import Request
9
+ from google.auth.credentials import Credentials
10
+
11
+
12
+ class AuthenticatedService:
13
+ """Handles authentication of the `GoogleCalendar`"""
14
+
15
+ _READ_WRITE_SCOPES = 'https://www.googleapis.com/auth/calendar'
16
+ _LIST_ORDERS = ("startTime", "updated")
17
+
18
+ def __init__(
19
+ self,
20
+ *,
21
+ credentials: Credentials = None,
22
+ credentials_path: str = None,
23
+ token_path: str = None,
24
+ save_token: bool = True,
25
+ read_only: bool = False,
26
+ authentication_flow_host: str = 'localhost',
27
+ authentication_flow_port: int = 8080,
28
+ authentication_flow_bind_addr: str = None
29
+ ):
30
+ """
31
+ Specify ``credentials`` to use in requests or ``credentials_path`` and ``token_path`` to get credentials from.
32
+
33
+ :param credentials:
34
+ Credentials with token and refresh token.
35
+ If specified, ``credentials_path``, ``token_path``, and ``save_token`` are ignored.
36
+ If not specified, credentials are retrieved from "token.pickle" file (specified in ``token_path`` or
37
+ default path) or with authentication flow using secret from "credentials.json" ("client_secret_*.json")
38
+ (specified in ``credentials_path`` or default path)
39
+ :param credentials_path:
40
+ Path to "credentials.json" ("client_secret_*.json") file.
41
+ Default: ~/.credentials/credentials.json or ~/.credentials/client_secret*.json
42
+ :param token_path:
43
+ Existing path to load the token from, or path to save the token after initial authentication flow.
44
+ Default: "token.pickle" in the same directory as the credentials_path
45
+ :param save_token:
46
+ Whether to pickle token after authentication flow for future uses
47
+ :param read_only:
48
+ If require read only access. Default: False
49
+ :param authentication_flow_host:
50
+ Host to receive response during authentication flow
51
+ :param authentication_flow_port:
52
+ Port to receive response during authentication flow
53
+ :param authentication_flow_bind_addr:
54
+ Optional IP address for the redirect server to listen on when it is not the same as host
55
+ (e.g. in a container)
56
+ """
57
+
58
+ if credentials:
59
+ self.credentials = self._ensure_refreshed(credentials)
60
+ else:
61
+ credentials_path = credentials_path or self._get_default_credentials_path()
62
+ credentials_dir, credentials_file = os.path.split(credentials_path)
63
+ token_path = token_path or os.path.join(credentials_dir, 'token.pickle')
64
+ scopes = [self._READ_WRITE_SCOPES + ('.readonly' if read_only else '')]
65
+
66
+ self.credentials = self._get_credentials(
67
+ token_path,
68
+ credentials_dir,
69
+ credentials_file,
70
+ scopes,
71
+ save_token,
72
+ authentication_flow_host,
73
+ authentication_flow_port,
74
+ authentication_flow_bind_addr
75
+ )
76
+
77
+ self.service = discovery.build('calendar', 'v3', credentials=self.credentials)
78
+
79
+ @staticmethod
80
+ def _ensure_refreshed(
81
+ credentials: Credentials
82
+ ) -> Credentials:
83
+ if not credentials.valid and credentials.expired:
84
+ credentials.refresh(Request())
85
+ return credentials
86
+
87
+ @staticmethod
88
+ def _get_credentials(
89
+ token_path: str,
90
+ credentials_dir: str,
91
+ credentials_file: str,
92
+ scopes: List[str],
93
+ save_token: bool,
94
+ host: str,
95
+ port: int,
96
+ bind_addr: str
97
+ ) -> Credentials:
98
+ credentials = None
99
+
100
+ if os.path.exists(token_path):
101
+ with open(token_path, 'rb') as token_file:
102
+ credentials = pickle.load(token_file)
103
+
104
+ if not credentials or not credentials.valid:
105
+ if credentials and credentials.expired and credentials.refresh_token:
106
+ credentials.refresh(Request())
107
+ else:
108
+ credentials_path = os.path.join(credentials_dir, credentials_file)
109
+ flow = InstalledAppFlow.from_client_secrets_file(credentials_path, scopes)
110
+ credentials = flow.run_local_server(host=host, port=port, bind_addr=bind_addr)
111
+
112
+ if save_token:
113
+ with open(token_path, 'wb') as token_file:
114
+ pickle.dump(credentials, token_file)
115
+
116
+ return credentials
117
+
118
+ @staticmethod
119
+ def _get_default_credentials_path() -> str:
120
+ """Checks if `.credentials` folder in home directory exists and contains `credentials.json` or
121
+ `client_secret*.json` file.
122
+
123
+ :raises ValueError: if `.credentials` folder does not exist, none of `credentials.json` or `client_secret*.json`
124
+ files do not exist, or there are multiple `client_secret*.json` files.
125
+ :return: expanded path to `credentials.json` or `client_secret*.json` file
126
+ """
127
+ home_dir = os.path.expanduser('~')
128
+ credential_dir = os.path.join(home_dir, '.credentials')
129
+ if not os.path.exists(credential_dir):
130
+ raise FileNotFoundError(f'Default credentials directory "{credential_dir}" does not exist.')
131
+ credential_path = os.path.join(credential_dir, 'credentials.json')
132
+ if os.path.exists(credential_path):
133
+ return credential_path
134
+ else:
135
+ credentials_files = glob.glob(credential_dir + '/client_secret*.json')
136
+ if len(credentials_files) > 1:
137
+ raise ValueError(f"Multiple credential files found in {credential_dir}.\n"
138
+ f"Try specifying the credentials file, e.x.:\n"
139
+ f"GoogleCalendar(credentials_path='{credentials_files[0]}')")
140
+ elif not credentials_files:
141
+ raise FileNotFoundError(f'Credentials file (credentials.json or client_secret*.json)'
142
+ f'not found in the default path: "{credential_dir}".')
143
+ else:
144
+ return credentials_files[0]
google-calendar-simple-api/build/lib/gcsa/_services/base_service.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Callable, Type, Union
2
+
3
+ from gcsa._resource import Resource
4
+ from gcsa._services.authentication import AuthenticatedService
5
+
6
+
7
+ class BaseService(AuthenticatedService):
8
+ def __init__(self, default_calendar, *args, **kwargs):
9
+ """
10
+ :param default_calendar:
11
+ Users email address or name/id of the calendar. Default: primary calendar of the user
12
+
13
+ If user's email or "primary" is specified, then primary calendar of the user is used.
14
+ You don't need to specify this parameter in this case as it is a default behaviour.
15
+
16
+ To use a different calendar you need to specify its id.
17
+ Go to calendar's `settings and sharing` -> `Integrate calendar` -> `Calendar ID`.
18
+ """
19
+ super().__init__(*args, **kwargs)
20
+ self.default_calendar = default_calendar
21
+
22
+ @staticmethod
23
+ def _list_paginated(
24
+ request_method: Callable,
25
+ serializer_cls: Type = None,
26
+ **kwargs
27
+ ):
28
+ page_token = None
29
+ while True:
30
+ response_json = request_method(
31
+ **kwargs,
32
+ pageToken=page_token
33
+ ).execute()
34
+ for item_json in response_json['items']:
35
+ if serializer_cls:
36
+ yield serializer_cls(item_json).get_object()
37
+ else:
38
+ yield item_json
39
+ page_token = response_json.get('nextPageToken')
40
+ if not page_token:
41
+ break
42
+
43
+ @staticmethod
44
+ def _get_resource_id(resource: Union[Resource, str]):
45
+ """If `resource` is `Resource` returns its id.
46
+ If `resource` is string, returns `resource` itself.
47
+
48
+ :raises:
49
+ ValueError: if `resource` is `Resource` object that doesn't have id
50
+ TypeError: if `resource` is neither `Resource` nor `str`
51
+ """
52
+ if isinstance(resource, Resource):
53
+ if resource.id is None:
54
+ raise ValueError("Resource has to have id to be updated, moved or deleted.")
55
+ return resource.id
56
+ elif isinstance(resource, str):
57
+ return resource
58
+ else:
59
+ raise TypeError('"resource" object must be Resource or str, not {!r}'.format(
60
+ resource.__class__.__name__
61
+ ))
google-calendar-simple-api/build/lib/gcsa/_services/calendar_lists_service.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Iterable, Union
2
+
3
+ from gcsa._services.base_service import BaseService
4
+ from gcsa.calendar import CalendarListEntry, Calendar
5
+ from gcsa.serializers.calendar_serializer import CalendarListEntrySerializer
6
+
7
+
8
+ class CalendarListService(BaseService):
9
+ """Calendar list management methods of the `GoogleCalendar`"""
10
+
11
+ def get_calendar_list(
12
+ self,
13
+ min_access_role: str = None,
14
+ show_deleted: bool = False,
15
+ show_hidden: bool = False
16
+ ) -> Iterable[CalendarListEntry]:
17
+ """Returns the calendars on the user's calendar list.
18
+
19
+ :param min_access_role:
20
+ The minimum access role for the user in the returned entries. See :py:class:`~gcsa.calendar.AccessRoles`
21
+ The default is no restriction.
22
+ :param show_deleted:
23
+ Whether to include deleted calendar list entries in the result. The default is False.
24
+ :param show_hidden:
25
+ Whether to show hidden entries. The default is False.
26
+
27
+ :return:
28
+ Iterable of :py:class:`~gcsa.calendar.CalendarListEntry` objects.
29
+ """
30
+ yield from self._list_paginated(
31
+ self.service.calendarList().list,
32
+ serializer_cls=CalendarListEntrySerializer,
33
+ minAccessRole=min_access_role,
34
+ showDeleted=show_deleted,
35
+ showHidden=show_hidden,
36
+ )
37
+
38
+ def get_calendar_list_entry(
39
+ self,
40
+ calendar_id: str = None
41
+ ) -> CalendarListEntry:
42
+ """Returns a calendar with the corresponding calendar_id from the user's calendar list.
43
+
44
+ :param calendar_id:
45
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`
46
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
47
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
48
+
49
+ :return:
50
+ The corresponding :py:class:`~gcsa.calendar.CalendarListEntry` object.
51
+ """
52
+ calendar_id = calendar_id or self.default_calendar
53
+ calendar_resource = self.service.calendarList().get(calendarId=calendar_id).execute()
54
+ return CalendarListEntrySerializer.to_object(calendar_resource)
55
+
56
+ def add_calendar_list_entry(
57
+ self,
58
+ calendar: CalendarListEntry,
59
+ color_rgb_format: bool = None
60
+ ) -> CalendarListEntry:
61
+ """Adds an existing calendar into the user's calendar list.
62
+
63
+ :param calendar:
64
+ :py:class:`~gcsa.calendar.CalendarListEntry` object.
65
+ :param color_rgb_format:
66
+ Whether to use the `foreground_color` and `background_color` fields to write the calendar colors (RGB).
67
+ If this feature is used, the index-based `color_id` field will be set to the best matching option
68
+ automatically. The default is True if `foreground_color` or `background_color` is set, False otherwise.
69
+
70
+ :return:
71
+ Created `CalendarListEntry` object with id.
72
+ """
73
+ if color_rgb_format is None:
74
+ color_rgb_format = (calendar.foreground_color is not None) or (calendar.background_color is not None)
75
+
76
+ body = CalendarListEntrySerializer.to_json(calendar)
77
+ calendar_json = self.service.calendarList().insert(
78
+ body=body,
79
+ colorRgbFormat=color_rgb_format
80
+ ).execute()
81
+ return CalendarListEntrySerializer.to_object(calendar_json)
82
+
83
+ def update_calendar_list_entry(
84
+ self,
85
+ calendar: CalendarListEntry,
86
+ color_rgb_format: bool = None
87
+ ) -> CalendarListEntry:
88
+ """Updates an existing calendar on the user's calendar list.
89
+
90
+ :param calendar:
91
+ :py:class:`~gcsa.calendar.Calendar` object with set `calendar_id`
92
+ :param color_rgb_format:
93
+ Whether to use the `foreground_color` and `background_color` fields to write the calendar colors (RGB).
94
+ If this feature is used, the index-based color_id field will be set to the best matching option
95
+ automatically. The default is True if `foreground_color` or `background_color` is set, False otherwise.
96
+
97
+ :return:
98
+ Updated calendar list entry object
99
+ """
100
+ calendar_id = self._get_resource_id(calendar)
101
+ if color_rgb_format is None:
102
+ color_rgb_format = calendar.foreground_color is not None or calendar.background_color is not None
103
+
104
+ body = CalendarListEntrySerializer.to_json(calendar)
105
+ calendar_json = self.service.calendarList().update(
106
+ calendarId=calendar_id,
107
+ body=body,
108
+ colorRgbFormat=color_rgb_format
109
+ ).execute()
110
+ return CalendarListEntrySerializer.to_object(calendar_json)
111
+
112
+ def delete_calendar_list_entry(
113
+ self,
114
+ calendar: Union[Calendar, CalendarListEntry, str]
115
+ ):
116
+ """Removes a calendar from the user's calendar list.
117
+
118
+ :param calendar:
119
+ Calendar's ID or :py:class:`~gcsa.calendar.Calendar`/:py:class:`~gcsa.calendar.CalendarListEntry` object
120
+ with the set `calendar_id`.
121
+ """
122
+ calendar_id = self._get_resource_id(calendar)
123
+ self.service.calendarList().delete(calendarId=calendar_id).execute()
google-calendar-simple-api/build/lib/gcsa/_services/calendars_service.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Union
2
+
3
+ from gcsa._services.base_service import BaseService
4
+ from gcsa.calendar import Calendar, CalendarListEntry
5
+ from gcsa.serializers.calendar_serializer import CalendarSerializer
6
+
7
+
8
+ class CalendarsService(BaseService):
9
+ """Calendars management methods of the `GoogleCalendar`"""
10
+
11
+ def get_calendar(
12
+ self,
13
+ calendar_id: str = None
14
+ ) -> Calendar:
15
+ """Returns the calendar with the corresponding calendar_id.
16
+
17
+ :param calendar_id:
18
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
19
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
20
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
21
+
22
+ :return:
23
+ The corresponding :py:class:`~gcsa.calendar.Calendar` object.
24
+ """
25
+ calendar_id = calendar_id or self.default_calendar
26
+ calendar_resource = self.service.calendars().get(
27
+ calendarId=calendar_id
28
+ ).execute()
29
+ return CalendarSerializer.to_object(calendar_resource)
30
+
31
+ def add_calendar(
32
+ self,
33
+ calendar: Calendar
34
+ ):
35
+ """Creates a secondary calendar.
36
+
37
+ :param calendar:
38
+ Calendar object.
39
+ :return:
40
+ Created calendar object with ID.
41
+ """
42
+ body = CalendarSerializer.to_json(calendar)
43
+ calendar_json = self.service.calendars().insert(
44
+ body=body
45
+ ).execute()
46
+ return CalendarSerializer.to_object(calendar_json)
47
+
48
+ def update_calendar(
49
+ self,
50
+ calendar: Calendar
51
+ ):
52
+ """Updates metadata for a calendar.
53
+
54
+ :param calendar:
55
+ Calendar object with set `calendar_id`
56
+
57
+ :return:
58
+ Updated calendar object
59
+ """
60
+ calendar_id = self._get_resource_id(calendar)
61
+ body = CalendarSerializer.to_json(calendar)
62
+ calendar_json = self.service.calendars().update(
63
+ calendarId=calendar_id,
64
+ body=body
65
+ ).execute()
66
+ return CalendarSerializer.to_object(calendar_json)
67
+
68
+ def delete_calendar(
69
+ self,
70
+ calendar: Union[Calendar, CalendarListEntry, str]
71
+ ):
72
+ """Deletes a secondary calendar.
73
+
74
+ Use :py:meth:`~gcsa.google_calendar.GoogleCalendar.clear_calendar` for clearing all events on primary calendars.
75
+
76
+ :param calendar:
77
+ Calendar's ID or :py:class:`~gcsa.calendar.Calendar` object with set `calendar_id`.
78
+ """
79
+ calendar_id = self._get_resource_id(calendar)
80
+ self.service.calendars().delete(calendarId=calendar_id).execute()
81
+
82
+ def clear_calendar(self):
83
+ """Clears a **primary** calendar.
84
+ This operation deletes all events associated with the **primary** calendar of an account.
85
+
86
+ Currently, there is no way to clear a secondary calendar.
87
+ You can use :py:meth:`~gcsa.google_calendar.GoogleCalendar.delete_event` method with the secondary calendar's ID
88
+ to delete events from a secondary calendar.
89
+ """
90
+ self.service.calendars().clear(calendarId='primary').execute()
91
+
92
+ def clear(self):
93
+ """Kept for back-compatibility. Use :py:meth:`~gcsa.google_calendar.GoogleCalendar.clear_calendar` instead.
94
+
95
+ Clears a **primary** calendar.
96
+ This operation deletes all events associated with the **primary** calendar of an account.
97
+
98
+ Currently, there is no way to clear a secondary calendar.
99
+ You can use :py:meth:`~gcsa.google_calendar.GoogleCalendar.delete_event` method with the secondary calendar's ID
100
+ to delete events from a secondary calendar.
101
+ """
102
+ self.clear_calendar()
google-calendar-simple-api/build/lib/gcsa/_services/colors_service.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gcsa._services.base_service import BaseService
2
+
3
+
4
+ class ColorsService(BaseService):
5
+ """Colors management methods of the `GoogleCalendar`"""
6
+
7
+ def list_event_colors(self) -> dict:
8
+ """A global palette of event colors, mapping from the color ID to its definition.
9
+ An :py:class:`~gcsa.event.Event` may refer to one of these color IDs in its color_id field."""
10
+ return self.service.colors().get().execute()['event']
11
+
12
+ def list_calendar_colors(self) -> dict:
13
+ """A global palette of calendar colors, mapping from the color ID to its definition.
14
+ :py:class:`~gcsa.calendar.CalendarListEntry` resource refers to one of these color IDs in its color_id field."""
15
+ return self.service.colors().get().execute()['calendar']
google-calendar-simple-api/build/lib/gcsa/_services/events_service.py ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import date, datetime
2
+ from typing import Union, Iterator, Iterable, Callable
3
+
4
+ from beautiful_date import BeautifulDate
5
+ from dateutil.relativedelta import relativedelta
6
+ from tzlocal import get_localzone_name
7
+
8
+ from gcsa._services.base_service import BaseService
9
+ from gcsa.event import Event
10
+ from gcsa.serializers.event_serializer import EventSerializer
11
+ from gcsa.util.date_time_util import to_localized_iso
12
+
13
+
14
+ class SendUpdatesMode:
15
+ """Possible values of the mode for sending updates or invitations to attendees.
16
+
17
+ * ALL - Send updates to all participants. This is the default value.
18
+ * EXTERNAL_ONLY - Send updates only to attendees not using google calendar.
19
+ * NONE - Do not send updates.
20
+ """
21
+
22
+ ALL = "all"
23
+ EXTERNAL_ONLY = "externalOnly"
24
+ NONE = "none"
25
+
26
+
27
+ class EventsService(BaseService):
28
+ """Event management methods of the `GoogleCalendar`"""
29
+
30
+ def _list_events(
31
+ self,
32
+ request_method: Callable,
33
+ time_min: Union[date, datetime, BeautifulDate],
34
+ time_max: Union[date, datetime, BeautifulDate],
35
+ timezone: str,
36
+ calendar_id: str,
37
+ **kwargs
38
+ ) -> Iterable[Event]:
39
+ """Lists paginated events received from request_method."""
40
+
41
+ time_min = time_min or datetime.now()
42
+ time_max = time_max or time_min + relativedelta(years=1)
43
+
44
+ time_min = to_localized_iso(time_min, timezone)
45
+ time_max = to_localized_iso(time_max, timezone)
46
+
47
+ yield from self._list_paginated(
48
+ request_method,
49
+ serializer_cls=EventSerializer,
50
+ calendarId=calendar_id,
51
+ timeMin=time_min,
52
+ timeMax=time_max,
53
+ **kwargs
54
+ )
55
+
56
+ def get_events(
57
+ self,
58
+ time_min: Union[date, datetime, BeautifulDate] = None,
59
+ time_max: Union[date, datetime, BeautifulDate] = None,
60
+ order_by: str = None,
61
+ timezone: str = get_localzone_name(),
62
+ single_events: bool = False,
63
+ query: str = None,
64
+ calendar_id: str = None,
65
+ **kwargs
66
+ ) -> Iterable[Event]:
67
+ """Lists events.
68
+
69
+ :param time_min:
70
+ Staring date/datetime
71
+ :param time_max:
72
+ Ending date/datetime
73
+ :param order_by:
74
+ Order of the events. Possible values: "startTime", "updated". Default is unspecified stable order.
75
+ :param timezone:
76
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
77
+ the computers local timezone is used if it is configured. UTC is used otherwise.
78
+ :param single_events:
79
+ Whether to expand recurring events into instances and only return single one-off events and
80
+ instances of recurring events, but not the underlying recurring events themselves.
81
+ :param query:
82
+ Free text search terms to find events that match these terms in any field, except for
83
+ extended properties.
84
+ :param calendar_id:
85
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
86
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
87
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
88
+ :param kwargs:
89
+ Additional API parameters.
90
+ See https://developers.google.com/calendar/v3/reference/events/list#optional-parameters
91
+
92
+ :return:
93
+ Iterable of `Event` objects
94
+ """
95
+ calendar_id = calendar_id or self.default_calendar
96
+ if not single_events and order_by == 'startTime':
97
+ raise ValueError(
98
+ '"startTime" ordering is only available when querying single events, i.e. single_events=True'
99
+ )
100
+ yield from self._list_events(
101
+ self.service.events().list,
102
+ time_min=time_min,
103
+ time_max=time_max,
104
+ timezone=timezone,
105
+ calendar_id=calendar_id,
106
+ **{
107
+ 'singleEvents': single_events,
108
+ 'orderBy': order_by,
109
+ 'q': query,
110
+ **kwargs
111
+ }
112
+ )
113
+
114
+ def get_instances(
115
+ self,
116
+ recurring_event: Union[Event, str],
117
+ time_min: Union[date, datetime, BeautifulDate] = None,
118
+ time_max: Union[date, datetime, BeautifulDate] = None,
119
+ timezone: str = get_localzone_name(),
120
+ calendar_id: str = None,
121
+ **kwargs
122
+ ) -> Iterable[Event]:
123
+ """Lists instances of recurring event
124
+
125
+ :param recurring_event:
126
+ Recurring event or instance of recurring event (`Event` object) or id of the recurring event
127
+ :param time_min:
128
+ Staring date/datetime
129
+ :param time_max:
130
+ Ending date/datetime
131
+ :param timezone:
132
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
133
+ the computers local timezone is used if it is configured. UTC is used otherwise.
134
+ :param calendar_id:
135
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
136
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
137
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
138
+ :param kwargs:
139
+ Additional API parameters.
140
+ See https://developers.google.com/calendar/v3/reference/events/instances#optional-parameters
141
+
142
+ :return:
143
+ Iterable of event objects
144
+ """
145
+ calendar_id = calendar_id or self.default_calendar
146
+ try:
147
+ event_id = self._get_resource_id(recurring_event)
148
+ except ValueError:
149
+ raise ValueError("Recurring event has to have id to retrieve its instances.")
150
+
151
+ yield from self._list_events(
152
+ self.service.events().instances,
153
+ time_min=time_min,
154
+ time_max=time_max,
155
+ timezone=timezone,
156
+ calendar_id=calendar_id,
157
+ **{
158
+ 'eventId': event_id,
159
+ **kwargs
160
+ }
161
+ )
162
+
163
+ def __iter__(self) -> Iterator[Event]:
164
+ return iter(self.get_events())
165
+
166
+ def __getitem__(self, r):
167
+ if isinstance(r, slice):
168
+ time_min, time_max, order_by = r.start or None, r.stop or None, r.step or None
169
+ elif isinstance(r, (date, datetime)):
170
+ time_min, time_max, order_by = r, None, None
171
+ else:
172
+ raise NotImplementedError
173
+
174
+ if (
175
+ (time_min and not isinstance(time_min, (date, datetime)))
176
+ or (time_max and not isinstance(time_max, (date, datetime)))
177
+ or (order_by and (not isinstance(order_by, str) or order_by not in self._LIST_ORDERS))
178
+ ):
179
+ raise ValueError('Calendar indexing is in the following format: time_min[:time_max[:order_by]],'
180
+ ' where time_min and time_max are date/datetime objects'
181
+ ' and order_by is None or one of "startTime" or "updated" strings.')
182
+
183
+ return self.get_events(time_min, time_max, order_by=order_by, single_events=(order_by == "startTime"))
184
+
185
+ def get_event(
186
+ self,
187
+ event_id: str,
188
+ calendar_id: str = None,
189
+ **kwargs
190
+ ) -> Event:
191
+ """Returns the event with the corresponding event_id.
192
+
193
+ :param event_id:
194
+ The unique event ID.
195
+ :param kwargs:
196
+ Additional API parameters.
197
+ See https://developers.google.com/calendar/v3/reference/events/get#optional-parameters
198
+ :param calendar_id:
199
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
200
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
201
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
202
+
203
+ :return:
204
+ The corresponding event object.
205
+ """
206
+ calendar_id = calendar_id or self.default_calendar
207
+ event_resource = self.service.events().get(
208
+ calendarId=calendar_id,
209
+ eventId=event_id,
210
+ **kwargs
211
+ ).execute()
212
+ return EventSerializer.to_object(event_resource)
213
+
214
+ def add_event(
215
+ self,
216
+ event: Event,
217
+ send_updates: str = SendUpdatesMode.NONE,
218
+ calendar_id: str = None,
219
+ **kwargs
220
+ ) -> Event:
221
+ """Creates event in the calendar
222
+
223
+ :param event:
224
+ Event object.
225
+ :param send_updates:
226
+ Whether and how to send updates to attendees. See :py:class:`~gcsa.google_calendar.SendUpdatesMode`
227
+ Default is "NONE".
228
+ :param calendar_id:
229
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
230
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
231
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
232
+ :param kwargs:
233
+ Additional API parameters.
234
+ See https://developers.google.com/calendar/v3/reference/events/insert#optional-parameters
235
+
236
+ :return:
237
+ Created event object with id.
238
+ """
239
+ calendar_id = calendar_id or self.default_calendar
240
+ body = EventSerializer.to_json(event)
241
+ event_json = self.service.events().insert(
242
+ calendarId=calendar_id,
243
+ body=body,
244
+ conferenceDataVersion=1,
245
+ sendUpdates=send_updates,
246
+ **kwargs
247
+ ).execute()
248
+ return EventSerializer.to_object(event_json)
249
+
250
+ def add_quick_event(
251
+ self,
252
+ event_string: str,
253
+ send_updates: str = SendUpdatesMode.NONE,
254
+ calendar_id: str = None,
255
+ **kwargs
256
+ ) -> Event:
257
+ """Creates event in the calendar by string description.
258
+
259
+ Example:
260
+ Appointment at Somewhere on June 3rd 10am-10:25am
261
+
262
+ :param event_string:
263
+ String that describes an event
264
+ :param send_updates:
265
+ Whether and how to send updates to attendees. See :py:class:`~gcsa.google_calendar.SendUpdatesMode`
266
+ Default is "NONE".
267
+ :param calendar_id:
268
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
269
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
270
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
271
+ :param kwargs:
272
+ Additional API parameters.
273
+ See https://developers.google.com/calendar/v3/reference/events/quickAdd#optional-parameters
274
+
275
+ :return:
276
+ Created event object with id.
277
+ """
278
+ calendar_id = calendar_id or self.default_calendar
279
+ event_json = self.service.events().quickAdd(
280
+ calendarId=calendar_id,
281
+ text=event_string,
282
+ sendUpdates=send_updates,
283
+ **kwargs
284
+ ).execute()
285
+ return EventSerializer.to_object(event_json)
286
+
287
+ def update_event(
288
+ self,
289
+ event: Event,
290
+ send_updates: str = SendUpdatesMode.NONE,
291
+ calendar_id: str = None,
292
+ **kwargs
293
+ ) -> Event:
294
+ """Updates existing event in the calendar
295
+
296
+ :param event:
297
+ Event object with set `event_id`.
298
+ :param send_updates:
299
+ Whether and how to send updates to attendees. See :py:class:`~gcsa.google_calendar.SendUpdatesMode`
300
+ Default is "NONE".
301
+ :param calendar_id:
302
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
303
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
304
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
305
+ :param kwargs:
306
+ Additional API parameters.
307
+ See https://developers.google.com/calendar/v3/reference/events/update#optional-parameters
308
+
309
+ :return:
310
+ Updated event object.
311
+ """
312
+ calendar_id = calendar_id or self.default_calendar
313
+ event_id = self._get_resource_id(event)
314
+ body = EventSerializer.to_json(event)
315
+ event_json = self.service.events().update(
316
+ calendarId=calendar_id,
317
+ eventId=event_id,
318
+ body=body,
319
+ conferenceDataVersion=1,
320
+ sendUpdates=send_updates,
321
+ **kwargs
322
+ ).execute()
323
+ return EventSerializer.to_object(event_json)
324
+
325
+ def import_event(
326
+ self,
327
+ event: Event,
328
+ calendar_id: str = None,
329
+ **kwargs
330
+ ) -> Event:
331
+ """Imports an event in the calendar
332
+
333
+ This operation is used to add a private copy of an existing event to a calendar.
334
+
335
+ :param event:
336
+ Event object.
337
+ :param calendar_id:
338
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
339
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
340
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
341
+ :param kwargs:
342
+ Additional API parameters.
343
+ See https://developers.google.com/calendar/v3/reference/events/import#optional-parameters
344
+
345
+ :return:
346
+ Created event object with id.
347
+ """
348
+ calendar_id = calendar_id or self.default_calendar
349
+ body = EventSerializer.to_json(event)
350
+ event_json = self.service.events().import_(
351
+ calendarId=calendar_id,
352
+ body=body,
353
+ conferenceDataVersion=1,
354
+ **kwargs
355
+ ).execute()
356
+ return EventSerializer.to_object(event_json)
357
+
358
+ def move_event(
359
+ self,
360
+ event: Event,
361
+ destination_calendar_id: str,
362
+ send_updates: str = SendUpdatesMode.NONE,
363
+ source_calendar_id: str = None,
364
+ **kwargs
365
+ ) -> Event:
366
+ """Moves existing event from calendar to another calendar
367
+
368
+ :param event:
369
+ Event object with set event_id.
370
+ :param destination_calendar_id:
371
+ ID of the destination calendar.
372
+ :param send_updates:
373
+ Whether and how to send updates to attendees. See :py:class:`~gcsa.google_calendar.SendUpdatesMode`
374
+ Default is "NONE".
375
+ :param source_calendar_id:
376
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
377
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
378
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
379
+ :param kwargs:
380
+ Additional API parameters.
381
+ See https://developers.google.com/calendar/v3/reference/events/move#optional-parameters
382
+
383
+ :return:
384
+ Moved event object.
385
+ """
386
+ source_calendar_id = source_calendar_id or self.default_calendar
387
+ event_id = self._get_resource_id(event)
388
+ moved_event_json = self.service.events().move(
389
+ calendarId=source_calendar_id,
390
+ eventId=event_id,
391
+ destination=destination_calendar_id,
392
+ sendUpdates=send_updates,
393
+ **kwargs
394
+ ).execute()
395
+ return EventSerializer.to_object(moved_event_json)
396
+
397
+ def delete_event(
398
+ self,
399
+ event: Union[Event, str],
400
+ send_updates: str = SendUpdatesMode.NONE,
401
+ calendar_id: str = None,
402
+ **kwargs
403
+ ):
404
+ """Deletes an event.
405
+
406
+ :param event:
407
+ Event's ID or `Event` object with set `event_id`.
408
+ :param send_updates:
409
+ Whether and how to send updates to attendees. See :py:class:`~gcsa.google_calendar.SendUpdatesMode`
410
+ Default is "NONE".
411
+ :param calendar_id:
412
+ Calendar identifier. Default is `default_calendar` specified in `GoogleCalendar`.
413
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
414
+ If you want to access the primary calendar of the currently logged-in user, use the "primary" keyword.
415
+ :param kwargs:
416
+ Additional API parameters.
417
+ See https://developers.google.com/calendar/v3/reference/events/delete#optional-parameters
418
+ """
419
+ calendar_id = calendar_id or self.default_calendar
420
+ event_id = self._get_resource_id(event)
421
+
422
+ self.service.events().delete(
423
+ calendarId=calendar_id,
424
+ eventId=event_id,
425
+ sendUpdates=send_updates,
426
+ **kwargs
427
+ ).execute()
google-calendar-simple-api/build/lib/gcsa/_services/free_busy_service.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import date, datetime
2
+ from typing import Union, List
3
+
4
+ from beautiful_date import BeautifulDate
5
+ from dateutil.relativedelta import relativedelta
6
+ from tzlocal import get_localzone_name
7
+
8
+ from gcsa._services.base_service import BaseService
9
+ from gcsa.free_busy import FreeBusy, FreeBusyQueryError
10
+ from gcsa.serializers.free_busy_serializer import FreeBusySerializer
11
+ from gcsa.util.date_time_util import to_localized_iso
12
+
13
+
14
+ class FreeBusyService(BaseService):
15
+ def get_free_busy(
16
+ self,
17
+ resource_ids: Union[str, List[str]] = None,
18
+ *,
19
+ time_min: Union[date, datetime, BeautifulDate] = None,
20
+ time_max: Union[date, datetime, BeautifulDate] = None,
21
+ timezone: str = get_localzone_name(),
22
+ group_expansion_max: int = None,
23
+ calendar_expansion_max: int = None,
24
+ ignore_errors: bool = False
25
+ ) -> FreeBusy:
26
+ """Returns free/busy information for a set of calendars and/or groups.
27
+
28
+ :param resource_ids:
29
+ Identifier or list of identifiers of calendar(s) and/or group(s).
30
+ Default is `default_calendar` specified in `GoogleCalendar`.
31
+ :param time_min:
32
+ The start of the interval for the query.
33
+ :param time_max:
34
+ The end of the interval for the query.
35
+ :param timezone:
36
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
37
+ the computers local timezone is used if it is configured. UTC is used otherwise.
38
+ :param group_expansion_max:
39
+ Maximal number of calendar identifiers to be provided for a single group.
40
+ An error is returned for a group with more members than this value.
41
+ Maximum value is 100.
42
+ :param calendar_expansion_max:
43
+ Maximal number of calendars for which FreeBusy information is to be provided.
44
+ Maximum value is 50.
45
+ :param ignore_errors:
46
+ Whether errors related to calendars and/or groups should be ignored.
47
+ If `False` :py:class:`~gcsa.free_busy.FreeBusyQueryError` is raised in case of query related errors.
48
+ If `True`, related errors are stored in the resulting :py:class:`~gcsa.free_busy.FreeBusy` object.
49
+ Default is `False`.
50
+ Note, request related errors (e.x. authentication error) will not be ignored regardless of
51
+ the `ignore_errors` value.
52
+
53
+ :return:
54
+ :py:class:`~gcsa.free_busy.FreeBusy` object.
55
+ """
56
+
57
+ time_min = time_min or datetime.now()
58
+ time_max = time_max or time_min + relativedelta(weeks=2)
59
+
60
+ time_min = to_localized_iso(time_min, timezone)
61
+ time_max = to_localized_iso(time_max, timezone)
62
+
63
+ if resource_ids is None:
64
+ resource_ids = [self.default_calendar]
65
+ elif not isinstance(resource_ids, (list, tuple, set)):
66
+ resource_ids = [resource_ids]
67
+
68
+ body = {
69
+ "timeMin": time_min,
70
+ "timeMax": time_max,
71
+ "timeZone": timezone,
72
+ "groupExpansionMax": group_expansion_max,
73
+ "calendarExpansionMax": calendar_expansion_max,
74
+ "items": [
75
+ {
76
+ "id": r_id
77
+ } for r_id in resource_ids
78
+ ]
79
+ }
80
+
81
+ free_busy_json = self.service.freebusy().query(body=body).execute()
82
+ free_busy = FreeBusySerializer.to_object(free_busy_json)
83
+ if not ignore_errors and (free_busy.groups_errors or free_busy.calendars_errors):
84
+ raise FreeBusyQueryError(groups_errors=free_busy.groups_errors,
85
+ calendars_errors=free_busy.calendars_errors)
86
+
87
+ return free_busy
google-calendar-simple-api/build/lib/gcsa/_services/settings_service.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gcsa._services.base_service import BaseService
2
+ from gcsa.serializers.settings_serializer import SettingsSerializer
3
+ from gcsa.settings import Settings
4
+
5
+
6
+ class SettingsService(BaseService):
7
+ """Settings management methods of the `GoogleCalendar`"""
8
+
9
+ def get_settings(self) -> Settings:
10
+ """Returns user settings for the authenticated user."""
11
+ settings_list = list(self._list_paginated(self.service.settings().list))
12
+ settings_json = {s['id']: s['value'] for s in settings_list}
13
+ return SettingsSerializer.to_object(settings_json)
google-calendar-simple-api/build/lib/gcsa/acl.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gcsa._resource import Resource
2
+
3
+
4
+ class ACLRole:
5
+ """
6
+ * `NONE` - Provides no access.
7
+ * `FREE_BUSY_READER` - Provides read access to free/busy information.
8
+ * `READER` - Provides read access to the calendar. Private events will appear to users with reader access, but event
9
+ details will be hidden.
10
+ * `WRITER` - Provides read and write access to the calendar. Private events will appear to users with writer access,
11
+ and event details will be visible.
12
+ * `OWNER` - Provides ownership of the calendar. This role has all of the permissions of the writer role with
13
+ the additional ability to see and manipulate ACLs.
14
+ """
15
+
16
+ NONE = "none"
17
+ FREE_BUSY_READER = "freeBusyReader"
18
+ READER = "reader"
19
+ WRITER = "writer"
20
+ OWNER = "owner"
21
+
22
+
23
+ class ACLScopeType:
24
+ """
25
+ * `DEFAULT` - The public scope.
26
+ * `USER` - Limits the scope to a single user.
27
+ * `GROUP` - Limits the scope to a group.
28
+ * `DOMAIN` - Limits the scope to a domain.
29
+ """
30
+
31
+ DEFAULT = "default"
32
+ USER = "user"
33
+ GROUP = "group"
34
+ DOMAIN = "domain"
35
+
36
+
37
+ class AccessControlRule(Resource):
38
+ def __init__(
39
+ self,
40
+ *,
41
+ role: str,
42
+ scope_type: str,
43
+ acl_id: str = None,
44
+ scope_value: str = None
45
+ ):
46
+ """
47
+ :param role:
48
+ The role assigned to the scope. See :py:class:`~gcsa.acl.ACLRole`.
49
+ :param scope_type:
50
+ The type of the scope. See :py:class:`~gcsa.acl.ACLScopeType`.
51
+ :param acl_id:
52
+ Identifier of the Access Control List (ACL) rule.
53
+ :param scope_value:
54
+ The email address of a user or group, or the name of a domain, depending on the scope type.
55
+ Omitted for type "default".
56
+ """
57
+ self.acl_id = acl_id
58
+ self.role = role
59
+ self.scope_type = scope_type
60
+ self.scope_value = scope_value
61
+
62
+ @property
63
+ def id(self):
64
+ return self.acl_id
65
+
66
+ def __str__(self):
67
+ return '{} - {}'.format(self.scope_value, self.role)
68
+
69
+ def __repr__(self):
70
+ return '<AccessControlRule {}>'.format(self.__str__())
google-calendar-simple-api/build/lib/gcsa/attachment.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Attachment:
2
+ _SUPPORTED_MIME_TYPES = {
3
+ "application/vnd.google-apps.audio",
4
+ "application/vnd.google-apps.document", # Google Docs
5
+ "application/vnd.google-apps.drawing", # Google Drawing
6
+ "application/vnd.google-apps.file", # Google Drive file
7
+ "application/vnd.google-apps.folder", # Google Drive folder
8
+ "application/vnd.google-apps.form", # Google Forms
9
+ "application/vnd.google-apps.fusiontable", # Google Fusion Tables
10
+ "application/vnd.google-apps.map", # Google My Maps
11
+ "application/vnd.google-apps.photo",
12
+ "application/vnd.google-apps.presentation", # Google Slides
13
+ "application/vnd.google-apps.script", # Google Apps Scripts
14
+ "application/vnd.google-apps.site", # Google Sites
15
+ "application/vnd.google-apps.spreadsheet", # Google Sheets
16
+ "application/vnd.google-apps.unknown",
17
+ "application/vnd.google-apps.video",
18
+ "application/vnd.google-apps.drive-sdk" # 3rd party shortcut
19
+ }
20
+
21
+ def __init__(
22
+ self,
23
+ file_url: str,
24
+ title: str = None,
25
+ mime_type: str = None,
26
+ _icon_link: str = None,
27
+ _file_id: str = None
28
+ ):
29
+ """File attachment for the event.
30
+
31
+ Currently only Google Drive attachments are supported.
32
+
33
+ :param file_url:
34
+ A link for opening the file in a relevant Google editor or viewer.
35
+ :param title:
36
+ Attachment title
37
+ :param mime_type:
38
+ Internet media type (MIME type) of the attachment. See `available MIME types`_
39
+ :param _icon_link:
40
+ URL link to the attachment's icon (read only)
41
+ :param _file_id:
42
+ Id of the attached file (read only)
43
+
44
+ .. note: "read only" means that Attachment has given property only
45
+ when received from the existing event in the calendar.
46
+
47
+ .. _`available MIME types`: https://developers.google.com/drive/api/v3/mime-types
48
+ """
49
+
50
+ self.unsupported_mime_type = mime_type not in Attachment._SUPPORTED_MIME_TYPES
51
+
52
+ self.file_url = file_url
53
+ self.title = title
54
+ self.mime_type = mime_type
55
+ self.icon_link = _icon_link
56
+ self.file_id = _file_id
57
+
58
+ def __eq__(self, other):
59
+ return (
60
+ isinstance(other, Attachment)
61
+ and self.file_url == other.file_url
62
+ and self.title == other.title
63
+ and self.mime_type == other.mime_type
64
+ and self.icon_link == other.icon_link
65
+ and self.file_id == other.file_id
66
+ )
67
+
68
+ def __str__(self):
69
+ return "'{}' - '{}'".format(self.title, self.file_url)
70
+
71
+ def __repr__(self):
72
+ return '<Attachment {}>'.format(self.__str__())
google-calendar-simple-api/build/lib/gcsa/attendee.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .person import Person
2
+
3
+
4
+ class ResponseStatus:
5
+ """Possible values for attendee's response status
6
+
7
+ * NEEDS_ACTION - The attendee has not responded to the invitation.
8
+ * DECLINED - The attendee has declined the invitation.
9
+ * TENTATIVE - The attendee has tentatively accepted the invitation.
10
+ * ACCEPTED - The attendee has accepted the invitation.
11
+ """
12
+ NEEDS_ACTION = "needsAction"
13
+ DECLINED = "declined"
14
+ TENTATIVE = "tentative"
15
+ ACCEPTED = "accepted"
16
+
17
+
18
+ class Attendee(Person):
19
+ def __init__(
20
+ self,
21
+ email: str,
22
+ display_name: str = None,
23
+ comment: str = None,
24
+ optional: bool = None,
25
+ is_resource: bool = None,
26
+ additional_guests: int = None,
27
+ _id: str = None,
28
+ _is_self: bool = None,
29
+ _response_status: str = None
30
+ ):
31
+ """Represents attendee of the event.
32
+
33
+ :param email:
34
+ The attendee's email address, if available.
35
+ :param display_name:
36
+ The attendee's name, if available
37
+ :param comment:
38
+ The attendee's response comment
39
+ :param optional:
40
+ Whether this is an optional attendee. The default is False.
41
+ :param is_resource:
42
+ Whether the attendee is a resource.
43
+ Can only be set when the attendee is added to the event
44
+ for the first time. Subsequent modifications are ignored.
45
+ The default is False.
46
+ :param additional_guests:
47
+ Number of additional guests. The default is 0.
48
+ :param _id:
49
+ The attendee's Profile ID, if available.
50
+ It corresponds to the id field in the People collection of the Google+ API
51
+ :param _is_self:
52
+ Whether this entry represents the calendar on which this copy of the event appears.
53
+ The default is False (set by Google's API).
54
+ :param _response_status:
55
+ The attendee's response status. See :py:class:`~gcsa.attendee.ResponseStatus`
56
+ """
57
+ super().__init__(email=email, display_name=display_name, _id=_id, _is_self=_is_self)
58
+ self.comment = comment
59
+ self.optional = optional
60
+ self.is_resource = is_resource
61
+ self.additional_guests = additional_guests
62
+ self.response_status = _response_status
63
+
64
+ def __eq__(self, other):
65
+ return (
66
+ isinstance(other, Attendee)
67
+ and super().__eq__(other)
68
+ and self.comment == other.comment
69
+ and self.optional == other.optional
70
+ and self.is_resource == other.is_resource
71
+ and self.additional_guests == other.additional_guests
72
+ and self.response_status == other.response_status
73
+ )
74
+
75
+ def __str__(self):
76
+ return "'{}' - response: '{}'".format(self.email, self.response_status)
77
+
78
+ def __repr__(self):
79
+ return '<Attendee {}>'.format(self.__str__())
google-calendar-simple-api/build/lib/gcsa/calendar.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from tzlocal import get_localzone_name
4
+
5
+ from ._resource import Resource
6
+ from .reminders import Reminder
7
+
8
+
9
+ class NotificationType:
10
+ """
11
+ * `EVENT_CREATION` - Notification sent when a new event is put on the calendar.
12
+ * `EVENT_CHANGE` - Notification sent when an event is changed.
13
+ * `EVENT_CANCELLATION` - Notification sent when an event is cancelled.
14
+ * `EVENT_RESPONSE` - Notification sent when an attendee responds to the event invitation.
15
+ * `AGENDA` - An agenda with the events of the day (sent out in the morning).
16
+ """
17
+
18
+ EVENT_CREATION = "eventCreation"
19
+ EVENT_CHANGE = "eventChange"
20
+ EVENT_CANCELLATION = "eventCancellation"
21
+ EVENT_RESPONSE = "eventResponse"
22
+ AGENDA = "agenda"
23
+
24
+
25
+ class AccessRoles:
26
+ """
27
+ * `FREE_BUSY_READER` - Provides read access to free/busy information.
28
+ * `READER` - Provides read access to the calendar.
29
+ Private events will appear to users with reader access, but event details will be hidden.
30
+ * `WRITER` - Provides read and write access to the calendar.
31
+ Private events will appear to users with writer access, and event details will be visible.
32
+ * `OWNER` - Provides ownership of the calendar.
33
+ This role has all of the permissions of the writer role with the additional ability to see and manipulate ACLs.
34
+ """
35
+
36
+ FREE_BUSY_READER = "freeBusyReader"
37
+ READER = "reader"
38
+ WRITER = "writer"
39
+ OWNER = "owner"
40
+
41
+
42
+ class Calendar(Resource):
43
+ def __init__(
44
+ self,
45
+ summary: str,
46
+ *,
47
+ calendar_id: str = None,
48
+ description: str = None,
49
+ location: str = None,
50
+ timezone: str = get_localzone_name(),
51
+ allowed_conference_solution_types: List[str] = None
52
+ ):
53
+ """
54
+ :param summary:
55
+ Title of the calendar.
56
+ :param calendar_id:
57
+ Identifier of the calendar.
58
+ To retrieve calendar IDs call the :py:meth:`~gcsa.google_calendar.GoogleCalendar.get_calendar_list`.
59
+ :param description:
60
+ Description of the calendar.
61
+ :param location:
62
+ Geographic location of the calendar as free-form text.
63
+ :param timezone:
64
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
65
+ the computers local timezone is used if it is configured. UTC is used otherwise.
66
+ :param allowed_conference_solution_types:
67
+ The types of conference solutions that are supported for this calendar.
68
+ See :py:class:`~gcsa.conference.SolutionType`
69
+ """
70
+ self.summary = summary
71
+ self.calendar_id = calendar_id
72
+ self.description = description
73
+ self.location = location
74
+ self.timezone = timezone
75
+ self.allowed_conference_solution_types = allowed_conference_solution_types
76
+
77
+ @property
78
+ def id(self):
79
+ return self.calendar_id
80
+
81
+ def to_calendar_list_entry(
82
+ self,
83
+ summary_override: str = None,
84
+ color_id: str = None,
85
+ background_color: str = None,
86
+ foreground_color: str = None,
87
+ hidden: bool = False,
88
+ selected: bool = False,
89
+ default_reminders: List[Reminder] = None,
90
+ notification_types: List[str] = None,
91
+ ) -> 'CalendarListEntry':
92
+ """Converts :py:class:`~gcsa.calendar.Calendar` to :py:class:`~gcsa.calendar.CalendarListEntry`
93
+ that can be added to the calendar list.
94
+
95
+ :py:class:`~gcsa.calendar.Calendar` has to have `calendar_id` set
96
+ to be converted to :py:class:`~gcsa.calendar.CalendarListEntry`
97
+
98
+ :param summary_override:
99
+ The summary that the authenticated user has set for this calendar.
100
+ :param color_id:
101
+ The color of the calendar. This is an ID referring to an entry in the calendar section of the colors'
102
+ definition (See :py:meth:`~gcsa.google_calendar.GoogleCalendar.list_calendar_colors`).
103
+ This property is superseded by the `background_color` and `foreground_color` properties
104
+ and can be ignored when using these properties.
105
+ :param background_color:
106
+ The main color of the calendar in the hexadecimal format "#0088aa".
107
+ This property supersedes the index-based color_id property.
108
+ :param foreground_color:
109
+ The foreground color of the calendar in the hexadecimal format "#ffffff".
110
+ This property supersedes the index-based color_id property.
111
+ :param hidden:
112
+ Whether the calendar has been hidden from the list.
113
+ :param selected:
114
+ Whether the calendar content shows up in the calendar UI. The default is False.
115
+ :param default_reminders:
116
+ The default reminders that the authenticated user has for this calendar. :py:mod:`~gcsa.reminders`
117
+ :param notification_types:
118
+ The list of notification types set for this calendar. :py:class:`~gcsa:calendar:NotificationType`
119
+
120
+ :return:
121
+ :py:class:`~gcsa.calendar.CalendarListEntry` object that can be added to the calendar list.
122
+ """
123
+ if self.id is None:
124
+ raise ValueError('Calendar has to have `calendar_id` set to be converted to CalendarListEntry')
125
+
126
+ return CalendarListEntry(
127
+ _summary=self.summary,
128
+ calendar_id=self.calendar_id,
129
+ _description=self.description,
130
+ _location=self.location,
131
+ _timezone=self.timezone,
132
+ _allowed_conference_solution_types=self.allowed_conference_solution_types,
133
+
134
+ summary_override=summary_override,
135
+ color_id=color_id,
136
+ background_color=background_color,
137
+ foreground_color=foreground_color,
138
+ hidden=hidden,
139
+ selected=selected,
140
+ default_reminders=default_reminders,
141
+ notification_types=notification_types,
142
+ )
143
+
144
+ def __str__(self):
145
+ return '{} - {}'.format(self.summary, self.description)
146
+
147
+ def __repr__(self):
148
+ return '<Calendar {}>'.format(self.__str__())
149
+
150
+ def __eq__(self, other):
151
+ if not isinstance(other, Calendar):
152
+ return NotImplemented
153
+ elif self is other:
154
+ return True
155
+ else:
156
+ return super().__eq__(other)
157
+
158
+
159
+ class CalendarListEntry(Calendar):
160
+ def __init__(
161
+ self,
162
+ calendar_id: str,
163
+ *,
164
+ summary_override: str = None,
165
+ color_id: str = None,
166
+ background_color: str = None,
167
+ foreground_color: str = None,
168
+ hidden: bool = False,
169
+ selected: bool = False,
170
+ default_reminders: List[Reminder] = None,
171
+ notification_types: List[str] = None,
172
+ _summary: str = None,
173
+ _description: str = None,
174
+ _location: str = None,
175
+ _timezone: str = None,
176
+ _allowed_conference_solution_types: List[str] = None,
177
+ _access_role: str = None,
178
+ _primary: bool = False,
179
+ _deleted: bool = False
180
+ ):
181
+ """
182
+ :param calendar_id:
183
+ Identifier of the calendar.
184
+ :param summary_override:
185
+ The summary that the authenticated user has set for this calendar.
186
+ :param color_id:
187
+ The color of the calendar. This is an ID referring to an entry in the calendar section of the colors'
188
+ definition (See :py:meth:`~gcsa.google_calendar.GoogleCalendar.list_calendar_colors`).
189
+ This property is superseded by the `background_color` and `foreground_color` properties
190
+ and can be ignored when using these properties.
191
+ :param background_color:
192
+ The main color of the calendar in the hexadecimal format "#0088aa".
193
+ This property supersedes the index-based color_id property.
194
+ :param foreground_color:
195
+ The foreground color of the calendar in the hexadecimal format "#ffffff".
196
+ This property supersedes the index-based color_id property.
197
+ :param hidden:
198
+ Whether the calendar has been hidden from the list.
199
+ :param selected:
200
+ Whether the calendar content shows up in the calendar UI. The default is False.
201
+ :param default_reminders:
202
+ The default reminders that the authenticated user has for this calendar. :py:mod:`~gcsa.reminders`
203
+ :param notification_types:
204
+ The list of notification types set for this calendar. :py:class:`~gcsa:calendar:NotificationType`
205
+ :param _summary:
206
+ Title of the calendar. Read-only.
207
+ :param _description:
208
+ Description of the calendar. Read-only.
209
+ :param _location:
210
+ Geographic location of the calendar as free-form text. Read-only.
211
+ :param _timezone:
212
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". Read-only.
213
+ :param _allowed_conference_solution_types:
214
+ The types of conference solutions that are supported for this calendar. Read-only.
215
+ See :py:class:`~gcsa.conference.SolutionType`
216
+ :param _access_role:
217
+ The effective access role that the authenticated user has on the calendar. Read-only.
218
+ See :py:class:`~gcsa.calendar.AccessRoles`
219
+ :param _primary:
220
+ Whether the calendar is the primary calendar of the authenticated user. Read-only.
221
+ :param _deleted:
222
+ Whether this calendar list entry has been deleted from the calendar list. Read-only.
223
+ """
224
+ super().__init__(
225
+ summary=_summary,
226
+ calendar_id=calendar_id,
227
+ description=_description,
228
+ location=_location,
229
+ timezone=_timezone,
230
+ allowed_conference_solution_types=_allowed_conference_solution_types
231
+ )
232
+ self.summary_override = summary_override
233
+ self._color_id = color_id
234
+ self.background_color = background_color
235
+ self.foreground_color = foreground_color
236
+ self.hidden = hidden
237
+ self.selected = selected
238
+ self.default_reminders = default_reminders
239
+ self.notification_types = notification_types
240
+ self.access_role = _access_role
241
+ self.primary = _primary
242
+ self.deleted = _deleted
243
+
244
+ @property
245
+ def color_id(self):
246
+ return self._color_id
247
+
248
+ @color_id.setter
249
+ def color_id(self, color_id):
250
+ """Sets the color_id and resets background_color and foreground_color."""
251
+ self._color_id = color_id
252
+ self.background_color = None
253
+ self.foreground_color = None
254
+
255
+ def __str__(self):
256
+ return '{} - ({})'.format(self.summary_override, self.summary)
257
+
258
+ def __repr__(self):
259
+ return '<CalendarListEntry {}>'.format(self.__str__())
260
+
261
+ def __eq__(self, other):
262
+ if not isinstance(other, CalendarListEntry):
263
+ return NotImplemented
264
+ elif self is other:
265
+ return True
266
+ else:
267
+ return super().__eq__(other)
google-calendar-simple-api/build/lib/gcsa/conference.py ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Union, List
2
+ from uuid import uuid4
3
+
4
+
5
+ class SolutionType:
6
+ """
7
+ * HANGOUT - for Hangouts for consumers (hangouts.google.com)
8
+ * NAMED_HANGOUT - for classic Hangouts for Google Workspace users (hangouts.google.com)
9
+ * HANGOUTS_MEET - for Google Meet (meet.google.com)
10
+ * ADD_ON - for 3P conference providers
11
+ """
12
+
13
+ HANGOUT = 'eventHangout'
14
+ NAMED_HANGOUT = 'eventNamedHangout'
15
+ HANGOUTS_MEET = 'hangoutsMeet'
16
+ ADD_ON = 'addOn'
17
+
18
+
19
+ class _BaseConferenceSolution:
20
+ """General conference-related information."""
21
+
22
+ def __init__(
23
+ self,
24
+ conference_id: str = None,
25
+ signature: str = None,
26
+ notes: str = None,
27
+ _status: str = 'success'
28
+ ):
29
+ """
30
+ :param conference_id:
31
+ The ID of the conference. Optional.
32
+ Can be used by developers to keep track of conferences, should not be displayed to users.
33
+
34
+ Values for solution types (see :py:class:`~gcsa.conference.SolutionType`):
35
+
36
+ * HANGOUT: unset
37
+ * NAMED_HANGOUT: the name of the Hangout
38
+ * HANGOUTS_MEET: the 10-letter meeting code, for example "aaa-bbbb-ccc"
39
+ * ADD_ON: defined by 3P conference provider
40
+
41
+ :param signature:
42
+ The signature of the conference data.
43
+ Generated on server side. Must be preserved while copying the conference data between events,
44
+ otherwise the conference data will not be copied.
45
+ None for a conference with a failed create request.
46
+ Optional for a conference with a pending create request.
47
+ :param notes:
48
+ String of additional notes (such as instructions from the domain administrator, legal notices)
49
+ to display to the user. Can contain HTML. The maximum length is 2048 characters
50
+
51
+ :param _status:
52
+ The current status of the conference create request. Should not be set by developer.
53
+
54
+ The possible values are:
55
+
56
+ * "pending": the conference create request is still being processed.
57
+ * "failure": the conference create request failed, there are no entry points.
58
+ * "success": the conference create request succeeded, the entry points are populated.
59
+ In this case `ConferenceSolution` with created entry points
60
+ is stored in the event's `conference_data`. And `ConferenceSolutionCreateRequest` is omitted.
61
+
62
+ Create requests are asynchronous. Check ``status`` field of event's ``conference_solution`` to find it's
63
+ status. If the status is ``"success"``, ``conference_solution`` will contain a
64
+ :py:class:`~gcsa.conference.ConferenceSolution` object and you'll be able to access it's field (like
65
+ ``entry_points``). Otherwise (if ``status`` is ``""pending"`` or ``"failure"``), ``conference_solution``
66
+ will contain a :py:class:`~gcsa.conference.ConferenceSolutionCreateRequest` object.
67
+
68
+ """
69
+ if notes and len(notes) > 2048:
70
+ raise ValueError('Maximum notes length is 2048 characters.')
71
+
72
+ self.conference_id = conference_id
73
+ self.signature = signature
74
+ self.notes = notes
75
+ self.status = _status
76
+
77
+ def __eq__(self, other):
78
+ if not isinstance(other, _BaseConferenceSolution):
79
+ return NotImplemented
80
+ elif self is other:
81
+ return True
82
+ else:
83
+ return (
84
+ self.conference_id == other.conference_id
85
+ and self.signature == other.signature
86
+ and self.notes == other.notes
87
+ )
88
+
89
+
90
+ class EntryPoint:
91
+ """Information about individual conference entry points, such as URLs or phone numbers."""
92
+
93
+ VIDEO = 'video'
94
+ PHONE = 'phone'
95
+ SIP = 'sip'
96
+ MORE = 'more'
97
+
98
+ ENTRY_POINT_TYPES = (VIDEO, PHONE, SIP, MORE)
99
+
100
+ def __init__(
101
+ self,
102
+ entry_point_type: str,
103
+ uri: str = None,
104
+ label: str = None,
105
+ pin: str = None,
106
+ access_code: str = None,
107
+ meeting_code: str = None,
108
+ passcode: str = None,
109
+ password: str = None
110
+ ):
111
+ """
112
+ When creating new conference data, populate only the subset of `meeting_code`, `access_code`, `passcode`,
113
+ `password`, and `pin` fields that match the terminology that the conference provider uses.
114
+
115
+ Only the populated fields should be displayed.
116
+
117
+ :param entry_point_type:
118
+ The type of the conference entry point.
119
+
120
+ Possible values are:
121
+
122
+ * VIDEO - joining a conference over HTTP.
123
+ A conference can have zero or one `VIDEO` entry point.
124
+ * PHONE - joining a conference by dialing a phone number.
125
+ A conference can have zero or more `PHONE` entry points.
126
+ * SIP - joining a conference over SIP.
127
+ A conference can have zero or one `SIP` entry point.
128
+ * MORE - further conference joining instructions, for example additional phone numbers.
129
+ A conference can have zero or one `MORE` entry point.
130
+ A conference with only a `MORE` entry point is not a valid conference.
131
+
132
+ :param uri:
133
+ The URI of the entry point. The maximum length is 1300 characters.
134
+ Format:
135
+
136
+ * for `VIDEO`, http: or https: schema is required.
137
+ * for `PHONE`, tel: schema is required.
138
+ The URI should include the entire dial sequence (e.g., tel:+12345678900,,,123456789;1234).
139
+ * for `SIP`, sip: schema is required, e.g., sip:12345678@myprovider.com.
140
+ * for `MORE`, http: or https: schema is required.
141
+
142
+ :param label:
143
+ The label for the URI.
144
+ Visible to end users. Not localized. The maximum length is 512 characters.
145
+
146
+ Examples:
147
+
148
+ * for `VIDEO`: meet.google.com/aaa-bbbb-ccc
149
+ * for `PHONE`: +1 123 268 2601
150
+ * for `SIP`: 12345678@altostrat.com
151
+ * for `MORE`: should not be filled
152
+
153
+ :param pin:
154
+ The PIN to access the conference. The maximum length is 128 characters.
155
+ :param access_code:
156
+ The access code to access the conference. The maximum length is 128 characters. Optional.
157
+ :param meeting_code:
158
+ The meeting code to access the conference. The maximum length is 128 characters.
159
+ :param passcode:
160
+ The passcode to access the conference. The maximum length is 128 characters.
161
+ :param password:
162
+ The password to access the conference. The maximum length is 128 characters.
163
+ """
164
+
165
+ if entry_point_type and entry_point_type not in self.ENTRY_POINT_TYPES:
166
+ raise ValueError('"entry_point" must be one of {}. {} was provided.'.format(
167
+ ', '.join(self.ENTRY_POINT_TYPES),
168
+ entry_point_type
169
+ ))
170
+ if label and len(label) > 512:
171
+ raise ValueError('Maximum label length is 512 characters.')
172
+ if pin and len(pin) > 128:
173
+ raise ValueError('Maximum pin length is 128 characters.')
174
+ if access_code and len(access_code) > 128:
175
+ raise ValueError('Maximum access_code length is 128 characters.')
176
+ if meeting_code and len(meeting_code) > 128:
177
+ raise ValueError('Maximum meeting_code length is 128 characters.')
178
+ if passcode and len(passcode) > 128:
179
+ raise ValueError('Maximum passcode length is 128 characters.')
180
+ if password and len(password) > 128:
181
+ raise ValueError('Maximum password length is 128 characters.')
182
+
183
+ self.entry_point_type = entry_point_type
184
+ self.uri = uri
185
+ self.label = label
186
+ self.pin = pin
187
+ self.access_code = access_code
188
+ self.meeting_code = meeting_code
189
+ self.passcode = passcode
190
+ self.password = password
191
+
192
+ def __eq__(self, other):
193
+ if not isinstance(other, EntryPoint):
194
+ return NotImplemented
195
+ elif self is other:
196
+ return True
197
+ else:
198
+ return (
199
+ self.entry_point_type == other.entry_point_type
200
+ and self.uri == other.uri
201
+ and self.label == other.label
202
+ and self.pin == other.pin
203
+ and self.access_code == other.access_code
204
+ and self.meeting_code == other.meeting_code
205
+ and self.passcode == other.passcode
206
+ and self.password == other.password
207
+ )
208
+
209
+ def __str__(self):
210
+ return "{} - '{}'".format(self.entry_point_type, self.uri)
211
+
212
+ def __repr__(self):
213
+ return '<EntryPoint {}>'.format(self.__str__())
214
+
215
+
216
+ class ConferenceSolution(_BaseConferenceSolution):
217
+ """Information about the conference solution, such as Hangouts or Google Meet."""
218
+
219
+ def __init__(
220
+ self,
221
+ entry_points: Union[EntryPoint, List[EntryPoint]],
222
+ solution_type: str = None,
223
+ name: str = None,
224
+ icon_uri: str = None,
225
+ conference_id: str = None,
226
+ signature: str = None,
227
+ notes: str = None
228
+ ):
229
+ """
230
+ :param entry_points:
231
+ :py:class:`~gcsa.conference.EntryPoint` or list of :py:class:`~gcsa.conference.EntryPoint` s.
232
+ Information about individual conference entry points, such as URLs or phone numbers.
233
+ All of them must belong to the same conference.
234
+ :param solution_type:
235
+ Solution type. See :py:class:`~gcsa.conference.SolutionType`
236
+
237
+ The possible values are:
238
+
239
+ * HANGOUT - for Hangouts for consumers (hangouts.google.com)
240
+ * NAMED_HANGOUT - for classic Hangouts for Google Workspace users (hangouts.google.com)
241
+ * HANGOUTS_MEET - for Google Meet (meet.google.com)
242
+ * ADD_ON - for 3P conference providers
243
+
244
+ :param name:
245
+ The user-visible name of this solution. Not localized.
246
+ :param icon_uri:
247
+ The user-visible icon for this solution.
248
+ :param conference_id:
249
+ The ID of the conference. Optional.
250
+ Can be used by developers to keep track of conferences, should not be displayed to users.
251
+
252
+ Values for solution types (see :py:class:`~gcsa.conference.SolutionType`):
253
+
254
+ * HANGOUT: unset
255
+ * NAMED_HANGOUT: the name of the Hangout
256
+ * HANGOUTS_MEET: the 10-letter meeting code, for example "aaa-bbbb-ccc"
257
+ * ADD_ON: defined by 3P conference provider
258
+
259
+ :param signature:
260
+ The signature of the conference data.
261
+ Generated on server side. Must be preserved while copying the conference data between events,
262
+ otherwise the conference data will not be copied.
263
+ None for a conference with a failed create request.
264
+ Optional for a conference with a pending create request.
265
+ :param notes:
266
+ String of additional notes (such as instructions from the domain administrator, legal notices)
267
+ to display to the user. Can contain HTML. The maximum length is 2048 characters
268
+ """
269
+ super().__init__(conference_id=conference_id, signature=signature, notes=notes)
270
+
271
+ self.entry_points = [entry_points] if isinstance(entry_points, EntryPoint) else entry_points
272
+ self._check_entry_points()
273
+
274
+ self.solution_type = solution_type
275
+ self.name = name
276
+ self.icon_uri = icon_uri
277
+
278
+ def _check_entry_points(self):
279
+ """
280
+ Checks counts of entry points types.
281
+
282
+ * A conference can have zero or one `VIDEO` entry point.
283
+ * A conference can have zero or more `PHONE` entry points.
284
+ * A conference can have zero or one `SIP` entry point.
285
+ * A conference can have zero or one `MORE` entry point.
286
+ A conference with only a `MORE` entry point is not a valid conference.
287
+ """
288
+ if len(self.entry_points) == 0:
289
+ raise ValueError('At least one entry point has to be provided.')
290
+
291
+ video_count = 0
292
+ sip_count = 0
293
+ more_count = 0
294
+ for ep in self.entry_points:
295
+ if ep.entry_point_type == EntryPoint.VIDEO:
296
+ video_count += 1
297
+ elif ep.entry_point_type == EntryPoint.SIP:
298
+ sip_count += 1
299
+ elif ep.entry_point_type == EntryPoint.MORE:
300
+ more_count += 1
301
+
302
+ if video_count > 1:
303
+ raise ValueError('A conference can have zero or one `VIDEO` entry point.')
304
+ if sip_count > 1:
305
+ raise ValueError('A conference can have zero or one `SIP` entry point.')
306
+ if more_count > 1:
307
+ raise ValueError('A conference can have zero or one `MORE` entry point.')
308
+ if more_count == len(self.entry_points):
309
+ raise ValueError('A conference with only a `MORE` entry point is not a valid conference.')
310
+
311
+ def __eq__(self, other):
312
+ if not isinstance(other, ConferenceSolution):
313
+ return NotImplemented
314
+ elif self is other:
315
+ return True
316
+ else:
317
+ return (
318
+ super().__eq__(other)
319
+ and self.entry_points == other.entry_points
320
+ and self.solution_type == other.solution_type
321
+ and self.name == other.name
322
+ and self.icon_uri == other.icon_uri
323
+ )
324
+
325
+ def __str__(self):
326
+ return '{} - {}'.format(self.solution_type, self.entry_points)
327
+
328
+ def __repr__(self):
329
+ return '<ConferenceSolution {}>'.format(self.__str__())
330
+
331
+
332
+ class ConferenceSolutionCreateRequest(_BaseConferenceSolution):
333
+ """
334
+ A request to generate a new conference and attach it to the event.
335
+ The data is generated asynchronously. To see whether the data is present check the status field.
336
+ """
337
+
338
+ def __init__(
339
+ self,
340
+ solution_type: str = None,
341
+ request_id: str = None,
342
+ _status: str = None,
343
+ conference_id: str = None,
344
+ signature: str = None,
345
+ notes: str = None
346
+ ):
347
+ """
348
+ :param solution_type:
349
+ Solution type. See :py:class:`~gcsa.conference.SolutionType`
350
+
351
+ The possible values are:
352
+
353
+ * HANGOUT - for Hangouts for consumers (hangouts.google.com)
354
+ * NAMED_HANGOUT - for classic Hangouts for Google Workspace users (hangouts.google.com)
355
+ * HANGOUTS_MEET - for Google Meet (meet.google.com)
356
+ * ADD_ON - for 3P conference providers
357
+
358
+ :param request_id:
359
+ The client-generated unique ID for this request.
360
+ By default it is generated as UUID.
361
+ If you specify request_id manually, they should be unique for every new CreateRequest,
362
+ otherwise request will be ignored.
363
+
364
+ :param _status:
365
+ The current status of the conference create request. Should not be set by developer.
366
+
367
+ The possible values are:
368
+
369
+ * "pending": the conference create request is still being processed.
370
+ * "failure": the conference create request failed, there are no entry points.
371
+ * "success": the conference create request succeeded, the entry points are populated.
372
+ In this case `ConferenceSolution` with created entry points
373
+ is stored in the event's `conference_data`. And `ConferenceSolutionCreateRequest` is omitted.
374
+ :param conference_id:
375
+ The ID of the conference. Optional.
376
+ Can be used by developers to keep track of conferences, should not be displayed to users.
377
+
378
+ Values for solution types (see :py:class:`~gcsa.conference.SolutionType`):
379
+
380
+ * HANGOUT: unset
381
+ * NAMED_HANGOUT: the name of the Hangout
382
+ * HANGOUTS_MEET: the 10-letter meeting code, for example "aaa-bbbb-ccc"
383
+ * ADD_ON: defined by 3P conference provider
384
+
385
+ :param signature:
386
+ The signature of the conference data.
387
+ Generated on server side. Must be preserved while copying the conference data between events,
388
+ otherwise the conference data will not be copied.
389
+ None for a conference with a failed create request.
390
+ Optional for a conference with a pending create request.
391
+ :param notes:
392
+ String of additional notes (such as instructions from the domain administrator, legal notices)
393
+ to display to the user. Can contain HTML. The maximum length is 2048 characters
394
+ """
395
+ super().__init__(conference_id=conference_id, signature=signature, notes=notes, _status=_status)
396
+ self.request_id = request_id or uuid4().hex
397
+ self.solution_type = solution_type
398
+
399
+ def __eq__(self, other):
400
+ if not isinstance(other, ConferenceSolutionCreateRequest):
401
+ return NotImplemented
402
+ elif self is other:
403
+ return True
404
+ else:
405
+ return (
406
+ super().__eq__(other)
407
+ and self.request_id == other.request_id
408
+ and self.solution_type == other.solution_type
409
+ and self.status == other.status
410
+ )
411
+
412
+ def __str__(self):
413
+ return "{} - status:'{}'".format(self.solution_type, self.status)
414
+
415
+ def __repr__(self):
416
+ return '<ConferenceSolutionCreateRequest {}>'.format(self.__str__())
google-calendar-simple-api/build/lib/gcsa/event.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import total_ordering
2
+ from typing import List, Union
3
+
4
+ from beautiful_date import BeautifulDate
5
+ from tzlocal import get_localzone_name
6
+ from datetime import datetime, date, timedelta, time
7
+
8
+ from ._resource import Resource
9
+ from .attachment import Attachment
10
+ from .attendee import Attendee
11
+ from .conference import ConferenceSolution, ConferenceSolutionCreateRequest
12
+ from .person import Person
13
+ from .reminders import PopupReminder, EmailReminder, Reminder
14
+ from .util.date_time_util import ensure_localisation
15
+
16
+
17
+ class Visibility:
18
+ """Possible values of the event visibility.
19
+
20
+ * `DEFAULT` - Uses the default visibility for events on the calendar. This is the default value.
21
+ * `PUBLIC` - The event is public and event details are visible to all readers of the calendar.
22
+ * `PRIVATE` - The event is private and only event attendees may view event details.
23
+ """
24
+
25
+ DEFAULT = "default"
26
+ PUBLIC = "public"
27
+ PRIVATE = "private"
28
+
29
+
30
+ class Transparency:
31
+ """Possible values of the event transparency.
32
+
33
+ * `OPAQUE` - Default value. The event does block time on the calendar.
34
+ This is equivalent to setting 'Show me as' to 'Busy' in the Calendar UI.
35
+ * `TRANSPARENT` - The event does not block time on the calendar.
36
+ This is equivalent to setting 'Show me as' to 'Available' in the Calendar UI.
37
+ """
38
+
39
+ OPAQUE = 'opaque'
40
+ TRANSPARENT = 'transparent'
41
+
42
+
43
+ @total_ordering
44
+ class Event(Resource):
45
+ def __init__(
46
+ self,
47
+ summary: str,
48
+ start: Union[date, datetime, BeautifulDate],
49
+ end: Union[date, datetime, BeautifulDate] = None,
50
+ *,
51
+ timezone: str = get_localzone_name(),
52
+ event_id: str = None,
53
+ description: str = None,
54
+ location: str = None,
55
+ recurrence: Union[str, List[str]] = None,
56
+ color_id: str = None,
57
+ visibility: str = Visibility.DEFAULT,
58
+ attendees: Union[Attendee, str, List[Attendee], List[str]] = None,
59
+ attachments: Union[Attachment, List[Attachment]] = None,
60
+ conference_solution: Union[ConferenceSolution, ConferenceSolutionCreateRequest] = None,
61
+ reminders: Union[Reminder, List[Reminder]] = None,
62
+ default_reminders: bool = False,
63
+ minutes_before_popup_reminder: int = None,
64
+ minutes_before_email_reminder: int = None,
65
+ guests_can_invite_others: bool = True,
66
+ guests_can_modify: bool = False,
67
+ guests_can_see_other_guests: bool = True,
68
+ transparency: str = None,
69
+ _creator: Person = None,
70
+ _organizer: Person = None,
71
+ _created: datetime = None,
72
+ _updated: datetime = None,
73
+ _recurring_event_id: str = None,
74
+ **other
75
+ ):
76
+ """
77
+ :param summary:
78
+ Title of the event.
79
+ :param start:
80
+ Starting date/datetime.
81
+ :param end:
82
+ Ending date/datetime. If 'end' is not specified, event is considered as a 1-day or 1-hour event
83
+ if 'start' is date or datetime respectively.
84
+ :param timezone:
85
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
86
+ the computers local timezone is used if it is configured. UTC is used otherwise.
87
+ :param event_id:
88
+ Opaque identifier of the event. By default, it is generated by the server. You can specify id as a
89
+ 5-1024 long string of characters used in base32hex ([a-vA-V0-9]). The ID must be unique per
90
+ calendar.
91
+ :param description:
92
+ Description of the event. Can contain HTML.
93
+ :param location:
94
+ Geographic location of the event as free-form text.
95
+ :param recurrence:
96
+ RRULE/RDATE/EXRULE/EXDATE string or list of such strings. See :py:mod:`~gcsa.recurrence`
97
+ :param color_id:
98
+ Color id referring to an entry from colors endpoint.
99
+ See :py:meth:`~gcsa.google_calendar.GoogleCalendar.list_event_colors`
100
+ :param visibility:
101
+ Visibility of the event. Default is default visibility for events on the calendar.
102
+ See :py:class:`~gcsa.event.Visibility`
103
+ :param attendees:
104
+ Attendee or list of attendees. See :py:class:`~gcsa.attendee.Attendee`.
105
+ Each attendee may be given as email string or :py:class:`~gcsa.attendee.Attendee` object.
106
+ :param attachments:
107
+ Attachment or list of attachments. See :py:class:`~gcsa.attachment.Attachment`
108
+ :param conference_solution:
109
+ :py:class:`~gcsa.conference.ConferenceSolutionCreateRequest` object to create a new conference
110
+ or :py:class:`~gcsa.conference.ConferenceSolution` object for existing conference.
111
+ :param reminders:
112
+ Reminder or list of reminder objects. See :py:mod:`~gcsa.reminders`
113
+ :param default_reminders:
114
+ Whether the default reminders of the calendar apply to the event.
115
+ :param minutes_before_popup_reminder:
116
+ Minutes before popup reminder or None if reminder is not needed.
117
+ :param minutes_before_email_reminder:
118
+ Minutes before email reminder or None if reminder is not needed.
119
+ :param guests_can_invite_others:
120
+ Whether attendees other than the organizer can invite others to the event.
121
+ :param guests_can_modify:
122
+ Whether attendees other than the organizer can modify the event.
123
+ :param guests_can_see_other_guests:
124
+ Whether attendees other than the organizer can see who the event's attendees are.
125
+ :param transparency:
126
+ Whether the event blocks time on the calendar. See :py:class:`~gcsa.event.Transparency`
127
+ :param _creator:
128
+ The creator of the event. See :py:class:`~gcsa.person.Person`
129
+ :param _organizer:
130
+ The organizer of the event. See :py:class:`~gcsa.person.Person`.
131
+ If the organizer is also an attendee, this is indicated with a separate entry in attendees with
132
+ the organizer field set to True.
133
+ To change the organizer, use the move operation
134
+ see :py:meth:`~gcsa.google_calendar.GoogleCalendar.move_event`
135
+ :param _created:
136
+ Creation time of the event. Read-only.
137
+ :param _updated:
138
+ Last modification time of the event. Read-only.
139
+ :param _recurring_event_id:
140
+ For an instance of a recurring event, this is the id of the recurring event to which
141
+ this instance belongs. Read-only.
142
+ :param other:
143
+ Other fields that should be included in request json. Will be included as they are.
144
+ See more in https://developers.google.com/calendar/v3/reference/events
145
+ """
146
+
147
+ def ensure_list(obj):
148
+ return [] if obj is None else obj if isinstance(obj, list) else [obj]
149
+
150
+ self.timezone = timezone
151
+ self.start = start
152
+ if end or start is None:
153
+ self.end = end
154
+ elif isinstance(start, datetime):
155
+ self.end = start + timedelta(hours=1)
156
+ elif isinstance(start, date):
157
+ self.end = start + timedelta(days=1)
158
+
159
+ if isinstance(self.start, datetime) and isinstance(self.end, datetime):
160
+ self.start = ensure_localisation(self.start, timezone)
161
+ self.end = ensure_localisation(self.end, timezone)
162
+ elif isinstance(self.start, datetime) or isinstance(self.end, datetime):
163
+ raise TypeError('Start and end must either both be date or both be datetime.')
164
+
165
+ def ensure_date(d):
166
+ """Converts d to date if it is of type BeautifulDate."""
167
+ if isinstance(d, BeautifulDate):
168
+ return date(year=d.year, month=d.month, day=d.day)
169
+ else:
170
+ return d
171
+
172
+ self.start = ensure_date(self.start)
173
+ self.end = ensure_date(self.end)
174
+
175
+ self.created = _created
176
+ self.updated = _updated
177
+
178
+ attendees = [self._ensure_attendee_from_email(a) for a in ensure_list(attendees)]
179
+ reminders = ensure_list(reminders)
180
+
181
+ if len(reminders) > 5:
182
+ raise ValueError('The maximum number of override reminders is 5.')
183
+
184
+ if default_reminders and reminders:
185
+ raise ValueError('Cannot specify both default reminders and overrides at the same time.')
186
+
187
+ self.event_id = event_id
188
+ self.summary = summary
189
+ self.description = description
190
+ self.location = location
191
+ self.recurrence = ensure_list(recurrence)
192
+ self.color_id = color_id
193
+ self.visibility = visibility
194
+ self.attendees = attendees
195
+ self.attachments = ensure_list(attachments)
196
+ self.conference_solution = conference_solution
197
+ self.reminders = reminders
198
+ self.default_reminders = default_reminders
199
+ self.recurring_event_id = _recurring_event_id
200
+ self.guests_can_invite_others = guests_can_invite_others
201
+ self.guests_can_modify = guests_can_modify
202
+ self.guests_can_see_other_guests = guests_can_see_other_guests
203
+ self.transparency = transparency
204
+ self.creator = _creator
205
+ self.organizer = _organizer
206
+
207
+ self.other = other
208
+
209
+ if minutes_before_popup_reminder is not None:
210
+ self.add_popup_reminder(minutes_before_popup_reminder)
211
+ if minutes_before_email_reminder is not None:
212
+ self.add_email_reminder(minutes_before_email_reminder)
213
+
214
+ @property
215
+ def id(self):
216
+ return self.event_id
217
+
218
+ def add_attendee(
219
+ self,
220
+ attendee: Union[str, Attendee]
221
+ ):
222
+ """Adds attendee to an event. See :py:class:`~gcsa.attendee.Attendee`.
223
+ Attendee may be given as email string or :py:class:`~gcsa.attendee.Attendee` object."""
224
+ self.attendees.append(self._ensure_attendee_from_email(attendee))
225
+
226
+ def add_attendees(
227
+ self,
228
+ attendees: List[Union[str, Attendee]]
229
+ ):
230
+ """Adds multiple attendees to an event. See :py:class:`~gcsa.attendee.Attendee`.
231
+ Each attendee may be given as email string or :py:class:`~gcsa.attendee.Attendee` object."""
232
+ for a in attendees:
233
+ self.add_attendee(a)
234
+
235
+ def add_attachment(
236
+ self,
237
+ file_url: str,
238
+ title: str = None,
239
+ mime_type: str = None
240
+ ):
241
+ """Adds attachment to an event. See :py:class:`~gcsa.attachment.Attachment`"""
242
+ self.attachments.append(Attachment(file_url=file_url, title=title, mime_type=mime_type))
243
+
244
+ def add_email_reminder(
245
+ self,
246
+ minutes_before_start: int = None,
247
+ days_before: int = None,
248
+ at: time = None
249
+ ):
250
+ """Adds email reminder to an event. See :py:class:`~gcsa.reminders.EmailReminder`"""
251
+ self.add_reminder(EmailReminder(minutes_before_start, days_before, at))
252
+
253
+ def add_popup_reminder(
254
+ self,
255
+ minutes_before_start: int = None,
256
+ days_before: int = None,
257
+ at: time = None
258
+ ):
259
+ """Adds popup reminder to an event. See :py:class:`~gcsa.reminders.PopupReminder`"""
260
+ self.add_reminder(PopupReminder(minutes_before_start, days_before, at))
261
+
262
+ def add_reminder(
263
+ self,
264
+ reminder: Reminder
265
+ ):
266
+ """Adds reminder to an event. See :py:mod:`~gcsa.reminders`"""
267
+ if len(self.reminders) > 4:
268
+ raise ValueError('The maximum number of override reminders is 5.')
269
+ self.reminders.append(reminder)
270
+
271
+ @staticmethod
272
+ def _ensure_attendee_from_email(
273
+ attendee_or_email: Union[str, Attendee]
274
+ ):
275
+ """If attendee_or_email is email string, returns created :py:class:`~gcsa.attendee.Attendee`
276
+ object with the given email."""
277
+ if isinstance(attendee_or_email, str):
278
+ return Attendee(email=attendee_or_email)
279
+ else:
280
+ return attendee_or_email
281
+
282
+ @property
283
+ def is_recurring_instance(self):
284
+ return self.recurring_event_id is not None
285
+
286
+ def __str__(self):
287
+ return '{} - {}'.format(self.start, self.summary)
288
+
289
+ def __repr__(self):
290
+ return '<Event {}>'.format(self.__str__())
291
+
292
+ def __lt__(self, other):
293
+ def ensure_datetime(d, timezone):
294
+ if type(d) is date:
295
+ return ensure_localisation(datetime(year=d.year, month=d.month, day=d.day), timezone)
296
+ else:
297
+ return d
298
+
299
+ start = ensure_datetime(self.start, self.timezone)
300
+ end = ensure_datetime(self.end, self.timezone)
301
+
302
+ other_start = ensure_datetime(other.start, other.timezone)
303
+ other_end = ensure_datetime(other.end, other.timezone)
304
+
305
+ return (start, end) < (other_start, other_end)
306
+
307
+ def __eq__(self, other):
308
+ return (
309
+ isinstance(other, Event)
310
+ and self.start == other.start
311
+ and self.end == other.end
312
+ and self.event_id == other.event_id
313
+ and self.summary == other.summary
314
+ and self.description == other.description
315
+ and self.location == other.location
316
+ and self.recurrence == other.recurrence
317
+ and self.color_id == other.color_id
318
+ and self.visibility == other.visibility
319
+ and self.attendees == other.attendees
320
+ and self.attachments == other.attachments
321
+ and self.reminders == other.reminders
322
+ and self.default_reminders == other.default_reminders
323
+ and self.created == other.created
324
+ and self.updated == other.updated
325
+ and self.recurring_event_id == other.recurring_event_id
326
+ and self.guests_can_invite_others == other.guests_can_invite_others
327
+ and self.guests_can_modify == other.guests_can_modify
328
+ and self.guests_can_see_other_guests == other.guests_can_see_other_guests
329
+ and self.other == other.other
330
+ )
google-calendar-simple-api/build/lib/gcsa/free_busy.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from collections import namedtuple
3
+ from datetime import datetime
4
+ from typing import Dict, List
5
+
6
+ TimeRange = namedtuple('TimeRange', ('start', 'end'))
7
+
8
+
9
+ class FreeBusy:
10
+ def __init__(
11
+ self,
12
+ *,
13
+ time_min: datetime,
14
+ time_max: datetime,
15
+ groups: Dict[str, List[str]],
16
+ calendars: Dict[str, List[TimeRange]],
17
+ groups_errors: Dict = None,
18
+ calendars_errors: Dict = None,
19
+ ):
20
+ """Represents free/busy information for a given calendar(s) and/or group(s)
21
+
22
+ :param time_min:
23
+ The start of the interval.
24
+ :param time_max:
25
+ The end of the interval.
26
+ :param groups:
27
+ Expansion of groups.
28
+ Dictionary that maps the name of the group to the list of calendars that are members of this group.
29
+ :param calendars:
30
+ Free/busy information for calendars.
31
+ Dictionary that maps calendar id to the list of time ranges during which this calendar should be
32
+ regarded as busy.
33
+ :param groups_errors:
34
+ Optional error(s) (if computation for the group failed).
35
+ Dictionary that maps the name of the group to the list of errors.
36
+ :param calendars_errors:
37
+ Optional error(s) (if computation for the calendar failed).
38
+ Dictionary that maps calendar id to the list of errors.
39
+
40
+
41
+ .. note:: Errors have the following format:
42
+
43
+ .. code-block::
44
+
45
+ {
46
+ "domain": "<domain>",
47
+ "reason": "<reason>"
48
+ }
49
+
50
+ Some possible values for "reason" are:
51
+
52
+ * "groupTooBig" - The group of users requested is too large for a single query.
53
+ * "tooManyCalendarsRequested" - The number of calendars requested is too large for a single query.
54
+ * "notFound" - The requested resource was not found.
55
+ * "internalError" - The API service has encountered an internal error.
56
+
57
+ Additional error types may be added in the future.
58
+ """
59
+ self.time_min = time_min
60
+ self.time_max = time_max
61
+ self.groups = groups
62
+ self.calendars = calendars
63
+ self.groups_errors = groups_errors or {}
64
+ self.calendars_errors = calendars_errors or {}
65
+
66
+ def __iter__(self):
67
+ """
68
+ :returns:
69
+ list of 'TimeRange's during which this calendar should be regarded as busy.
70
+ :raises:
71
+ ValueError if requested all requested calendars have errors
72
+ or more than one calendar has been requested.
73
+ """
74
+ if len(self.calendars) == 0:
75
+ raise ValueError("No free/busy information has been received. "
76
+ "Check the 'calendars_errors' and 'groups_errors' fields.")
77
+ if len(self.calendars) > 1 or len(self.calendars_errors) > 0:
78
+ raise ValueError("Can't iterate over FreeBusy objects directly when more than one calendars were requested."
79
+ "Use 'calendars' field instead to get free/busy information of the specific calendar.")
80
+ return iter(next(iter(self.calendars.values())))
81
+
82
+ def __str__(self):
83
+ return '<FreeBusy {} - {}>'.format(self.time_min, self.time_max)
84
+
85
+ def __repr__(self):
86
+ return self.__str__()
87
+
88
+
89
+ class FreeBusyQueryError(Exception):
90
+ def __init__(self, groups_errors, calendars_errors):
91
+ message = '\n'
92
+ if groups_errors:
93
+ message += f'Groups errors: {json.dumps(groups_errors, indent=4)}'
94
+ if calendars_errors:
95
+ message += f'Calendars errors: {json.dumps(calendars_errors, indent=4)}'
96
+ super().__init__(message)
97
+ self.groups_errors = groups_errors
98
+ self.calendars_errors = calendars_errors
google-calendar-simple-api/build/lib/gcsa/google_calendar.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from google.oauth2.credentials import Credentials
2
+
3
+ from ._services.acl_service import ACLService
4
+ from ._services.events_service import EventsService, SendUpdatesMode # noqa: F401
5
+ from ._services.calendars_service import CalendarsService
6
+ from ._services.calendar_lists_service import CalendarListService
7
+ from ._services.colors_service import ColorsService
8
+ from ._services.free_busy_service import FreeBusyService
9
+ from ._services.settings_service import SettingsService
10
+
11
+
12
+ class GoogleCalendar(
13
+ EventsService,
14
+ CalendarsService,
15
+ CalendarListService,
16
+ ColorsService,
17
+ SettingsService,
18
+ ACLService,
19
+ FreeBusyService
20
+ ):
21
+ """Collection of all supported methods for events and calendars management."""
22
+
23
+ def __init__(
24
+ self,
25
+ default_calendar: str = 'primary',
26
+ *,
27
+ credentials: Credentials = None,
28
+ credentials_path: str = None,
29
+ token_path: str = None,
30
+ save_token: bool = True,
31
+ read_only: bool = False,
32
+ authentication_flow_host: str = 'localhost',
33
+ authentication_flow_port: int = 8080,
34
+ authentication_flow_bind_addr: str = None
35
+ ):
36
+ """
37
+ Specify ``credentials`` to use in requests or ``credentials_path`` and ``token_path`` to get credentials from.
38
+
39
+ :param default_calendar:
40
+ Users email address or name/id of the calendar. Default: primary calendar of the user
41
+
42
+ If user's email or "primary" is specified, then primary calendar of the user is used.
43
+ You don't need to specify this parameter in this case as it is a default behaviour.
44
+
45
+ To use a different calendar you need to specify its id.
46
+ Go to calendar's `settings and sharing` -> `Integrate calendar` -> `Calendar ID`.
47
+ :param credentials:
48
+ Credentials with token and refresh token.
49
+ If specified, ``credentials_path``, ``token_path``, and ``save_token`` are ignored.
50
+ If not specified, credentials are retrieved from "token.pickle" file (specified in ``token_path`` or
51
+ default path) or with authentication flow using secret from "credentials.json" ("client_secret_*.json")
52
+ (specified in ``credentials_path`` or default path)
53
+ :param credentials_path:
54
+ Path to "credentials.json" ("client_secret_*.json") file.
55
+ Default: ~/.credentials/credentials.json or ~/.credentials/client_secret*.json
56
+ :param token_path:
57
+ Existing path to load the token from, or path to save the token after initial authentication flow.
58
+ Default: "token.pickle" in the same directory as the credentials_path
59
+ :param save_token:
60
+ Whether to pickle token after authentication flow for future uses
61
+ :param read_only:
62
+ If require read only access. Default: False
63
+ :param authentication_flow_host:
64
+ Host to receive response during authentication flow
65
+ :param authentication_flow_port:
66
+ Port to receive response during authentication flow
67
+ :param authentication_flow_bind_addr:
68
+ Optional IP address for the redirect server to listen on when it is not the same as host
69
+ (e.g. in a container)
70
+ """
71
+ super().__init__(
72
+ default_calendar=default_calendar,
73
+ credentials=credentials,
74
+ credentials_path=credentials_path,
75
+ token_path=token_path,
76
+ save_token=save_token,
77
+ read_only=read_only,
78
+ authentication_flow_host=authentication_flow_host,
79
+ authentication_flow_port=authentication_flow_port,
80
+ authentication_flow_bind_addr=authentication_flow_bind_addr
81
+ )
google-calendar-simple-api/build/lib/gcsa/person.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Person:
2
+ def __init__(
3
+ self,
4
+ email: str = None,
5
+ display_name: str = None,
6
+ _id: str = None,
7
+ _is_self: bool = None
8
+ ):
9
+ """Represents organizer's, creator's, or primary attendee's fields.
10
+ For attendees see more in :py:class:`~gcsa.attendee.Attendee`.
11
+
12
+ :param email:
13
+ The person's email address, if available
14
+ :param display_name:
15
+ The person's name, if available
16
+ :param _id:
17
+ The person's Profile ID, if available.
18
+ It corresponds to the id field in the People collection of the Google+ API
19
+ :param _is_self:
20
+ Whether the person corresponds to the calendar on which the copy of the event appears.
21
+ The default is False (set by Google's API).
22
+ """
23
+ self.email = email
24
+ self.display_name = display_name
25
+ self.id_ = _id
26
+ self.is_self = _is_self
27
+
28
+ def __eq__(self, other):
29
+ return (
30
+ isinstance(other, Person)
31
+ and self.email == other.email
32
+ and self.display_name == other.display_name
33
+ and self.id_ == other.id_
34
+ and self.is_self == other.is_self
35
+ )
36
+
37
+ def __str__(self):
38
+ return "'{}' - '{}'".format(self.email, self.display_name)
39
+
40
+ def __repr__(self):
41
+ return '<Person {}>'.format(self.__str__())
google-calendar-simple-api/build/lib/gcsa/recurrence.py ADDED
@@ -0,0 +1,570 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, date
2
+
3
+ from tzlocal import get_localzone_name
4
+
5
+ from .util.date_time_util import ensure_localisation
6
+
7
+
8
+ class Duration:
9
+ """Represents properties that contain a duration of time."""
10
+
11
+ def __init__(self, w=None, d=None, h=None, m=None, s=None):
12
+ """
13
+ :param w: weeks
14
+ :param d: days
15
+ :param h: hours
16
+ :param m: minutes
17
+ :param s: seconds
18
+ """
19
+
20
+ self.w = w
21
+ self.d = d
22
+ self.h = h
23
+ self.m = m
24
+ self.s = s
25
+
26
+ def __str__(self):
27
+ res = 'P'
28
+ if self.w:
29
+ res += '{}W'.format(self.w)
30
+ if self.d:
31
+ res += '{}D'.format(self.d)
32
+ if self.h or self.m or self.s:
33
+ res += 'T'
34
+ if self.h:
35
+ res += '{}H'.format(self.h)
36
+ if self.m:
37
+ res += '{}M'.format(self.m)
38
+ if self.s:
39
+ res += '{}S'.format(self.s)
40
+
41
+ return res
42
+
43
+
44
+ class _DayOfTheWeek:
45
+ """Weekday representation. Optionally includes positive or negative integer
46
+ value that indicates the nth occurrence of a specific day within the "MONTHLY"
47
+ or "YEARLY" recurrence rules.
48
+
49
+ >>> str(SU)
50
+ 'SU'
51
+
52
+ >>> str(FR)
53
+ 'FR'
54
+
55
+ >>> str(SU(4))
56
+ '4SU'
57
+
58
+ >>> str(SU(-1))
59
+ '-1SU'
60
+ """
61
+
62
+ def __init__(self, short, n=None):
63
+ self.short = short
64
+ self.n = n
65
+
66
+ def __call__(self, n):
67
+ return _DayOfTheWeek(self.short, n)
68
+
69
+ def __str__(self):
70
+ if self.n is None:
71
+ return self.short
72
+ else:
73
+ return str(self.n) + self.short
74
+
75
+
76
+ SU = SUNDAY = _DayOfTheWeek('SU')
77
+ MO = MONDAY = _DayOfTheWeek('MO')
78
+ TU = TUESDAY = _DayOfTheWeek('TU')
79
+ WE = WEDNESDAY = _DayOfTheWeek('WE')
80
+ TH = THURSDAY = _DayOfTheWeek('TH')
81
+ FR = FRIDAY = _DayOfTheWeek('FR')
82
+ SA = SATURDAY = _DayOfTheWeek('SA')
83
+
84
+ DEFAULT_WEEK_START = SUNDAY
85
+
86
+ SECONDLY = 'SECONDLY'
87
+ MINUTELY = 'MINUTELY'
88
+ HOURLY = 'HOURLY'
89
+
90
+ DAILY = 'DAILY'
91
+ WEEKLY = 'WEEKLY'
92
+ MONTHLY = 'MONTHLY'
93
+ YEARLY = 'YEARLY'
94
+
95
+
96
+ class Recurrence:
97
+
98
+ @staticmethod
99
+ def rule(
100
+ freq=DAILY,
101
+ interval=None,
102
+ count=None,
103
+ until=None,
104
+ by_second=None,
105
+ by_minute=None,
106
+ by_hour=None,
107
+ by_week_day=None,
108
+ by_month_day=None,
109
+ by_year_day=None,
110
+ by_week=None,
111
+ by_month=None,
112
+ by_set_pos=None,
113
+ week_start=DEFAULT_WEEK_START
114
+ ):
115
+ """This property defines a rule or repeating pattern for recurring events.
116
+
117
+ :param freq:
118
+ Identifies the type of recurrence rule. Possible values are SECONDLY, HOURLY,
119
+ MINUTELY, DAILY, WEEKLY, MONTHLY, YEARLY. Default: DAILY
120
+ :param interval:
121
+ Positive integer representing how often the recurrence rule repeats
122
+ :param count:
123
+ Number of occurrences at which to range-bound the recurrence
124
+ :param until:
125
+ End date of recurrence
126
+ :param by_second:
127
+ Second or list of seconds within a minute. Valid values are 0 to 60
128
+ :param by_minute:
129
+ Minute or list of minutes within a hour. Valid values are 0 to 59
130
+ :param by_hour:
131
+ Hour or list of hours of the day. Valid values are 0 to 23
132
+ :param by_week_day:
133
+ Day or list of days of the week.
134
+ Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
135
+ :py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
136
+ :param by_month_day:
137
+ Day or list of days of the month. Valid values are 1 to 31 or -31 to -1.
138
+ For example, -10 represents the tenth to the last day of the month.
139
+ :param by_year_day:
140
+ Day or list of days of the year. Valid values are 1 to 366 or -366 to -1.
141
+ For example, -1 represents the last day of the year.
142
+ :param by_week:
143
+ Ordinal or list of ordinals specifying weeks of the year. Valid values are 1 to 53 or -53 to -1.
144
+ :param by_month:
145
+ Month or list of months of the year. Valid values are 1 to 12.
146
+ :param by_set_pos:
147
+ Value or list of values which corresponds to the nth occurrence within the set of events
148
+ specified by the rule. Valid values are 1 to 366 or -366 to -1.
149
+ It can only be used in conjunction with another by_xxx parameter.
150
+ :param week_start:
151
+ The day on which the workweek starts.
152
+ Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
153
+ :py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
154
+
155
+ :return:
156
+ String representing specified recurrence rule in `RRULE format`_.
157
+
158
+ .. note:: If none of the by_day, by_month_day, or by_year_day are specified, the day is gotten from start date.
159
+
160
+
161
+ .. _`RRULE format`: https://tools.ietf.org/html/rfc5545#section-3.8.5
162
+ """
163
+ return 'RRULE:' + Recurrence._rule(freq, interval, count, until, by_second, by_minute, by_hour, by_week_day,
164
+ by_month_day, by_year_day, by_week, by_month, by_set_pos, week_start)
165
+
166
+ @staticmethod
167
+ def exclude_rule(
168
+ freq=DAILY,
169
+ interval=None,
170
+ count=None,
171
+ until=None,
172
+ by_second=None,
173
+ by_minute=None,
174
+ by_hour=None,
175
+ by_week_day=None,
176
+ by_month_day=None,
177
+ by_year_day=None,
178
+ by_week=None,
179
+ by_month=None,
180
+ by_set_pos=None,
181
+ week_start=DEFAULT_WEEK_START
182
+ ):
183
+ """This property defines an exclusion rule or repeating pattern for recurring events.
184
+
185
+ :param freq:
186
+ Identifies the type of recurrence rule. Possible values are SECONDLY, HOURLY,
187
+ MINUTELY, DAILY, WEEKLY, MONTHLY, YEARLY. Default: DAILY
188
+ :param interval:
189
+ Positive integer representing how often the recurrence rule repeats
190
+ :param count:
191
+ Number of occurrences at which to range-bound the recurrence
192
+ :param until:
193
+ End date of recurrence
194
+ :param by_second:
195
+ Second or list of seconds within a minute. Valid values are 0 to 60
196
+ :param by_minute:
197
+ Minute or list of minutes within a hour. Valid values are 0 to 59
198
+ :param by_hour:
199
+ Hour or list of hours of the day. Valid values are 0 to 23
200
+ :param by_week_day:
201
+ Day or list of days of the week.
202
+ Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
203
+ :py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
204
+ :param by_month_day:
205
+ Day or list of days of the month. Valid values are 1 to 31 or -31 to -1.
206
+ For example, -10 represents the tenth to the last day of the month.
207
+ :param by_year_day:
208
+ Day or list of days of the year. Valid values are 1 to 366 or -366 to -1.
209
+ For example, -1 represents the last day of the year.
210
+ :param by_week:
211
+ Ordinal or list of ordinals specifying weeks of the year. Valid values are 1 to 53 or -53 to -1.
212
+ :param by_month:
213
+ Month or list of months of the year. Valid values are 1 to 12.
214
+ :param by_set_pos:
215
+ Value or list of values which corresponds to the nth occurrence within the set of events
216
+ specified by the rule. Valid values are 1 to 366 or -366 to -1.
217
+ It can only be used in conjunction with another by_xxx parameter.
218
+ :param week_start:
219
+ The day on which the workweek starts.
220
+ Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
221
+ :py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
222
+
223
+ :return:
224
+ String representing specified recurrence rule in `RRULE format`_.
225
+
226
+ .. note:: If none of the by_day, by_month_day, or by_year_day are specified, the day is gotten from start date.
227
+
228
+
229
+ .. _`RRULE format`: https://tools.ietf.org/html/rfc5545#section-3.8.5
230
+ """
231
+ return 'EXRULE:' + Recurrence._rule(freq, interval, count, until, by_second, by_minute, by_hour, by_week_day,
232
+ by_month_day, by_year_day, by_week, by_month, by_set_pos, week_start)
233
+
234
+ @staticmethod
235
+ def dates(ds):
236
+ """Converts date(s) set to RDATE format.
237
+
238
+ :param ds:
239
+ date/datetime object or list of date/datetime objects
240
+
241
+ :return:
242
+ RDATE string of dates.
243
+ """
244
+ return 'RDATE;' + Recurrence._dates(ds)
245
+
246
+ @staticmethod
247
+ def times(dts, timezone=get_localzone_name()):
248
+ """Converts datetime(s) set to RDATE format.
249
+
250
+ :param dts:
251
+ datetime object or list of datetime objects
252
+ :param timezone:
253
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
254
+ the computers local timezone is used if it is configured. UTC is used otherwise.
255
+
256
+ :return:
257
+ RDATE string of datetimes with specified timezone.
258
+ """
259
+ return 'RDATE;' + Recurrence._times(dts, timezone)
260
+
261
+ @staticmethod
262
+ def periods(ps, timezone=get_localzone_name()):
263
+ """Converts date period(s) to RDATE format.
264
+
265
+ Period is defined as tuple of starting date/datetime and ending date/datetime or duration as Duration object:
266
+ (date/datetime, date/datetime/Duration)
267
+
268
+ :param ps:
269
+ Period or list of periods.
270
+ :param timezone:
271
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
272
+ the computers local timezone is used if it is configured. UTC is used otherwise.
273
+
274
+ :return:
275
+ RDATE string of periods.
276
+ """
277
+ return 'RDATE;' + Recurrence._periods(ps, timezone)
278
+
279
+ @staticmethod
280
+ def exclude_dates(ds):
281
+ """Converts date(s) set to EXDATE format.
282
+
283
+ :param ds:
284
+ date/datetime object or list of date/datetime objects
285
+
286
+ :return:
287
+ EXDATE string of dates.
288
+ """
289
+ return 'EXDATE;' + Recurrence._dates(ds)
290
+
291
+ @staticmethod
292
+ def exclude_times(dts, timezone=get_localzone_name()):
293
+ """Converts datetime(s) set to EXDATE format.
294
+
295
+ :param dts:
296
+ datetime object or list of datetime objects
297
+ :param timezone:
298
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
299
+ the computers local timezone is used if it is configured. UTC is used otherwise.
300
+
301
+ :return:
302
+ EXDATE string of datetimes with specified timezone.
303
+ """
304
+ return 'EXDATE;' + Recurrence._times(dts, timezone)
305
+
306
+ @staticmethod
307
+ def exclude_periods(ps, timezone=get_localzone_name()):
308
+ """Converts date period(s) to EXDATE format.
309
+
310
+ Period is defined as tuple of starting date/datetime and ending date/datetime or duration as Duration object:
311
+ (date/datetime, date/datetime/Duration)
312
+
313
+ :param ps:
314
+ Period or list of periods.
315
+ :param timezone:
316
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
317
+ the computers local timezone is used if it is configured. UTC is used otherwise.
318
+
319
+ :return:
320
+ EXDATE string of periods.
321
+ """
322
+ return 'EXDATE;' + Recurrence._periods(ps, timezone)
323
+
324
+ @staticmethod
325
+ def _times(dts, timezone=get_localzone_name()):
326
+ """Converts datetime(s) set to RDATE format.
327
+
328
+ :param dts:
329
+ datetime object or list of datetime objects
330
+ :param timezone:
331
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
332
+ the computers local timezone is used if it is configured. UTC is used otherwise.
333
+
334
+ :return:
335
+ RDATE string of datetimes with specified timezone.
336
+ """
337
+
338
+ if not isinstance(dts, list):
339
+ dts = [dts]
340
+
341
+ localized_datetimes = []
342
+ for dt in dts:
343
+ if not isinstance(dt, (date, datetime)):
344
+ msg = 'The dts object(s) must be date or datetime, not {!r}.'.format(dt.__class__.__name__)
345
+ raise TypeError(msg)
346
+ localized_datetimes.append(ensure_localisation(dt, timezone))
347
+
348
+ return 'TZID={}:{}'.format(timezone, ','.join(d.strftime('%Y%m%dT%H%M%S') for d in localized_datetimes))
349
+
350
+ @staticmethod
351
+ def _dates(ds):
352
+ """Converts date(s) set to RDATE format.
353
+
354
+ :param ds:
355
+ date/datetime object or list of date/datetime objects
356
+
357
+ :return:
358
+ RDATE string of dates.
359
+ """
360
+ if not isinstance(ds, list):
361
+ ds = [ds]
362
+
363
+ for d in ds:
364
+ if not (isinstance(d, (date, datetime))):
365
+ msg = 'The dates object(s) must be date or datetime, not {!r}.'.format(d.__class__.__name__)
366
+ raise TypeError(msg)
367
+
368
+ return 'VALUE=DATE:' + ','.join(d.strftime('%Y%m%d') for d in ds)
369
+
370
+ @staticmethod
371
+ def _periods(ps, timezone=get_localzone_name()):
372
+ """Converts date period(s) to RDATE format.
373
+
374
+ Period is defined as tuple of starting date/datetime and ending date/datetime or duration as Duration object:
375
+ (date/datetime, date/datetime/Duration)
376
+
377
+ :param ps:
378
+ Period or list of periods.
379
+ :param timezone:
380
+ Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
381
+ the computers local timezone is used if it is configured. UTC is used otherwise.
382
+
383
+ :return:
384
+ RDATE string of periods.
385
+ """
386
+ if not isinstance(ps, list):
387
+ ps = [ps]
388
+
389
+ period_strings = []
390
+ for start, end in ps:
391
+ if not isinstance(start, (date, datetime)):
392
+ msg = 'The start object(s) must be a date or datetime, not {!r}.'.format(end.__class__.__name__)
393
+ raise TypeError(msg)
394
+
395
+ start = ensure_localisation(start, timezone)
396
+ if isinstance(end, (date, datetime)):
397
+ end = ensure_localisation(end, timezone)
398
+ pstr = '{}/{}'.format(start.strftime('%Y%m%dT%H%M%SZ'), end.strftime('%Y%m%dT%H%M%SZ'))
399
+ elif isinstance(end, Duration):
400
+ pstr = '{}/{}'.format(start.strftime('%Y%m%dT%H%M%SZ'), end)
401
+ else:
402
+ msg = 'The end object(s) must be a date, datetime or Duration, not {!r}.'.format(end.__class__.__name__)
403
+ raise TypeError(msg)
404
+ period_strings.append(pstr)
405
+
406
+ return 'VALUE=PERIOD:' + ','.join(period_strings)
407
+
408
+ @staticmethod
409
+ def _rule(
410
+ freq=DAILY,
411
+ interval=None,
412
+ count=None,
413
+ until=None,
414
+ by_second=None, # BYSECOND
415
+ by_minute=None, # BYMINUTE
416
+ by_hour=None, # BYHOUR
417
+ by_week_day=None, # BYDAY
418
+ by_month_day=None, # BYMONTHDAY
419
+ by_year_day=None, # BYYEARDAY
420
+ by_week=None, # BYWEEKNO
421
+ by_month=None, # BYMONTH
422
+ by_set_pos=None, # BYSETPOS
423
+ week_start=DEFAULT_WEEK_START # WKST
424
+ ):
425
+ """This property defines a rule or repeating pattern for recurring events.
426
+
427
+ :param freq:
428
+ Identifies the type of recurrence rule. Possible values are SECONDLY, HOURLY,
429
+ MINUTELY, DAILY, WEEKLY, MONTHLY, YEARLY. Default: DAILY
430
+ :param interval:
431
+ Positive integer representing how often the recurrence rule repeats
432
+ :param count:
433
+ Number of occurrences at which to range-bound the recurrence
434
+ :param until:
435
+ End date of recurrence
436
+ :param by_second:
437
+ Second or list of seconds within a minute. Valid values are 0 to 60
438
+ :param by_minute:
439
+ Minute or list of minutes within a hour. Valid values are 0 to 59
440
+ :param by_hour:
441
+ Hour or list of hours of the day. Valid values are 0 to 23
442
+ :param by_week_day:
443
+ Day or list of days of the week.
444
+ Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
445
+ :py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
446
+ :param by_month_day:
447
+ Day or list of days of the month. Valid values are 1 to 31 or -31 to -1.
448
+ For example, -10 represents the tenth to the last day of the month.
449
+ :param by_year_day:
450
+ Day or list of days of the year. Valid values are 1 to 366 or -366 to -1.
451
+ For example, -1 represents the last day of the year.
452
+ :param by_week:
453
+ Ordinal or list of ordinals specifying weeks of the year. Valid values are 1 to 53 or -53 to -1.
454
+ :param by_month:
455
+ Month or list of months of the year. Valid values are 1 to 12.
456
+ :param by_set_pos:
457
+ Value or list of values which corresponds to the nth occurrence within the set of events
458
+ specified by the rule. Valid values are 1 to 366 or -366 to -1.
459
+ It can only be used in conjunction with another by_xxx parameter.
460
+ :param week_start:
461
+ The day on which the workweek starts.
462
+ Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
463
+ :py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
464
+
465
+ :return:
466
+ String representing specified recurrence rule in `RRULE format`_.
467
+
468
+ .. note:: If none of the by_day, by_month_day, or by_year_day are specified, the day is gotten from start date.
469
+
470
+
471
+ .. _`RRULE format`: https://tools.ietf.org/html/rfc5545#section-3.8.5
472
+ """
473
+
474
+ def ensure_iterable(it):
475
+ return it if isinstance(it, (list, tuple, set)) else [it] if it is not None else []
476
+
477
+ def check_all_type(it, type_, name):
478
+ if any(not isinstance(o, type_) for o in it):
479
+ raise TypeError('"{}" parameter must be a {} or list of {}s.'
480
+ .format(name, type_.__name__, type_.__name__))
481
+
482
+ def check_all_type_and_range(it, type_, range_, name, nonzero=False):
483
+ check_all_type(it, type_, name)
484
+ low, high = range_
485
+ if any(not (low <= o <= high) for o in it):
486
+ raise ValueError('"{}" parameter must be in range {}-{}.'
487
+ .format(name, low, high))
488
+ if nonzero and any(o == 0 for o in it):
489
+ raise ValueError('"{}" parameter must be in range {}-{} and nonzero.'
490
+ .format(name, low, high))
491
+
492
+ def to_string(values):
493
+ return ','.join(map(str, values)) if values else None
494
+
495
+ if freq not in (SECONDLY, MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY):
496
+ raise ValueError('"freq" parameter must be one of SECONDLY, HOURLY, MINUTELY, DAILY, '
497
+ 'WEEKLY, MONTHLY or YEARLY. {} was provided'.format(freq))
498
+ if interval is not None and (not isinstance(interval, int) or interval < 1):
499
+ raise ValueError('"interval" parameter must be a positive int. '
500
+ '{} was provided'.format(interval))
501
+ if count is not None and (not isinstance(count, int) or count < 1):
502
+ raise ValueError('"count" parameter must be a positive int. '
503
+ '{} was provided'.format(count))
504
+ if until is not None:
505
+ if not isinstance(until, (date, datetime)):
506
+ raise TypeError('The until object must be a date or datetime, '
507
+ 'not {!r}.'.format(until.__class__.__name__))
508
+ else:
509
+ until = until.strftime("%Y%m%dT%H%M%SZ")
510
+ if count is not None and until is not None:
511
+ raise ValueError('"count" and "until" may not appear in one recurrence rule.')
512
+
513
+ by_second = ensure_iterable(by_second)
514
+ check_all_type_and_range(by_second, int, (0, 60), "by_second")
515
+
516
+ by_minute = ensure_iterable(by_minute)
517
+ check_all_type_and_range(by_minute, int, (0, 59), "by_minute")
518
+
519
+ by_hour = ensure_iterable(by_hour)
520
+ check_all_type_and_range(by_hour, int, (0, 23), "by_hour")
521
+
522
+ by_week_day = ensure_iterable(by_week_day)
523
+ check_all_type(by_week_day, _DayOfTheWeek, "by_week_day")
524
+
525
+ by_month_day = ensure_iterable(by_month_day)
526
+ check_all_type_and_range(by_month_day, int, (-31, 31), "by_month_day", nonzero=True)
527
+
528
+ by_year_day = ensure_iterable(by_year_day)
529
+ check_all_type_and_range(by_year_day, int, (-366, 366), "by_year_day", nonzero=True)
530
+
531
+ by_week = ensure_iterable(by_week)
532
+ check_all_type_and_range(by_week, int, (-53, 53), "by_week", nonzero=True)
533
+
534
+ by_month = ensure_iterable(by_month)
535
+ check_all_type_and_range(by_month, int, (1, 12), "by_month")
536
+
537
+ by_set_pos = ensure_iterable(by_set_pos)
538
+ check_all_type_and_range(by_set_pos, int, (-366, 366), "by_set_pos", nonzero=True)
539
+ if by_set_pos and all(not v for v in (by_second, by_minute, by_hour,
540
+ by_week_day, by_month_day, by_year_day,
541
+ by_week, by_month)):
542
+ raise ValueError('"by_set_pos" parameter can only be used in conjunction with another by_xxx parameter.')
543
+
544
+ if not isinstance(week_start, _DayOfTheWeek):
545
+ raise ValueError('"week_start" parameter must be one of SUNDAY, MONDAY, etc. '
546
+ '{} was provided'.format(week_start))
547
+
548
+ rrule = 'FREQ={}'.format(freq)
549
+
550
+ rule_properties = (
551
+ ('INTERVAL', interval),
552
+ ('COUNT', count),
553
+ ('UNTIL', until),
554
+ ('BYSECOND', to_string(by_second)),
555
+ ('BYMINUTE', to_string(by_minute)),
556
+ ('BYHOUR', to_string(by_hour)),
557
+ ('BYDAY', to_string(by_week_day)),
558
+ ('BYMONTHDAY', to_string(by_month_day)),
559
+ ('BYYEARDAY', to_string(by_year_day)),
560
+ ('BYWEEKNO', to_string(by_week)),
561
+ ('BYMONTH', to_string(by_month)),
562
+ ('BYSETPOS', to_string(by_set_pos)),
563
+ ('WKST', week_start)
564
+ )
565
+
566
+ for key, value in rule_properties:
567
+ if value:
568
+ rrule += ';{}={}'.format(key, value)
569
+
570
+ return rrule
google-calendar-simple-api/build/lib/gcsa/reminders.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import time, date, datetime
2
+ from typing import Union
3
+
4
+ from beautiful_date import BeautifulDate, days
5
+
6
+
7
+ class Reminder:
8
+ def __init__(
9
+ self,
10
+ method: str,
11
+ minutes_before_start: int = None,
12
+ days_before: int = None,
13
+ at: time = None
14
+ ):
15
+ """Represents base reminder object
16
+
17
+ Provide `minutes_before_start` to create "relative" reminder.
18
+ Provide `days_before` and `at` to create "absolute" reminder.
19
+
20
+ :param method:
21
+ Method of the reminder. Possible values: email or popup
22
+ :param minutes_before_start:
23
+ Minutes before reminder
24
+ :param days_before:
25
+ Days before reminder
26
+ :param at:
27
+ Specific time for a reminder
28
+ """
29
+ # Nothing was provided
30
+ if minutes_before_start is None and days_before is None and at is None:
31
+ raise ValueError("Relative reminder needs 'minutes_before_start'. "
32
+ "Absolute reminder 'days_before' and 'at' set. "
33
+ "None of them were provided.")
34
+
35
+ # Both minutes_before_start and days_before/at were provided
36
+ if minutes_before_start is not None and (days_before is not None or at is not None):
37
+ raise ValueError("Only minutes_before_start or days_before/at can be specified.")
38
+
39
+ # Only one of days_before and at was provided
40
+ if (days_before is None) != (at is None):
41
+ raise ValueError(f'Both "days_before" and "at" values need to be set '
42
+ f'when using absolute time for a reminder. '
43
+ f'Provided days_before={days_before} and at={at}.')
44
+
45
+ self.method = method
46
+ self.minutes_before_start = minutes_before_start
47
+ self.days_before = days_before
48
+ self.at = at
49
+
50
+ def __eq__(self, other):
51
+ return (
52
+ isinstance(other, Reminder)
53
+ and self.method == other.method
54
+ and self.minutes_before_start == other.minutes_before_start
55
+ and self.days_before == other.days_before
56
+ and self.at == other.at
57
+ )
58
+
59
+ def __str__(self):
60
+ if self.minutes_before_start is not None:
61
+ return '{} - minutes_before_start:{}'.format(self.__class__.__name__, self.minutes_before_start)
62
+ else:
63
+ return '{} - {} days before at {}'.format(self.__class__.__name__, self.days_before, self.at)
64
+
65
+ def __repr__(self):
66
+ return '<{}>'.format(self.__str__())
67
+
68
+ def convert_to_relative(self, start: Union[date, datetime, BeautifulDate]) -> 'Reminder':
69
+ """Converts absolute reminder (with set `days_before` and `at`) to relative (with set `minutes_before_start`)
70
+ relative to `start` date/datetime. Returns self if `minutes_before_start` already set.
71
+ """
72
+ if self.minutes_before_start is not None:
73
+ return self
74
+
75
+ tzinfo = start.tzinfo if isinstance(start, datetime) else None
76
+ start_of_the_day = datetime.combine(start, datetime.min.time(), tzinfo=tzinfo)
77
+
78
+ reminder_tzinfo = self.at.tzinfo or tzinfo
79
+ reminder_time = datetime.combine(start_of_the_day - self.days_before * days, self.at, tzinfo=reminder_tzinfo)
80
+
81
+ if isinstance(start, datetime):
82
+ minutes_before_start = int((start - reminder_time).total_seconds() / 60)
83
+ else:
84
+ minutes_before_start = int((start_of_the_day - reminder_time).total_seconds() / 60)
85
+
86
+ return Reminder(
87
+ method=self.method,
88
+ minutes_before_start=minutes_before_start
89
+ )
90
+
91
+
92
+ class EmailReminder(Reminder):
93
+ def __init__(
94
+ self,
95
+ minutes_before_start: int = None,
96
+ days_before: int = None,
97
+ at: time = None
98
+ ):
99
+ """Represents email reminder object
100
+
101
+ Provide `minutes_before_start` to create "relative" reminder.
102
+ Provide `days_before` and `at` to create "absolute" reminder.
103
+
104
+ :param minutes_before_start:
105
+ Minutes before reminder
106
+ :param days_before:
107
+ Days before reminder
108
+ :param at:
109
+ Specific time for a reminder
110
+ """
111
+ if not days_before and not at and not minutes_before_start:
112
+ minutes_before_start = 60
113
+ super().__init__('email', minutes_before_start, days_before, at)
114
+
115
+
116
+ class PopupReminder(Reminder):
117
+ def __init__(
118
+ self,
119
+ minutes_before_start: int = None,
120
+ days_before: int = None,
121
+ at: time = None
122
+ ):
123
+ """Represents popup reminder object
124
+
125
+ Provide `minutes_before_start` to create "relative" reminder.
126
+ Provide `days_before` and `at` to create "absolute" reminder.
127
+
128
+ :param minutes_before_start:
129
+ Minutes before reminder
130
+ :param days_before:
131
+ Days before reminder
132
+ :param at:
133
+ Specific time for a reminder
134
+ """
135
+ if not days_before and not at and not minutes_before_start:
136
+ minutes_before_start = 30
137
+ super().__init__('popup', minutes_before_start, days_before, at)