Video Explanation: FastAPI lab


Overview

In this Lab, we will learn how to expose ML models as APIs using FastAPI and uvicorn.

  1. FastAPI: FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints.
  2. uvicorn: Uvicorn is an ASGI web server implementation for Python. It is often used to serve FastAPI aplications.

The workflow involves the following steps:

  1. Training a Decision Tree Classifier on Iris Dataset.
  2. Serving the trained model as an API using FastAPI and uvicorn.

Prerequisites

  1. Github Lab-1

Setting up the lab

GitHub link

Note: If you’d like to save your work files to GitHub, setup your working directory similar to Github Lab-1 and add necessary files to .gitignore. If you just want to run the lab locally, follow the below steps.

  1. Open the local mlops_labs directory using Visual Studio Code(VSC) and open a new terminal inside VSC.
  2. Create a new working directory for this lab(e.g. fastapi_lab1).
  3. Move into the working directory using the command cd fastapi_lab1 in your terminal.
  4. Create a virtual environment(e.g. fastapi_lab1_env).
  5. Activate the environment and install the required packages using pip install -r requirements.txt.

Note:

  • fastapi[all] in requirements.txt will install optional additional dependencies for fastapi which contains uvicorn too.

Project structure

mlops_labs
└── fastapi_lab1
    ├── assets/
    ├── fastapi_lab1_env/
    ├── model/
    │   └── iris_model.pkl
    ├── src/
    │   ├── __init__.py
    │   ├── data.py
    │   ├── main.py
    │   ├── predict.py
    │   └── train.py
    ├── README.md
    └── requirements.txt

Running the Lab

  1. First step is to train a Decision Tree Classifier(Although you have model/iris_model.pkl when you cloned from the repo, let’s create a new model). To do this, move into src/ folder with
     cd src
    
  2. To train the Decision Tree Classifier, run:
     python train.py
    
  3. To serve the trained model as an API, run:
     uvicorn main:app --reload
    
  4. Testing endpoints - to view the documentation of your api model you can use http://127.0.0.1:8000/docs (or) http://localhost:8000/docs after you run you run your FastAPI app.

API page

You can also test out the results of your endpoints by interacting with them. Click on the dropdown button of your endpoint -> Try it out -> Fill the Request body -> Click on Execute button.

API response

  • You can also use other tools like Postman for API testing.

FastAPI Syntax

  • The instance of FASTAPI class can be defined as:
      app = FastAPI()
    
  • When you run a FastAPI application, you often pass this app instance to an ASGI server uvicorn. The server then uses the app instance to handle incoming web requests and send responses based on the routes and logic you’ve defined in your FastAPI application.
  • To run a FastAPI application, run:
      uvicorn main:app --reload
    
  • In this command, main is the name of the Python file containing your app instance (without the .py extension), and app is the name of the instance itself. The –reload flag tells uvicorn to restart the server whenever code changes are detected, which is useful during development and should not be used in production.
  • All the functions which should be used as API should be prefixed by @app.get(“/followed_by_endpoint_name”) or @app.post(“/followed_by_endpoint_name”). This particular syntax is used to define route handlers (which function should handle an incoming request based on the URL and HTTP method), which are the functions responsible for responding to client requests to a given endpoint.
    1. Decorator (@): This symbol is used to define a decorator, which is a way to dynamically add functionality to functions or methods. In FastAPI, decorators are used to associate a function with a particular HTTP method and path.
    2. App Instance (app): This represents an instance of the FastAPI class. It is the core of your application and maintains the list of defined routes, request handlers, and other configurations.
    3. HTTP Method (get, post, etc.): The HTTP method specifies the type of HTTP request the route will respond to. For example, get is used for retrieving data, and post is used for sending data to the server. FastAPI provides a decorator for each standard HTTP method, such as @app.put, @app.delete, @app.patch, and @app.options, allowing you to define handlers for different types of client requests. For detailed info refer to this webiste by Mdn.
    4. Path/Endpoint (“/endpoint_name”): This is the URL path where the API will be accessible. When a client makes a request to this path using the specified HTTP method, FastAPI will execute the associated function and return the response.
  • Using async in FastAPI allows for non-blocking operations, enabling the server to handle other requests while waiting for I/O tasks, like database queries or model loading, to complete. This leads to improved concurrency and resource utilization, enhancing the application’s ability to manage multiple simultaneous requests efficiently.

