Compare commits
5 Commits
830f024413
...
d26e6ecbc2
Author | SHA1 | Date |
---|---|---|
Remi Rampin | d26e6ecbc2 | |
Remi Rampin | 2c636d7ff3 | |
Remi Rampin | c1f8de26fc | |
Remi Rampin | cf40a87fe5 | |
Remi Rampin | b438294aba |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,164 @@
|
|||
import bisect
|
||||
from datetime import datetime, timedelta
|
||||
from flask import Flask, jsonify, make_response, redirect, request
|
||||
from google.auth.transport.requests import Request
|
||||
from google.oauth2.credentials import Credentials
|
||||
from googleapiclient.discovery import build
|
||||
import pytz
|
||||
|
||||
|
||||
# Monday to Friday
|
||||
WEEK_DAYS = {1, 2, 3, 4, 5}
|
||||
|
||||
# 9 to 5
|
||||
FROM = 9
|
||||
TO = 17
|
||||
|
||||
TIMEZONE_NAME = 'America/New_York'
|
||||
TIMEZONE = pytz.timezone(TIMEZONE_NAME)
|
||||
|
||||
AVAILABILITY_LIFETIME = timedelta(minutes=2)
|
||||
|
||||
SCOPES = ['https://www.googleapis.com/auth/calendar']
|
||||
|
||||
UTC = pytz.timezone('UTC')
|
||||
|
||||
creds = Credentials.from_authorized_user_file('creds.json', SCOPES)
|
||||
|
||||
|
||||
def read_datetime(dct):
|
||||
dt = datetime.fromisoformat(dct['dateTime']).replace(tzinfo=None)
|
||||
dt = pytz.timezone(dct['timeZone']).localize(dt)
|
||||
return dt
|
||||
|
||||
|
||||
def asiso(dt):
|
||||
return dt.isoformat()[:19]
|
||||
|
||||
|
||||
def asutciso(dt):
|
||||
return asiso(dt.astimezone(UTC)) + 'Z'
|
||||
|
||||
|
||||
def aslocal(dt):
|
||||
return dt.astimezone(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
def get_availability():
|
||||
if not creds.valid:
|
||||
creds.refresh(Request())
|
||||
service = build('calendar', 'v3', credentials=creds)
|
||||
|
||||
# Get events for the next 2 weeks
|
||||
now = datetime.utcnow()
|
||||
start = datetime(now.year, now.month, now.day)
|
||||
end = start + timedelta(days=14)
|
||||
events_result = service.events().list(
|
||||
calendarId='primary',
|
||||
timeMin=asutciso(now),
|
||||
timeMax=asutciso(end),
|
||||
maxResults=200,
|
||||
singleEvents=True,
|
||||
orderBy='startTime',
|
||||
).execute()
|
||||
|
||||
# Build a list of possible time slots for the next 2 weeks
|
||||
availability = []
|
||||
for day in range(14):
|
||||
day = start + timedelta(days=day)
|
||||
if day.weekday() not in WEEK_DAYS:
|
||||
continue
|
||||
for hour in range(FROM, TO):
|
||||
for minute in (0, 30):
|
||||
availability.append(TIMEZONE.localize(
|
||||
day.replace(hour=hour, minute=minute),
|
||||
))
|
||||
|
||||
for event in events_result.get('items', []):
|
||||
event_start = read_datetime(event['start'])
|
||||
event_end = read_datetime(event['end'])
|
||||
|
||||
# Find the first slot that starts less than 30min before this event
|
||||
first_idx = bisect.bisect_right(
|
||||
availability,
|
||||
event_start - timedelta(minutes=30),
|
||||
)
|
||||
|
||||
# Find the first slot that starts after this event ends
|
||||
last_idx = bisect.bisect_left(
|
||||
availability,
|
||||
event_end,
|
||||
)
|
||||
|
||||
# Remove the slots from the availability
|
||||
del availability[first_idx:last_idx]
|
||||
|
||||
return availability
|
||||
|
||||
|
||||
availability = get_availability()
|
||||
availability_as_of_date = datetime.utcnow()
|
||||
|
||||
|
||||
app = Flask('calendar_booking_api')
|
||||
|
||||
|
||||
@app.route('/availability')
|
||||
def api_availability():
|
||||
global availability, availability_as_of_date
|
||||
if (
|
||||
availability_as_of_date is None
|
||||
or availability_as_of_date + AVAILABILITY_LIFETIME < datetime.utcnow()
|
||||
):
|
||||
availability = get_availability()
|
||||
availability_as_of_date = datetime.utcnow()
|
||||
|
||||
return jsonify({'availability': [asutciso(time) for time in availability]})
|
||||
|
||||
|
||||
@app.route('/book', methods=['POST', 'OPTIONS'])
|
||||
def api_book():
|
||||
# Allow cross-origin
|
||||
if request.method == 'OPTIONS':
|
||||
response = make_response()
|
||||
response.headers.add(
|
||||
'Access-Control-Allow-Origin',
|
||||
'https://vicky.rampin.org',
|
||||
)
|
||||
response.headers.add(
|
||||
'Access-Control-Allow-Headers',
|
||||
'Content-Type',
|
||||
)
|
||||
response.headers.add(
|
||||
'Access-Control-Allow-Methods',
|
||||
'POST',
|
||||
)
|
||||
return response
|
||||
|
||||
if not creds.valid:
|
||||
creds.refresh(Request())
|
||||
service = build('calendar', 'v3', credentials=creds)
|
||||
|
||||
full_name = request.form['name']
|
||||
email = request.form['email']
|
||||
topic = request.form['topic']
|
||||
date = request.form['date']
|
||||
|
||||
end = start + timedelta(minutes=30)
|
||||
|
||||
service.events().insert(
|
||||
calendarId='primary',
|
||||
body=dict(
|
||||
start={'dateTime': asiso(start), 'timeZone': TIMEZONE_NAME},
|
||||
end={'dateTime': asiso(end), 'timeZone': TIMEZONE_NAME},
|
||||
summary="Meeting with Vicky",
|
||||
description="Meeting scheduled from the web",
|
||||
),
|
||||
)
|
||||
|
||||
response = redirect('https://vicky.rampin.org/book-successful', 303)
|
||||
response.headers.add(
|
||||
'Access-Control-Allow-Origin',
|
||||
'https://vicky.rampin.org',
|
||||
)
|
||||
return response
|
23
form.html
23
form.html
|
@ -2,40 +2,40 @@
|
|||
<head>
|
||||
<title>Meet with Vicky Rampin</title>
|
||||
<meta content="">
|
||||
<link rel="stylesheet" href="bootstrap.css">
|
||||
<link rel="stylesheet" href=".bootstrap/bootstrap.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container px-5 my-5">
|
||||
<div class="container px-5 my-5">
|
||||
<form id="contactForm" data-sb-form-api-token="API_TOKEN">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="fullName">Full name</label>
|
||||
<input class="form-control" id="fullName" type="text" placeholder="Full name" data-sb-validations="required" />
|
||||
<label class="form-label" for="name">Full name</label>
|
||||
<input class="form-control" id="name" name="name" type="text" placeholder="Full name" data-sb-validations="required" />
|
||||
<div class="invalid-feedback" data-sb-feedback="fullName:required">Full name is required.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="emailAddress">Email address</label>
|
||||
<input class="form-control" id="emailAddress" type="email" placeholder="Email address" data-sb-validations="required,email" />
|
||||
<label class="form-label" for="email">Email address</label>
|
||||
<input class="form-control" id="email" name="email" type="email" placeholder="Email address" data-sb-validations="required,email" />
|
||||
<div class="invalid-feedback" data-sb-feedback="emailAddress:required">Email address is required.</div>
|
||||
<div class="invalid-feedback" data-sb-feedback="emailAddress:email">Email address Email is not valid.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="whatWouldYouLikeToDiscuss">What would you like to discuss?</label>
|
||||
<textarea class="form-control" id="whatWouldYouLikeToDiscuss" type="text" placeholder="What would you like to discuss?" style="height: 10rem;" data-sb-validations="required"></textarea>
|
||||
<label class="form-label" for="topic">What would you like to discuss?</label>
|
||||
<textarea class="form-control" id="topic" name="topic" placeholder="What would you like to discuss?" style="height: 10rem;" data-sb-validations="required"></textarea>
|
||||
<div class="invalid-feedback" data-sb-feedback="whatWouldYouLikeToDiscuss:required">What would you like to discuss? is required.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label d-block">Which day and time works best for us to meet on Zoom?</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" id="optionA" type="radio" name="whichDayAndTimeWorksBestForUsToMeetOnZoom" data-sb-validations="" />
|
||||
<input class="form-check-input" id="optionA" type="radio" name="date" data-sb-validations="" />
|
||||
<label class="form-check-label" for="optionA">option A</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" id="optionB" type="radio" name="whichDayAndTimeWorksBestForUsToMeetOnZoom" data-sb-validations="" />
|
||||
<input class="form-check-input" id="optionB" type="radio" name="date" data-sb-validations="" />
|
||||
<label class="form-check-label" for="optionB">option B</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" id="optionC" type="radio" name="whichDayAndTimeWorksBestForUsToMeetOnZoom" data-sb-validations="" />
|
||||
<input class="form-check-input" id="optionC" type="radio" name="date" data-sb-validations="" />
|
||||
<label class="form-check-label" for="optionC">option C</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -54,7 +54,6 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="https://cdn.startbootstrap.com/sb-forms-latest.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
|
|
@ -66,11 +66,22 @@ python-versions = ">=3.5.0"
|
|||
[package.extras]
|
||||
unicode_backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.4"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
|
@ -106,6 +117,25 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "2.1.2"
|
||||
description = "A simple framework for building complex web applications."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0"
|
||||
importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
|
||||
itsdangerous = ">=2.0"
|
||||
Jinja2 = ">=3.0"
|
||||
Werkzeug = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
async = ["asgiref (>=3.2)"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-core"
|
||||
version = "2.7.3"
|
||||
|
@ -220,6 +250,22 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "4.11.3"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
|
||||
perf = ["ipython"]
|
||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "ipykernel"
|
||||
version = "6.13.0"
|
||||
|
@ -278,6 +324,14 @@ qtconsole = ["qtconsole"]
|
|||
test = ["pytest (<7.1)", "pytest-asyncio", "testpath"]
|
||||
test_extra = ["pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "trio"]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.1.2"
|
||||
description = "Safely pass data to untrusted environments and back."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.18.1"
|
||||
|
@ -293,6 +347,20 @@ parso = ">=0.8.0,<0.9.0"
|
|||
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
|
||||
testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.2"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-client"
|
||||
version = "7.3.1"
|
||||
|
@ -329,6 +397,14 @@ traitlets = "*"
|
|||
[package.extras]
|
||||
test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib-inline"
|
||||
version = "0.1.3"
|
||||
|
@ -517,6 +593,14 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
|||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2022.1"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "304"
|
||||
|
@ -653,10 +737,33 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "2.1.2"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
watchdog = ["watchdog"]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.8.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
|
||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "83be0a029b324415badfd26479b415852946c927f43f7f677308f7135f7c8596"
|
||||
content-hash = "9f4cc03d98c0330f2704b5d44ef2354b0c9c91532b16ffc0aed0bd99ee7b4cc2"
|
||||
|
||||
[metadata.files]
|
||||
appnope = [
|
||||
|
@ -735,6 +842,10 @@ charset-normalizer = [
|
|||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||
|
@ -771,6 +882,10 @@ executing = [
|
|||
{file = "executing-0.8.3-py2.py3-none-any.whl", hash = "sha256:d1eef132db1b83649a3905ca6dd8897f71ac6f8cac79a7e58a1a09cf137546c9"},
|
||||
{file = "executing-0.8.3.tar.gz", hash = "sha256:c6554e21c6b060590a6d3be4b82fb78f8f0194d809de5ea7df1c093763311501"},
|
||||
]
|
||||
flask = [
|
||||
{file = "Flask-2.1.2-py3-none-any.whl", hash = "sha256:fad5b446feb0d6db6aec0c3184d16a8c1f6c3e464b511649c8918a9be100b4fe"},
|
||||
{file = "Flask-2.1.2.tar.gz", hash = "sha256:315ded2ddf8a6281567edb27393010fe3406188bafbfe65a3339d5787d89e477"},
|
||||
]
|
||||
google-api-core = [
|
||||
{file = "google-api-core-2.7.3.tar.gz", hash = "sha256:17957f0704cbe95bd2ce25019efd2046423978594d181d4263e5dcffd2dbbc79"},
|
||||
{file = "google_api_core-2.7.3-py3-none-any.whl", hash = "sha256:bb40d2d6412c77c367943b88d08323091d724deaea96892fff4a1ebd06f6e5be"},
|
||||
|
@ -803,6 +918,10 @@ idna = [
|
|||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"},
|
||||
{file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"},
|
||||
]
|
||||
ipykernel = [
|
||||
{file = "ipykernel-6.13.0-py3-none-any.whl", hash = "sha256:2b0987af43c0d4b62cecb13c592755f599f96f29aafe36c01731aaa96df30d39"},
|
||||
{file = "ipykernel-6.13.0.tar.gz", hash = "sha256:0e28273e290858393e86e152b104e5506a79c13d25b951ac6eca220051b4be60"},
|
||||
|
@ -811,10 +930,18 @@ ipython = [
|
|||
{file = "ipython-8.3.0-py3-none-any.whl", hash = "sha256:341456643a764c28f670409bbd5d2518f9b82c013441084ff2c2fc999698f83b"},
|
||||
{file = "ipython-8.3.0.tar.gz", hash = "sha256:807ae3cf43b84693c9272f70368440a9a7eaa2e7e6882dad943c32fbf7e51402"},
|
||||
]
|
||||
itsdangerous = [
|
||||
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
|
||||
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
|
||||
]
|
||||
jedi = [
|
||||
{file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
|
||||
{file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||
]
|
||||
jupyter-client = [
|
||||
{file = "jupyter_client-7.3.1-py3-none-any.whl", hash = "sha256:404abe552540aff3527e66e16beb114b6b4ff58479d51a301f4eb9701e4f52ef"},
|
||||
{file = "jupyter_client-7.3.1.tar.gz", hash = "sha256:05d4ff6a0ade25138c6bb0fbeac7ddc26b5fe835e7dd816b64b4a45b931bdc0b"},
|
||||
|
@ -823,6 +950,48 @@ jupyter-core = [
|
|||
{file = "jupyter_core-4.10.0-py3-none-any.whl", hash = "sha256:e7f5212177af7ab34179690140f188aa9bf3d322d8155ed972cbded19f55b6f3"},
|
||||
{file = "jupyter_core-4.10.0.tar.gz", hash = "sha256:a6de44b16b7b31d7271130c71a6792c4040f077011961138afed5e5e73181aec"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
|
||||
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
|
||||
]
|
||||
matplotlib-inline = [
|
||||
{file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"},
|
||||
{file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"},
|
||||
|
@ -973,6 +1142,10 @@ python-dateutil = [
|
|||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||
]
|
||||
pytz = [
|
||||
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
|
||||
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
|
||||
]
|
||||
pywin32 = [
|
||||
{file = "pywin32-304-cp310-cp310-win32.whl", hash = "sha256:3c7bacf5e24298c86314f03fa20e16558a4e4138fc34615d7de4070c23e65af3"},
|
||||
{file = "pywin32-304-cp310-cp310-win_amd64.whl", hash = "sha256:4f32145913a2447736dad62495199a8e280a77a0ca662daa2332acf849f0be48"},
|
||||
|
@ -1117,3 +1290,11 @@ wcwidth = [
|
|||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
]
|
||||
werkzeug = [
|
||||
{file = "Werkzeug-2.1.2-py3-none-any.whl", hash = "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"},
|
||||
{file = "Werkzeug-2.1.2.tar.gz", hash = "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"},
|
||||
{file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
|
||||
]
|
||||
|
|
|
@ -9,6 +9,8 @@ python = "^3.8"
|
|||
google-api-python-client = ">=2,<3"
|
||||
google-auth-httplib2 = ">=0.1,<0.2"
|
||||
google-auth-oauthlib = ">=0.5,<0.6"
|
||||
Flask = ">=2,<3"
|
||||
pytz = "*"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
ipykernel = ">=6,<7"
|
||||
|
|
Loading…
Reference in New Issue