%pip install fastapi uvicorn pyngrok
API with FastAPI
Picture this - You’ve spent endless hours meticulously gathering data, performing feature engineering, tuning hyperparameters, and training a machine learning model. This model has surpassed your expectations, delivering excellent results and insightful predictions. You’re thrilled, filled with a sense of achievement, and now you’re eager to share this AI marvel with the world. But there’s a catch - how do you make this trained model accessible to others? How do you turn this static, local file into a dynamic and interactive service?
This is where APIs (Application Programming Interfaces) and FastAPI come to the rescue!
APIs: The Heroes of Connectivity
An API is essentially a set of rules that specify how different software applications should interact and communicate. APIs discreetly expose the functions of an application in a secure way, making them available for other applications to utilize. They are like diplomats, fostering relations and enabling seamless data transfer between different software systems.
API flow (sumber www.piworks.net)
FastAPI: The Speedy Python-to-Web Translator
Now, let’s talk about FastAPI. FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. This handy tool is your go-to solution for setting up a simple yet powerful API. It enables you to map Python functions to specific URLs, transforming these operations into network-accessible services.
On Your Marks, Get Set, FastAPI!
Excited? Let’s dive right into crafting APIs with FastAPI! Before we set sail, ensure FastAPI is installed. You can do this using pip:
Igniting the FastAPI
Let’s kick-start our FastAPI adventure by initiating a basic application:
%%writefile main.py
from fastapi import FastAPI
= FastAPI()
app
@app.get("/")
def read_root():
return {"Welcome": "Welcome to our API!"}
Overwriting main.py
The cell above will create a main.py
file (or overwrite it if it already exists) according to the code in that cell but using the magic command %%writefile main.py
Now we can run main.py
using Uvicorn. What is Uvicorn? 🤔 This is a very fast ASGI server implementation, used to serve your FastAPI applications. ASGI, short for Asynchronous Server Gateway Interface, is a standard interface between web servers, frameworks, and async-enabled Python applications. Uvicorn serves as a bridge between your FastAPI applications and the outside world.
Use the command below in your terminal to run the application
uvicorn main:app --reload
Note: make sure the directory position of your terminal is the same as the
main.py
file
Congratulations! You have just kindled the first sparks of your FastAPI application. With just a few lines of code, you’ve transformed a simple python function into a full-fledged API service.
But how do we know if it’s working? 🤔 You can try with curl
:
What if we want to run it in the Jupyter Notebook cell? 🤔 Let’s send a request to our new application and see what happens.
import uvicorn
import nest_asyncio
apply()
nest_asyncio.
# Start server synchronous
='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True) uvicorn.run(app
We’ve started our FastAPI application, and are now waiting for incoming requests.
We can use curl again to try it out:
So far, so good. Our API is up, running and has successfully responded to our request. But let’s consider a scenario. What if you want to share your newly built API with your colleagues or deploy it for a demo. Running the API on localhost won’t be visible to others on the internet.
This is where Ngrok comes into play. Ngrok is a fantastic tool that creates a secure tunnel from a public endpoint to your localhost. But before we can utilize it, we need a token. Let’s see how we can get this token:
- Visit the Ngrok website. The first step is to visit Ngrok’s official website.
- Create an account. Once you’re on the website, you need to create an account. If you already have one, you can simply log in.
- Verify your account. After signing up, you will receive a verification email. Click on the link in that email to verify your account.
- Log in to your account. Once your account is verified, log in to your Ngrok account.
- Get your token. After logging in, you will be directed to the dashboard. Here, you can find your Auth token under the ‘Auth’ section.
Let’s illustrate the final step with an image:
Remember to keep your token secure, as anyone with access to it can create a tunnel to your localhost.
Enter the token you have obtained
= "" # @param {type:"string"} your_ngrok_token
With the ngrok token in our hands, we are now ready to expose our local API to the world. Let’s set it up and give it a shot!
Try api using pyngrok
from pyngrok import ngrok
import uvicorn
# Setup ngrok
ngrok.set_auth_token(your_ngrok_token)
= ngrok.connect(addr="8000", proto="http", bind_tls=True)
public_url print(f"Public URL: {public_url}")
# Start server
='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True) uvicorn.run(app
Now we’re ready to share our API with the world! We’ve set up a secure tunnel using ngrok, and our API is now accessible from anywhere. You can easily share the public URL with others, and they can interact with your API in real-time.
Access the api endpoint via ngrok
(click Visit Site)
Here’s a piece of advice! While ngrok is a fantastic tool, remember that the free version of ngrok includes certain limitations. It provides a random URL every time you start a new session, and the connections can occasionally be slow. For extensive use, consider opting for a paid plan.
Look at that! You’ve just given birth to your first API using FastAPI and made it accessible to the world through ngrok. This API now functions with a single endpoint at the root URL (‘/’) which offers a friendly welcome message. Isn’t it thrilling to witness your Python functions communicating with the world? This is just the beginning of your FastAPI journey. As you dive deeper, you’ll uncover its power to build more complex and powerful APIs. Happy exploring!
But before we wrap up, don’t forget to kill the server process when done!
To do so, you need to find the process ID (PID) and kill it - the method varies depending on the operating system.
- On Windows, you can use
netstat -ano | findstr :<YourPort>
to find the PID andtaskkill /PID <PID> /F
to kill the process. - On Linux/Mac,
lsof -i :<YourPort>
andkill -9 <PID>
should do the trick.
API Endpoints & HTTP Methods: A Deeper Dive!
Imagine you’re building an online store. You need to provide your users with a way to view products, add items to their shopping cart, and make purchases. This is where FastAPI’s ability to create multiple API endpoints and utilize different HTTP methods comes into play.
%%writefile main.py
from fastapi import FastAPI
= FastAPI()
app
@app.get("/products")
def get_products():
return {"message": "Display all products"}
@app.post("/cart/{item_id}")
def add_to_cart(item_id: int):
return {"message": f"Added item with id {item_id} to the cart"}
@app.delete("/cart/{item_id}")
def remove_from_cart(item_id: int):
return {"message": f"Removed item with id {item_id} from the cart"}
Overwriting main.py
Running localhost and ngrok:
from pyngrok import ngrok
import uvicorn
# Setup ngrok
ngrok.set_auth_token(your_ngrok_token)
= ngrok.connect(addr="8000", proto="http", bind_tls=True)
public_url print(f"Public URL: {public_url}")
# Start server
='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True) uvicorn.run(app
Now to carry out tests, in Fast API there is an Interactive API docs feature where we can see detailed information about the API that we created and make test requests directly there.
Now go to http://127.0.0.1:8000/docs or https://{ngrok-url}/docs
You will see the automatic interactive API documentation (provided by Swagger UI):
Or you can use other alternatives is http://127.0.0.1:8000/redoc or https://{ngrok-url}/redoc
You will see the alternative automatic documentation (provided by ReDoc):
OK now, we will revise the previous code and add in-memory data structures for products
and cart
to make it more interactive:
%%writefile main.py
from fastapi import FastAPI, HTTPException
= FastAPI()
app
# Our product list - a dictionary where key is product id and value is product name
= {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}
products
# The shopping cart - a dictionary where key is product id and value is quantity
= {}
cart
@app.get("/products")
def get_products():
return products
@app.post("/cart/{product_id}")
def add_to_cart(product_id: int):
if product_id not in products:
raise HTTPException(status_code=404, detail="Product not found")
if product_id in cart:
+= 1
cart[product_id] else:
= 1
cart[product_id] return {"message": f"Added {products[product_id]} to the cart"}
@app.delete("/cart/{product_id}")
def remove_from_cart(product_id: int):
if product_id not in cart:
raise HTTPException(status_code=404, detail="Product not in cart")
-= 1
cart[product_id] if cart[product_id] == 0:
del cart[product_id]
return {"message": f"Removed {products[product_id]} from the cart"}
@app.get("/cart")
def get_cart():
return {products[product_id]: quantity for product_id, quantity in cart.items()}
Running localhost and ngrok:
from pyngrok import ngrok
import uvicorn
# Setup ngrok
ngrok.set_auth_token(your_ngrok_token)
= ngrok.connect(addr="8000", proto="http", bind_tls=True)
public_url print(f"Public URL: {public_url}")
# Start server
='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True) uvicorn.run(app
Now, our API server can handle the following requests: - GET
requests to /products
to view all products. - POST
requests to /cart/{product_id}
to add products to the cart. - DELETE
requests to /cart/{product_id}
to remove products from the cart. - GET
requests to /cart
to view the cart contents.
Go to http://127.0.0.1:8000/docs, https://{ngrok-url}/docs or http://127.0.0.1:8000/redoc or https://{ngrok-url}/redoc for interactive documentation your API
Working with JSON Responses and Requests
All the data exchanged between the client and server in our online store scenario happens in the form of JSON. FastAPI makes it ultra-easy to handle JSON data.
Let’s say, instead of adding one product at a time to the cart, we want to allow customers to add multiple products at once. They can send a list of product IDs as JSON data in a POST request, and FastAPI will automatically convert it to a Python list for us.
%%writefile main.py
from typing import List
from fastapi import FastAPI, HTTPException
= FastAPI()
app
# Our product list - a dictionary where key is product id and value is product name
= {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}
products
# The shopping cart - a dictionary where key is product id and value is quantity
= {}
cart
@app.post("/cart/items")
async def add_items_to_cart(item_ids: List[int]):
= [item_id for item_id in item_ids if item_id not in products]
not_found_ids if not_found_ids:
raise HTTPException(status_code=404, detail=f"Products with ids {not_found_ids} were not found.")
for item_id in item_ids:
if item_id in cart:
+= 1
cart[item_id] else:
= 1
cart[item_id] = {products[item_id]: quantity for item_id, quantity in cart.items()}
response return response
Running localhost and ngrok:
from pyngrok import ngrok
import uvicorn
# Setup ngrok
ngrok.set_auth_token(your_ngrok_token)
= ngrok.connect(addr="8000", proto="http", bind_tls=True)
public_url print(f"Public URL: {public_url}")
# Start server
='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True) uvicorn.run(app
Customers can now perform the following actions: - Add multiple items to the cart in one go by: - Sending a POST
request to /cart/items
with a JSON array of product ids. - FastAPI will automatically parse the JSON data into a Python list. - Corresponding products will be added to the cart. - If a product is not found in our store: - The system will raise an HTTPException
with a 404 status code.
Go to http://127.0.0.1:8000/docs, https://{ngrok-url}/docs or http://127.0.0.1:8000/redoc or https://{ngrok-url}/redoc for interactive documentation your API
Handling Errors Gracefully
In an ideal world, users would always use our online store API perfectly. They’d only add products that exist, they wouldn’t attempt to remove products from an empty cart, and they certainly wouldn’t try to buy more products than we have in stock.
But we don’t live in an ideal world.
Thankfully, FastAPI provides an easy way to handle errors and raise HTTP exceptions with meaningful error messages.
%%writefile main.py
from typing import List
from fastapi import FastAPI, HTTPException
= FastAPI()
app
# Our product list - a dictionary where key is product id and value is product name
= {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}
products
# The shopping cart - a dictionary where key is product id and value is quantity
= {}
cart
@app.get("/products/{product_id}")
async def get_product(product_id: int):
if product_id not in products:
raise HTTPException(status_code=404, detail=f"No product found with id {product_id}.")
return {product_id: products[product_id]}
@app.post("/cart/items")
async def add_items_to_cart(item_ids: List[int]):
= [item_id for item_id in item_ids if item_id not in products]
not_found_ids if not_found_ids:
raise HTTPException(status_code=404, detail=f"Products with ids {not_found_ids} were not found.")
for item_id in item_ids:
if item_id in cart:
+= 1
cart[item_id] else:
= 1
cart[item_id] = {products[item_id]: quantity for item_id, quantity in cart.items()}
response return response
@app.delete("/cart/{product_id}")
async def remove_from_cart(product_id: int):
if product_id not in cart:
raise HTTPException(status_code=404, detail="Product not in cart.")
-= 1
cart[product_id] if cart[product_id] == 0:
del cart[product_id]
return {"message": f"Removed {products[product_id]} from the cart"}
Overwriting main.py
Running localhost and ngrok:
from pyngrok import ngrok
import uvicorn
# Setup ngrok
ngrok.set_auth_token(your_ngrok_token)
= ngrok.connect(addr="8000", proto="http", bind_tls=True)
public_url print(f"Public URL: {public_url}")
# Start server
='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True) uvicorn.run(app
In this script, we’ve enhanced our API endpoints to handle common error cases such as: - If a user tries to view a product that doesn’t exist. - If a user tries to add a non-existent product to their cart. - If a user tries to remove a product from an empty cart.
In these instances, FastAPI will: - Raise an HTTPException
with a 404 status code. - Provide a descriptive error message.
We have performed tests on these enhancements: - Made a GET
request to the /products/{product_id}
endpoint with a non-existent product id. - As expected, our API returned a 404 status code with an appropriate error message.
Go to http://127.0.0.1:8000/docs, https://{ngrok-url}/docs or http://127.0.0.1:8000/redoc or https://{ngrok-url}/redoc for interactive documentation your API
Rendering HTML
FastAPI is not limited to exchange JSON data. It can also handle and render HTML. This is useful if, besides the API, you want to have an introductory web page for your online store. All we need to do is return a multiline string (enclosed in triple quotes) containing our HTML code.
%%writefile main.py
from fastapi import FastAPI
from starlette.responses import HTMLResponse
= FastAPI()
app
@app.get("/home", response_class=HTMLResponse)
async def get_home():
return """
<html>
<head>
<title> Welcome to Our Online Store </title>
</head>
<body>
<p> Welcome to our online store! Check out our products <a href="/products">here</a>. </p>
</body>
</html>
"""
Overwriting main.py
Running localhost and ngrok:
from pyngrok import ngrok
import uvicorn
# Setup ngrok
ngrok.set_auth_token(your_ngrok_token)
= ngrok.connect(addr="8000", proto="http", bind_tls=True)
public_url print(f"Public URL: {public_url}")
# Start server
='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True) uvicorn.run(app
Now, when someone visits the “/home” endpoint of our API, FastAPI will respond with a simple HTML page. The response_class=HTMLResponse
argument tells FastAPI to convert the returned string into HTML.
Let’s navigate to the home page.
As you can see, our API correctly returns an HTML response for the “/home” endpoint.
In this script, we’ve defined a new /home endpoint that returns an HTML response, effectively serving a simple web page. The response_class=HTMLResponse argument tells FastAPI to convert the returned string into HTML. We then make a GET request to this endpoint and print the HTML response.
Path Parameters and Query Parameters
Path parameters and query parameters are two ways to pass dynamic information to an API.
First, let’s start with path parameters. These allow us to pass information via the URL path. We have already used path parameters in our /cart/{product_id}
endpoints. Let’s add one more endpoint which gives us the details of a specific product.
%%writefile main.py
from fastapi import FastAPI, HTTPException
= FastAPI()
app
# Our product list - a dictionary where key is product id and value is product name
= {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}
products
@app.get("/product/{product_id}")
async def get_product_detail(product_id: int):
if product_id not in products:
raise HTTPException(status_code=404, detail=f"No product found with id {product_id}.")
return {product_id: products[product_id]}
Overwriting main.py
Run in terminal with command: uvicorn main:app --reload
and now, a user can get the details of a single product by passing the product id in the URL, such as ‘/product/1’.
Let’s try to get the details of a product with id 1 using another terminal curl http://127.0.0.1:8000/product/1
Great, it works!
Now, let’s move on to query parameters. These offer another way to pass dynamic information to an API, but via the URL query string (the part of the URL after the ‘?’).
Let’s say we want to allow users to filter the products by part of their names. We can do that by introducing a query parameter:
%%writefile main.py
from fastapi import FastAPI
from typing import Optional
= FastAPI()
app
# Our product list - a dictionary where key is product id and value is product name
= {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}
products
@app.get("/search")
async def search_products(q: Optional[str] = None):
if q:
return {product_id: name for product_id, name in products.items() if q.lower() in name.lower()}
else:
return products
Overwriting main.py
Run in terminal with command: uvicorn main:app --reload
and now, users can search for products by including a ‘q’ parameter in the URL, like ‘/search?q=Sneakers’.
Let’s try to search product with q “Jeans” using another terminal curl http://127.0.0.1:8000/search?q=Jeans
As you can see, our API correctly returned the product that matches our search query.
In this code, we’ve made the following enhancements to our API: - Accepted dynamic path and query parameters. - Added a /product/{product_id}
endpoint that: - Returns the details of a specific product based on the product id passed in the URL. - Added a /search
endpoint that: - Allows users to filter the products based on a search query. - The search query is passed as a query parameter in the URL.
We then performed tests on these enhancements: - Made GET
requests to these endpoints. - Printed the responses for verification.
Project Example: Text Generation API Using Hugging Face’s Transformers
Imagine you’ve crafted an amazing GPT-2 model that can generate witty text, mimicking the style of famous authors. You’re quite thrilled with this digital author and now you want to share your AI-powered wordsmith with the world. How can you make this happen? You guessed it - let’s build an API with FastAPI!
Before we embark on this journey, we need to ensure that we have all the necessary tools. We will install two indispensable libraries, Transformers and Torch, using pip:
%pip install transformers torch
Setting Up the Text Generation Model
Now, let’s prepare our AI author by setting up a pipeline for text generation with the GPT-2 model from Hugging Face’s Transformers library:
from transformers import pipeline
= pipeline('text-generation', model='gpt2') generator
With our generator in place, we’re ready to start creating masterpieces!
Building the Text Generation API
Now, we’re going to design an API that allows users to feed a string of text to our AI author, who will then generate a piece of text based on the input:
%%writefile main.py
from fastapi import FastAPI
from pydantic import BaseModel
from transformers import pipeline
= pipeline('text-generation', model='gpt2')
generator
class Item(BaseModel):
str
text:
= FastAPI()
app
@app.post("/api/generate")
def generate_text(item: Item):
# Generate text
= generator(item.text, max_length=150, num_return_sequences=5, no_repeat_ngram_size=2)
outputs = [output['generated_text'] for output in outputs]
generated_text
return {"message": "Text generated!", "generated_text": generated_text}
Overwriting main.py
Running localhost and ngrok:
from pyngrok import ngrok
import uvicorn
# Setup ngrok
ngrok.set_auth_token(your_ngrok_token)
= ngrok.connect(addr="8000", proto="http", bind_tls=True)
public_url print(f"Public URL: {public_url}")
# Start server
='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True) uvicorn.run(app
And there you have it! We’ve just created an endpoint ‘/api/generate’ that accepts POST requests. This endpoint expects a string of text as input in the form of JSON data. It takes this text, feeds it to our AI author, and in response, provides five unique continuations of the initial text.
Go to http://127.0.0.1:8000/docs, https://{ngrok-url}/docs or http://127.0.0.1:8000/redoc or https://{ngrok-url}/redoc for interactive documentation your API
With FastAPI and Hugging Face’s Transformers, you’ve just turned your local GPT-2 model into a globally accessible AI-powered author. This is the beauty of APIs in machine learning. This project is a foundation upon which you can build more complex APIs, integrating different models and tasks to suit your needs.
Wrapping Up
FastAPI provides an intuitive and flexible way to build robust APIs, and you’ve just scratched the surface by designing an API for text generation with a GPT-2 model. We walked through setting up endpoints, handling JSON data, routing, error handling, and making our API globally accessible with ngrok. We also took a deep dive into the dynamic world of path and query parameters.
Equipped with these tools, you’re now set to construct comprehensive, powerful, and secure APIs. Keep practicing, keep exploring, and before you know it, you’ll be an accomplished FastAPI maestro!