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”

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:

%pip install fastapi uvicorn pyngrok

Igniting the FastAPI

Let’s kick-start our FastAPI adventure by initiating a basic application:

%%writefile main.py

from fastapi import FastAPI

app = FastAPI()

@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

“fast-api-running”

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:

“curl-get”

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

nest_asyncio.apply()

# Start server synchronous
uvicorn.run(app='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True)

We’ve started our FastAPI application, and are now waiting for incoming requests.

We can use curl again to try it out:

“curl-get”

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:

  1. Visit the Ngrok website. The first step is to visit Ngrok’s official website.
  2. 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.
  3. Verify your account. After signing up, you will receive a verification email. Click on the link in that email to verify your account.
  4. Log in to your account. Once your account is verified, log in to your Ngrok account.
  5. 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:

“Ngrok Auth Token”

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

your_ngrok_token = "" # @param {type:"string"} 

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)

public_url = ngrok.connect(addr="8000", proto="http", bind_tls=True)
print(f"Public URL: {public_url}")

# Start server
uvicorn.run(app='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True)

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)

“ngrok-warning-free”

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 and taskkill /PID <PID> /F to kill the process.
  • On Linux/Mac, lsof -i :<YourPort> and kill -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
app = FastAPI()

@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)

public_url = ngrok.connect(addr="8000", proto="http", bind_tls=True)
print(f"Public URL: {public_url}")

# Start server
uvicorn.run(app='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True)

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):

“fast-api-swagger-docs”

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):

“fast-api-swagger-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

app = FastAPI()

# Our product list - a dictionary where key is product id and value is product name
products = {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}

# 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:
        cart[product_id] += 1
    else:
        cart[product_id] = 1
    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")
    cart[product_id] -= 1
    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)

public_url = ngrok.connect(addr="8000", proto="http", bind_tls=True)
print(f"Public URL: {public_url}")

# Start server
uvicorn.run(app='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True)

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

app = FastAPI()

# Our product list - a dictionary where key is product id and value is product name
products = {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}

# 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]):
    not_found_ids = [item_id for item_id in item_ids if item_id not in products]
    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:
            cart[item_id] += 1
        else:
            cart[item_id] = 1
    response = {products[item_id]: quantity for item_id, quantity in cart.items()}
    return response

Running localhost and ngrok:

from pyngrok import ngrok
import uvicorn

# Setup ngrok
ngrok.set_auth_token(your_ngrok_token)

public_url = ngrok.connect(addr="8000", proto="http", bind_tls=True)
print(f"Public URL: {public_url}")

# Start server
uvicorn.run(app='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True)

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

app = FastAPI()

# Our product list - a dictionary where key is product id and value is product name
products = {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}

# 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]):
    not_found_ids = [item_id for item_id in item_ids if item_id not in products]
    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:
            cart[item_id] += 1
        else:
            cart[item_id] = 1
    response = {products[item_id]: quantity for item_id, quantity in cart.items()}
    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.")
    cart[product_id] -= 1
    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)

public_url = ngrok.connect(addr="8000", proto="http", bind_tls=True)
print(f"Public URL: {public_url}")

# Start server
uvicorn.run(app='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True)

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

app = FastAPI()

@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)

public_url = ngrok.connect(addr="8000", proto="http", bind_tls=True)
print(f"Public URL: {public_url}")

# Start server
uvicorn.run(app='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True)

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.

“fast-api-html”

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

app = FastAPI()

# Our product list - a dictionary where key is product id and value is product name
products = {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}

@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

“bash-fast-params”

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

app = FastAPI()

# Our product list - a dictionary where key is product id and value is product name
products = {1: "Sneakers", 2: "T-Shirt", 3: "Jeans", 4: "Sunglasses", 5: "Hat"}

@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

“bash-fast-query”

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

generator = pipeline('text-generation', model='gpt2')

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

generator = pipeline('text-generation', model='gpt2')

class Item(BaseModel):
    text: str

app = FastAPI()

@app.post("/api/generate")
def generate_text(item: Item):
    # Generate text
    outputs = generator(item.text, max_length=150, num_return_sequences=5, no_repeat_ngram_size=2)
    generated_text = [output['generated_text'] for output in outputs]

    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)

public_url = ngrok.connect(addr="8000", proto="http", bind_tls=True)
print(f"Public URL: {public_url}")

# Start server
uvicorn.run(app='main:app', host="0.0.0.0", port=8000, reload=True, use_colors=True)

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!

Back to top