Data Models in FastAPI

1. IrisData class
class IrisData(BaseModel):
    petal_length: float
    sepal_length:float
    petal_width:float
    sepal_width:float

The IrisData class is a Pydantic model which defines the expected structure of the data for a request body. When you use it as a type annotation for a route operation parameter, FastAPI will perform the following actions:

  • Request Body Reading: FastAPI will read the request body as JSON.
  • Data Conversion: It will convert the corresponding types, if necessary.
  • Data Validation: It will validate the data. If the data is invalid, it will return a 422 Unprocessable Entity error response with details about what was incorrect.

2. IrisResponse class

class IrisResponse(BaseModel):
    response:int

The IrisResponse class is another Pydantic model that defines the structure of the response data for an endpoint. When you specify response_model=IrisResponse in a route operation, it tells FastAPI to:

  • Serialize the Output: Convert the output data to JSON format according to the IrisResponse model.
  • Document the API: Include the IrisResponse model in the generated API documentation, so API consumers know what to expect in the response.

FastAPI features

  1. Request Body Reading: When a client sends a request to a FastAPI endpoint, the request can include a body with data. For routes that expect data (commonly POST, PUT, or PATCH requests), this data is often in JSON format. FastAPI automatically reads the request body by checking the Content-Type header, which should be set to application/json for JSON payloads.
  2. Data Conversion: Once the request body is read, FastAPI utilizes Pydantic models to parse the JSON data. Pydantic attempts to construct an instance of the specified model using the data from the request body. During this instantiation, Pydantic converts the JSON data into the proper Python data types as declared in the model.
    • For instance, if the JSON object has a field like petal_length with a value of “5.1” (a string), and the model expects a float, Pydantic will transform the string into a float. If conversion isn’t possible (say, the value was “five point one”), Pydantic will raise a validation error.
  3. Data Validation: Pydantic checks that all required fields are present and that the values are of the correct type, adhering to any constraints defined in the model (such as string length or number range). If the validation passes, the endpoint has a verified Python object to work with. If validation fails (due to missing fields, incorrect types, or constraint violations), FastAPI responds with a 422 Unprocessable Entity status. This response includes a JSON body detailing the validation errors, aiding clients in correcting their request data.
  4. Error Handling: Error handling in FastAPI can be effectively managed using the HTTPException class. HTTPException is used to explicitly signal an HTTP error status code and return additional details about the error. When an HTTPException is raised within a route, FastAPI will catch the exception and use its content to form the HTTP response.
    • Instantiation: The HTTPException class is instantiated with at least two arguments: status_code and detail. The status_code argument is an integer that represents the HTTP status code (e.g., 404 for Not Found, 400 for Bad Request). The detail argument is a string or any JSON-encodable object that describes the error.
    • Response: When an HTTPException is raised, FastAPI sends an HTTP response with the status code specified. The detail provided in the HTTPException is sent as the body of the response in JSON format.
from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    item = get_item_by_id(item_id)  # Hypothetical function to fetch an item
    if item is None:
        raise HTTPException(status_code=404, detail=f"Item with ID {item_id} not found")
    return item

In this example, get_item_by_id is a function that retrieves an item based on its ID. If no item with the given ID is found, an HTTPException with a 404 Not Found status code is raised, and the detail message is customized to include the ID of the item that was not found.

FastAPI will catch this exception and return a response with a 404 status code and a JSON body like this:

{
    "detail": "Item with ID 1 not found"
}
  • For more information on how to handle errors in FASTAPI refer to this documentation.