Skip to main content

A FastAPI-based ML worker node library for easy model deployment

Project description

FastMLAPI

A FastAPI-based ML model serving library for easy deployment. Create production-ready ML APIs with minimal boilerplate.

Features

  • 🚀 Simple API: Extend MLController and implement a few methods
  • 🔄 Automatic /predict endpoint: Generated automatically with proper request/response handling
  • 🎯 Flexible prediction: Use load_model() for standard models or @prediction decorator for custom logic
  • 🔧 Data pipelines: @preprocessing and @postprocessing decorators for clean data flow
  • 📊 Health checks: Built-in /health endpoint
  • 📝 Auto documentation: Swagger/OpenAPI docs out of the box
  • 🎨 Customizable: Custom request/response Pydantic models supported

Installation

pip install fastmlapi

Or install from source:

pip install -e .

Quick Start

Option 1: Using load_model() (Recommended for sklearn, XGBoost, etc.)

Best for models that have a standard .predict() method:

from fastmlapi import MLController, preprocessing, postprocessing
import joblib
import numpy as np

class MyClassifier(MLController):
    model_name = "my-classifier"
    model_version = "1.0.0"
    
    def load_model(self):
        """Load and return your model. Called once at startup."""
        return joblib.load("model.pkl")
    
    @preprocessing
    def preprocess(self, data: dict) -> np.ndarray:
        """Transform input data for the model."""
        features = data.get("features", [])
        return np.array(features).reshape(1, -1)
    
    @postprocessing  
    def postprocess(self, prediction: np.ndarray) -> dict:
        """Format model output for API response."""
        return {
            "class": int(prediction[0]),
            "label": "positive" if prediction[0] == 1 else "negative"
        }

if __name__ == "__main__":
    MyClassifier().run()

Option 2: Using @prediction Decorator (Recommended for custom inference)

Best for PyTorch, TensorFlow, or any custom prediction logic:

from fastmlapi import MLController, prediction, postprocessing
import torch

class PyTorchModel(MLController):
    model_name = "pytorch-classifier"
    model_version = "1.0.0"
    
    def load_model(self):
        """Load PyTorch model."""
        model = torch.load("model.pt")
        model.eval()
        return model
    
    @prediction
    def run_inference(self, data: dict) -> torch.Tensor:
        """Custom prediction logic - replaces the default predict behavior."""
        with torch.no_grad():
            tensor = torch.tensor(data["features"], dtype=torch.float32)
            return self.model(tensor)
    
    @postprocessing
    def postprocess(self, output: torch.Tensor) -> dict:
        """Convert tensor to JSON-serializable format."""
        probabilities = torch.softmax(output, dim=-1)
        return {
            "class": int(torch.argmax(probabilities)),
            "confidence": float(probabilities.max())
        }

if __name__ == "__main__":
    PyTorchModel().run()

Option 3: No Model Needed (External APIs, rule-based systems)

When you don't need to load a model at all:

from fastmlapi import MLController, prediction
import requests

class ExternalAPIController(MLController):
    model_name = "external-predictor"
    
    # No load_model() needed!
    
    @prediction
    def call_external_service(self, data: dict) -> dict:
        """Call an external ML service."""
        response = requests.post(
            "https://api.example.com/predict",
            json=data,
            timeout=30
        )
        return response.json()

if __name__ == "__main__":
    ExternalAPIController().run()

How It Works

Prediction Pipeline

Request → preprocess() → predict_raw() → postprocess() → Response
                              ↑
                    Uses @prediction method
                    OR model.predict()
  1. preprocess(data): Transform raw input into model-ready format
  2. predict_raw(preprocessed_data): Run the actual prediction
    • If @prediction decorator is used → calls your decorated method
    • Otherwise → calls self.model.predict()
  3. postprocess(prediction): Format output for the API response

Decorators

Decorator Purpose Required?
@preprocessing Mark a method as the preprocessing step No (defaults to pass-through)
@postprocessing Mark a method as the postprocessing step No (defaults to {"result": prediction})
@prediction Mark a method as the custom prediction function No (uses model.predict() by default)

Running the Server

Direct execution

if __name__ == "__main__":
    MyClassifier().run(host="0.0.0.0", port=8000)

With Uvicorn (for development with auto-reload)

# main.py
classifier = MyClassifier()
app = classifier.app
uvicorn main:app --reload --host 0.0.0.0 --port 8000

API Endpoints

Once running, your API provides:

Endpoint Method Description
/predict POST Run predictions
/health GET Health check status
/ GET API info
/docs GET Swagger UI documentation
/redoc GET ReDoc documentation

Example Request

curl -X POST http://localhost:8000/predict \
  -H "Content-Type: application/json" \
  -d '{"data": {"features": [1.0, 2.0, 3.0]}}'

