Upcoming events from contacts using FastAPI

8 min read

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.

What are we going to talk about?

What our application will look like

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:

Select a contact to get the upcoming events

With the selected contact, we will look for all the upcoming events where that contact is a participant:

Display all upcoming events coming from a contact

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.

No upcoming events

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:

$ pip3 install -r requirements.txt

This file was created using pipreqs:

$ pip3 install pipreqs
$ pip3 install pip-tools
$ pipreqs --savepath=requirements.in && pip-compile

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:

<!DOCTYPE html>
<html>
<head>
  <style>
	td {  
		vertical-align: top;
		text-align: center
	}
  </style>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- Call the TailwindCSS library -->  
  <script src="https://cdn.tailwindcss.com"></script>
  <title>Upcoming Events from Contacts</title>
</head>
<body>
{% block content %}  
{% endblock %}  
</body>
</html>

We use Tailwind CSS to make our webpage look better:

{% extends 'base.html' %}
{% block content %}
<div class="bg-[#FFFFFF] border-b p-4 m-4 rounded grid place-items-center">
	<form method="post">
	<table>
	<tr>
	<td>
	<select name="contacts" id="contacts" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
		{% for contact in contact_list -%}
			<option value={{contact["email"]}}>{{contact["full_name"]}}</option>
		{% endfor %}
    </select>
    </td>
    <td >
    <button type="submit" class="text-white bg-gray-50 hover:bg-blue-800 focus:ring-4 focus:bg-gray-50 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Submit</button>
    </td>
    </tr>
    </table>
    </form>
    {% if name %}
    <h2 class="text-4xl font-bold dark:text-black">Upcoming events with {{name}}</h2><br>
		{% for events in events_list -%}
			<div class="bg-[#eeeeee] border-green-600 border-b p-4 m-4 rounded grid">
				<h5 class="text-xl font-bold dark:text-black"><b>Title: </b>{{events["title"]}}</h5>
				<h5 class="text-xl font-bold dark:text-black"><b>Description: </b>{{events["description"]}}</h5>
				<h5 class="text-xl font-bold dark:text-black"><b>Date: </b>{{events["event_date"]}}</h5>
			</div>
		{% endfor %}	
	{% endif %}	
{% endblock %}

Here, we show the contacts list combo box. If we have pressed submit yet, the variable name is empty, so we don’t display anything else.

When the variable name is not empty, we loop the events and display them on the screen.

Running the Upcoming events from contacts FastAPI application

The good thing about this script is that we don’t need anything special to run the server. We just need to type this on the terminal window:

$ python3 upcoming_events.py
Running the FastAPI application

Our application will be running on port 8000.

Now we can get all the upcoming events from our contacts using FastAPI.

What’s next?

You can check the Github repo here [Coming soon].

To learn more about our Contacts APIs, visit our documentation Contacts API Overview.

To learn more about our Calendar APIs, visit our documentation Calendar API Overview.

You can sign up for Nylas for free and start building!

Don’t miss the action, join our LiveStream Coding with Nylas!

Related resources

How to build a CRM in 3 sprints with Nylas

What is a CRM? CRM stands for Customer Relationship Management, and it’s basically a way…

How to create an appointment scheduler in your React app

Learn how to create an appointment scheduler in your React app using Nylas Scheduler. Streamline bookings and UX with step-by-step guidance.

Beyond APIs: Designing elegant, smart Web Components

Dive into the world of smart Web Components for advanced API integration solutions. Design elegant, customizable elements to elevate user experiences.