API 4-4: REST APIs#
Hosting your own APIs with FastAPI
API vs Module#
You might be asking why host an API versus just writing a module? After all they have similar functionality of sharing and interacting with code and data.
def do_something_sensitive():
# do something sensitive
# if I import this, I can see this code
return "sensitive data"
VS
/api/do_something_sensitive
There are some key differences between an API and a module:
You can hide the implementation details of your code with an API. Unlike a module, people only see the interface, not way to code was implemented.
An API can be accessed over the internet, while a module is typically used locally.
API’s work across different programming languages, while modules are restricted to the language they were written in (Python).
Cede is hosted as a web service, meaning it can be accessed from anywhere, anytime with an internet connection.
Why API?#
Break up complex applications into smaller, more manageable pieces
Allow different parts of an application to be developed independently
Allow different parts of an application to be developed in different languages
Fast API#
Fast API is a easy to learn Python framework for building API’s.
It has a lot of features that make it a great choice for building API’s, but
we will focus keeping it simple so that you can understand the benefits of building an API.
Fast API Features#
Live edit similar to Streamlit. You can edit your code and see the changes in real time.
Highly opinionated. It has a lot of features that are built in but you must do it the “FastAPI way.”
Easy to understand the code.
Auto-generates API documentation Swagger UI.
1# fasthello.py
2
3from fastapi import FastAPI
4
5app = FastAPI() # Create a FastAPI instance
6
7@app.get("/") # Define a route
8def root(): # Define a function that will be called when the route is requested
9 return {"message": "Hello World"} # Serializes to JSON automatically
10
11
12## http://localhost:8000/docs
Query String Path, and Header Parameters#
Fast API makes it easy to add parameters to your API.
Python type hints are used to define the type of the parameter (int, str, etc.)
The
Query()function is used to define a query parameterThe
Header()function is used to define a header parameterPath parameters are defined by including the parameter in the URL path
1# fastparams.py
2
3from fastapi import FastAPI, Header, Query
4
5app = FastAPI()
6
7@app.get("/calculator/{operator}") # <== path parameter
8def read_item(operator: str,
9 a: str = Query(), # <== query parameter
10 b: int = Query(), # <== query parameter
11 h: str = Header()): # <== header parameter
12 if operator == "add":
13 result = a + b
14 elif operator == "sub":
15 result = a - b
16 elif operator == "mul":
17 result = a * b
18 elif operator == "div":
19 result = a / b
20 return {
21 "operator": operator,
22 "a": a,
23 "b": b,
24 "result": result,
25 "h": h
26 }
Challenge 4-4-1#
Design and build an API to search for flights (depart/arrive) by Airport Code.
Use this dataset to get the source of your flights:
https://raw.githubusercontent.com/mafudge/datasets/refs/heads/master/flights/sample-flights.csv
Here’s some examples of the the API endpoint you need to build:
/api/flights/search?type=dep&code=OKA
/api/flights/search?type=arr&code=KEY
Test your API using the Swagger UI.
Handling Errors#
FastAPI makes it easy to handle errors. Use the HTTPException class to raise an error with a specific status code and message.
Remember status codes are a way to communicate the status of a request to the client. They are a design contract which means you SHOULD use them as they are EXPECTED, but they are not REQURED.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
1# fastparams2.py
2
3from fastapi import FastAPI, Header, Query, HTTPException
4
5app = FastAPI()
6
7@app.get("/calculator/{operator}") # <== path parameter
8def read_item(operator: str,
9 a: str = Query(), # <== query parameter
10 b: int = Query(), # <== query parameter
11 h: str = Header()): # <== header parameter
12 if operator == "add":
13 result = a + b
14 elif operator == "sub":
15 result = a - b
16 elif operator == "mul":
17 result = a * b
18 elif operator == "div":
19 result = a / b
20 else:
21 raise HTTPException(status_code=404, detail="Operator not found. should be: add, sub, mul, div")
22
23 return {
24 "operator": operator,
25 "a": a,
26 "b": b,
27 "result": result,
28 "h": h
29 }
Sending data in the request body#
Use the
Body()function to define a request body parameterThese are added to POST/PUT requests in JSON format.
1# fastbody.py
2
3from fastapi import FastAPI, Body()
4
5app = FastAPI()
6
7friends = []
8
9@app.post("/friends")
10def create_friend(name: str = Body(), age: int = Body()):
11 '''
12 Add a new friend to the list of friends and return the list each time a new friend is added
13 '''
14 friend = {"name": name, "age": age}
15 friends.append(friend)
16 return friends
API Wrappers#
A common use case for API’s is to wrap an existing API to make it easier to use. The IoT Portal is a good example of this.
API Wrappers are common with AI/ML models. We want to control access and restrict behavior of the AI and the best way to do that is wrap a public AI API in your own API.
Challenge 4-4-2#
TLDR (Too Long Didn’t Read) bot.
Let’s design and build an API to wrap the OpenAI API in the IoT portal. We will build a tldr bot that summarizes a body of text in a short / simple way.
Things to figure out:
What should the endpoint name be?
Method (GET, POST, PUT, DELETE)?
What parameters should be passed?
What should the response look like?
What does the OpenAI API prompt look like?
What is the system prompt?
Few shots?
NOTE:
Building a CRUD API#
CRUD stands for Create, Read, Update, Delete
Its a common pattern for building non-public data APIs
We use the HTTP methods POST (Create), GET (Read), PUT (Update), DELETE to perform these operations
NOTE: to perform CRUD, we need a business key or natural key to identify the item.
In the example below, we use the name of our friend. Not the best, but workable for our example.
The best practice for API design is to return the object that was created, updated, or deleted. This allows the client to know the state of the object after the operation.
See fastcrud.py for an example of a CRUD API.
TinyDb - Persistent CRUD Storage#
TinyDB is a lightweight document oriented database optimized for your happiness. It’s written in pure Python and has no external dependencies.
It gives you CRUD over a JSON file.
https://tinydb.readthedocs.io/en/latest/
1from tinydb import TinyDB, Query
2
3# Initialize TinyDB
4db = TinyDB('db.json')
5db.truncate() #start over
6
7
8# Create documents
9print("CREATE Alice, Bob, Charlie")
10db.insert({'name': 'Alice', 'age': 25, 'hometown': 'Seattle'})
11db.insert({'name': 'Bob', 'age': 22, 'hometown': 'Portland'})
12db.insert({'name': 'Charlie', 'age': 30, 'hometown': 'San Francisco'})
13
14# get all people
15print("PEOPLE", db.all())
16
17# update bob
18Person = Query()
19print("UPDATE Bob")
20db.update({'age': 23, 'hometown' : 'New York'}, Person.name == 'Bob')
21print("PEOPLE", db.all())
22
23# delete alice
24print("DELETE Alice")
25db.remove(Person.name == 'Alice')
26print("PEOPLE", db.all())
27
28# loop
29print("PEOPLE")
30for person in db:
31 print(person['name'], person['age'], person['hometown'])
CREATE Alice, Bob, Charlie
PEOPLE [{'name': 'Alice', 'age': 25, 'hometown': 'Seattle'}, {'name': 'Bob', 'age': 22, 'hometown': 'Portland'}, {'name': 'Charlie', 'age': 30, 'hometown': 'San Francisco'}]
UPDATE Bob
PEOPLE [{'name': 'Alice', 'age': 25, 'hometown': 'Seattle'}, {'name': 'Bob', 'age': 23, 'hometown': 'New York'}, {'name': 'Charlie', 'age': 30, 'hometown': 'San Francisco'}]
DELETE Alice
PEOPLE [{'name': 'Bob', 'age': 23, 'hometown': 'New York'}, {'name': 'Charlie', 'age': 30, 'hometown': 'San Francisco'}]
PEOPLE
Bob 23 New York
Charlie 30 San Francisco
Challenge 4-4-3#
Putting it all together so far.
Take your TLDR bot from 4-4-2 and build a UI for it in streamlit. This can be a simple question / answer user interface. You do not need to make it conversational.