Example Response

{
  "success": true,
  "prediction": {
    "class": 1,
    "label": "positive"
  },
  "metadata": {
    "model_name": "my-classifier",
    "model_version": "1.0.0"
  }
}

Advanced Usage

Custom Request/Response Models

Define your own Pydantic models for type-safe requests and responses:

from pydantic import BaseModel, Field
from typing import List

class ImageRequest(BaseModel):
    image_url: str = Field(..., description="URL of the image to analyze")
    threshold: float = Field(0.5, ge=0, le=1, description="Detection confidence threshold")

class DetectedObject(BaseModel):
    label: str
    confidence: float
    bbox: List[float]

class ImageResponse(BaseModel):
    objects: List[DetectedObject]
    count: int

class ObjectDetector(MLController):
    model_name = "object-detector"
    request_model = ImageRequest    # Custom request schema
    response_model = ImageResponse  # Custom response schema
    
    def load_model(self):
        return load_yolo_model()
    
    @preprocessing
    def preprocess(self, data: dict):
        # data contains: {"image_url": "...", "threshold": 0.5}
        image = download_image(data["image_url"])
        self.threshold = data["threshold"]
        return image
    
    @prediction
    def detect(self, image):
        detections = self.model(image)
        return [d for d in detections if d.confidence >= self.threshold]
    
    @postprocessing
    def postprocess(self, detections) -> dict:
        return {
            "objects": [
                {"label": d.label, "confidence": d.conf, "bbox": d.bbox}
                for d in detections
            ],
            "count": len(detections)
        }

Accessing the Model in @prediction Methods

When using @prediction, you can still access the loaded model via self.model:

class HybridController(MLController):
    def load_model(self):
        return {"encoder": load_encoder(), "classifier": load_classifier()}
    
    @prediction
    def predict(self, data: dict):
        # Access multiple models
        encoded = self.model["encoder"].transform(data["text"])
        return self.model["classifier"].predict(encoded)

TensorFlow/Keras Example

from fastmlapi import MLController, preprocessing, postprocessing
import tensorflow as tf
import numpy as np

class KerasClassifier(MLController):
    model_name = "keras-classifier"
    
    def load_model(self):
        return tf.keras.models.load_model("model.h5")
    
    @preprocessing
    def preprocess(self, data: dict) -> np.ndarray:
        return np.array(data["features"]).reshape(1, -1)
    
    @postprocessing
    def postprocess(self, prediction: np.ndarray) -> dict:
        class_idx = int(np.argmax(prediction[0]))
        return {
            "class": class_idx,
            "probabilities": prediction[0].tolist()
        }

Configuration Reference

MLController Class Attributes

Attribute Type Default Description
model_name str "ml-model" Name of your model
model_version str "1.0.0" Model version string
title str "FastMLAPI" API title (shown in docs)
description str "ML Model Serving API" API description
api_version str "1.0.0" API version
request_model BaseModel PredictionRequest Custom Pydantic request model
response_model BaseModel PredictionResponse Custom Pydantic response model
enable_health bool True Enable /health endpoint
enable_docs bool True Enable Swagger/OpenAPI docs

run() Method Parameters

Parameter Type Default Description
host str "0.0.0.0" Host to bind to
port int 8000 Port to bind to
reload bool False Enable auto-reload (dev only)
**uvicorn_kwargs dict {} Additional Uvicorn options

Development

# Clone the repository
git clone https://github.com/yourusername/fastmlapi.git
cd fastmlapi

# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run example
python examples/simple_classifier.py

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastmlapi-0.1.1.tar.gz (12.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastmlapi-0.1.1-py3-none-any.whl (12.2 kB view details)

Uploaded Python 3

File details

Details for the file fastmlapi-0.1.1.tar.gz.

File metadata

  • Download URL: fastmlapi-0.1.1.tar.gz
  • Upload date:
  • Size: 12.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.11

File hashes

Hashes for fastmlapi-0.1.1.tar.gz
Algorithm Hash digest
SHA256 33732edb9b6a6b4dc231d45a17c19cbbc2bca1bae932762c7b4964546f4cbb39
MD5 ae844ad49baf94adfc25614acb4b7510
BLAKE2b-256 21cb4bac52718ec3f25f93ac61bbdcd55611c0c4f8a1a333f8c846d51bf59695

See more details on using hashes here.

File details

Details for the file fastmlapi-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: fastmlapi-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 12.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.11

File hashes

Hashes for fastmlapi-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 14b45147026aec8df6587d644837516667fb542d002ede08ac0c95947992e902
MD5 c0e9da2bfe86fdd7df080da9dc94fe22
BLAKE2b-256 c1a41a98752bfde7ff4dc8dbb873cd8dccf7e8e01f37536e347f61d85ee4e01d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page