Deploying Models as APIs using FastAPI and Pydantic

Master model deployment. Learn to wrap machine learning models into high-performance web APIs using FastAPI and Pydantic in Python.

Introduction

Once you have serialized your model into a file like model.joblib, how do other software services access it? They do not read your filesystem directly. Instead, you wrap the model inside a Web API (Application Programming Interface).

When a client application (like a mobile app or frontend website) sends a network request containing user data, the web API receives the payload, passes it to the loaded model, calculates the prediction, and returns the result in JSON format. Today, the industry standard for wrapping Python machine learning models is FastAPI.

What You Will Learn

  • Why FastAPI is preferred for machine learning serving.
  • How to write REST API endpoints (GET and POST).
  • How to validate input schemas using Pydantic.
  • How to load and cash model files at server startup.
  • Building a functional prediction server in Python.

Why This Topic Matters

Wrapping models poorly can lead to crashes, slow query times, and security vulnerabilities. FastAPI leverages Python's asynchronous features to process hundreds of requests per second. When combined with Pydantic schemas, it automatically rejects invalid inputs (e.g., a text string sent to a model expecting age numbers) before they hit the model, protecting your server from runtime failures.

Prerequisites

Detailed Explanation

FastAPI serves as the translation layer between network JSON protocols and Python matrices.


Why FastAPI?

  • Speed: Built on top of Starlette and Uvicorn, it matches the performance of Node.js and Go.
  • Auto Documentation: Generates interactive API documentation (Swagger UI) at /docs automatically.
  • Pydantic Validation: Ensures input parameters match expected types.
  • Asynchronous support: Handles concurrent requests without blocking operations.

Request Flow in an Inference API

graph LR
    A[Client JSON Request] -->|POST /predict| B[FastAPI Server]
    B -->|Pydantic validation| C{Is Data Valid?}
    C -->|No| D[Return 422 Error]
    C -->|Yes| E[Convert to Numpy array]
    E --> F[Model Predict]
    F --> G[Return JSON Response 200 OK]
  1. Client JSON Payload: The client sends values: {"square_feet": 1500, "bedrooms": 3}.
  2. Pydantic Parsing: FastAPI verifies that square_feet is a number and bedrooms is an integer.
  3. Inference: The server transforms JSON attributes to a NumPy format: [[1500, 3]], passes it to the model, and retrieves predictions.
  4. JSON Response: The server formats the results back to the client: {"predicted_price": 320000.0}.

Python Code Examples

Here is a complete, production-grade FastAPI script that loads a serialized model and serves predictions.

# To test this, you would run: uvicorn main:app --reload
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
import joblib
import numpy as np

# 1. Instantiate the FastAPI App
app = FastAPI(title="House Price Prediction API", version="1.0")

# 2. Define the Input Data Schema using Pydantic
class HouseInputSchema(BaseModel):
    square_feet: float = Field(..., gt=0, description="Size of the house in sqft")
    bedrooms: int = Field(..., ge=1, le=10, description="Number of bedrooms (1 to 10)")
    
    # Example input representation for Swagger UI docs
    model_config = {
        "json_schema_extra": {
            "example": {
                "square_feet": 1250.5,
                "bedrooms": 3
            }
        }
    }

# 3. Load the pre-trained model at server startup (Simulated loading)
# In real deployments, you would do: model = joblib.load("model.joblib")
class DummyModel:
    def predict(self, features):
        # Dummy prediction formula: price = sqft * 200 + bedrooms * 10000
        sqft, bedrooms = features[0]
        return np.array([sqft * 200 + bedrooms * 10000])

model = DummyModel()

# 4. Define Endpoints
@app.get("/")
def home():
    return {"message": "House Price Prediction Server is online."}

@app.post("/predict")
def predict_price(data: HouseInputSchema):
    try:
        # Convert Pydantic variables to numpy matrix shape (1, 2)
        features = np.array([[data.square_feet, data.bedrooms]])
        
        # Calculate prediction
        prediction = model.predict(features)
        
        # Return response
        return {
            "status": "success",
            "predicted_price_usd": float(prediction[0])
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Inference error: {str(e)}")

Industry Use Cases

  • Real-Time Recommendation APIs: E-commerce systems querying candidate recommendation servers during cart checkouts.
  • Financial Risk Checking: Processing banking transaction payloads instantly to block fraudulent credit card transactions.
  • Image Recognition APIs: Accepting base64 image strings, passing them to CNN classifiers, and returning boundary coordinates.

Summary

Inference APIs bridge network protocols and model calculations. By wrapping models inside FastAPI endpoints and validating payloads using Pydantic schemas, engineers build high-performance, self-documenting web servers that receive JSON, compute matrix inferences, and return outputs with minimal latency.

Next Topic

How do we package our Python versions, library dependencies (FastAPI, Scikit-Learn, NumPy), and model files together so they run identically on any server in the cloud? Let's check: Docker for Machine Learning: Containerizing ML Apps.