In this tech world, scheduling meetings by agreeing on a common date and time is common. However, we don’t always have access to our calendars or we assume we have an available spot, which makes the meeting invite go back and forth until the meeting is finally decided. While here at Nylas we provide a powerful scheduler, it’s always a good thing to be able to create our version, and for that, we will use FastAPI, a powerful and blazing-fast Python framework. This application will read upcoming events from contacts using FastAPI.
Is your system ready?
If you already have the Nylas Python SDK installed and your Python environment configured, skip to the next section.
If this is your first time working with the Nylas SDK, I recommend reading the post How to Send Emails with the Nylas Python SDK, where I explain how to set up a basic environment.
When we run our application, we will we’re be presented with a list of our contacts. We need to choose one and then press submit:
With the selected contact, we will look for all the upcoming events where that contact is a participant:
Nothing will be displayed if we choose a contact that doesn’t have upcoming events. And by upcoming events, we mean events that haven’t happened yet. The ones that happened already will not be taken into consideration.
Installing the dependencies
Unlike Flask, FastAPI doesn’t have a bundled server, so we need to install one. To make things easier, use the following requirement.txt file:
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile
#
annotated-types==0.6.0
# via pydantic
anyio==3.7.1
# via
# fastapi
# starlette
beautifulsoup4==4.12.2
# via -r requirements.in
certifi==2023.7.22
# via requests
charset-normalizer==3.3.0
# via requests
click==8.1.7
# via uvicorn
exceptiongroup==1.1.3
# via anyio
fastapi==0.103.2
# via -r requirements.in
h11==0.14.0
# via uvicorn
idna==3.4
# via
# anyio
# requests
nylas==5.14.1
# via -r requirements.in
pendulum==2.1.2
# via -r requirements.in
pydantic==2.4.2
# via fastapi
pydantic-core==2.10.1
# via pydantic
python-dateutil==2.8.2
# via pendulum
python-dotenv==1.0.0
# via -r requirements.in
pytzdata==2020.1
# via pendulum
requests[security]==2.31.0
# via nylas
six==1.16.0
# via
# nylas
# python-dateutil
# websocket-client
sniffio==1.3.0
# via anyio
soupsieve==2.5
# via beautifulsoup4
starlette==0.27.0
# via fastapi
typing-extensions==4.8.0
# via
# fastapi
# pydantic
# pydantic-core
# uvicorn
urllib3==2.0.6
# via requests
urlobject==2.4.3
# via nylas
uvicorn==0.23.2
# via -r requirements.in
websocket-client==0.59.0
# via nylas
Then run the following command on the terminal window:
Creating the Upcoming events from contacts FastAPI application
We’re going to create a folder called Upcoming_Events_from_Contacts and inside create a folder called templates.
On the root folder, we’re going to create a file called upcoming_events.py with the following code:
# Import your dependencies
from dotenv import load_dotenv
import os
import uvicorn
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from typing import Annotated
import pendulum
from bs4 import BeautifulSoup
from nylas import APIClient # type: ignore
# Create a FastAPI application
app = FastAPI()
# Load your env variables
load_dotenv()
# Initialize an instance of the Nylas SDK using the client credentials
nylas = APIClient(
os.environ.get("CLIENT_ID"),
os.environ.get("CLIENT_SECRET"),
os.environ.get("ACCESS_TOKEN")
)
# List to hold all contacts
contact_list = []
# List to hold all events
events_list = []
def get_contacts():
# Grab the first 5 contacts from the specified group
contacts = nylas.contacts.where(source = 'address_book',
group = "517v55haghlcvnuu7lcm4f7k8")
# Loop all contacts and get the Email and Full Name
for contact in contacts:
contact_list.append({'email': list(contact.emails.values())[0][0] ,
'full_name' : contact.given_name + " " + contact.surname})
def upcoming_events(email: str):
# Get today’s date
today = pendulum.now()
# Not all events have a description
description = "No description"
# Get the time for today at the current time
after_time = pendulum.local(today.year, today.month, today.day, today.hour, 0, 0).int_timestamp
# Get all events where the contact is a participant
events = nylas.events.where(calendar_id=os.environ.get("CALENDAR_ID"),
starts_after=after_time, participants = email)
# Remove all events from the list
events_list.clear()
# Loop the events
for event in events:
# If the event has a description
if event.description is not None:
# Get rid of all the html tags
description = BeautifulSoup(event.description,features="html.parser").text
# Determine the type of date event
match event.when["object"]:
case "timespan":
start_date = pendulum.from_timestamp(event.when["start_time"],
today.timezone.name).strftime("%m/%d/%Y at %H:%M")
end_date = pendulum.from_timestamp(event.when["end_time"],
today.timezone.name).strftime("%m/%d/%Y at %H:%M")
event_date = f"from {start_date} to {end_date}"
case "datespan":
start_date = pendulum.from_timestamp(event.when["start_time"],
today.timezone.name).strftime("%m/%d/%Y")
end_date = pendulum.from_timestamp(event.when["end_time"],
today.timezone.name).strftime("%m/%d/%Y")
event_date = f"from {start_date} to {end_date}"
case "date":
event_date = pendulum.from_timestamp(event.when["date"],
today.timezone.name).strftime("%m/%d/%Y")
# Add the title, description and event date to the event
events_list.append({'title': event.title, 'description': description, 'event_date': event_date})
# Return the list of events
return events_list
# Load the templates folder
templates = Jinja2Templates(directory="templates")
# Call the main page
@app.get("/", response_class=HTMLResponse)
async def get_events(request: Request):
# Load contacts
get_contacts()
# Call the main.html template passing the list of contacts
return templates.TemplateResponse("main.html", {"request": request, "contact_list": contact_list})
# When we press the submit button
@app.post("/")
# Read the form parameter
async def post_events(request: Request, contacts: Annotated[str, Form()]):
name = ""
# Get a list of the upcoming events
events_list = upcoming_events(contacts)
# Loop the contacts
for contact in contact_list:
# Get the full name for the selected email
if contact.get("email") == contacts:
name = contact.get("full_name")
exit
# Call the main.html template passing the list of contacts, contact name and the list of events
return templates.TemplateResponse("main.html", {"request": request, "contact_list": contact_list,
"name":name, "events_list": events_list})
if __name__ == "__main__":
# Use the uvicorn server to run the application
uvicorn.run("upcoming_events:app")
Now, inside the templates, create the files base.html and main.html: