Skip to content

A simple Python package to pre-compute and cache FastAPI endpoints that have parameters with discrete values (like Enums or Literals). This is ideal for slow, data-intensive endpoints where the inputs are predictable and the data does not change frequently.

Notifications You must be signed in to change notification settings

jithinsankar/fastapi-cached

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fastapi-cached

A simple Python package to pre-compute and cache FastAPI endpoints that have parameters with discrete values (like Enums or Literals). This is ideal for slow, data-intensive endpoints where the inputs are predictable and the data does not change frequently.

Features

  • Automatic Pre-computation: Runs on server startup to compute all possible outcomes.
  • Persistent Caching: Saves results to a JSON file, so you don't re-compute on every server restart.
  • Resume Support: If the startup computation is interrupted, it resumes where it left off.
  • Easy to Use: Requires only a simple decorator and a startup event hook.
  • Automatic Parameter Detection: Infers discrete values from type hints like Enum and typing.Literal.
  • Editable Cache: The JSON cache file is human-readable and can be edited manually if needed.

Installation

You can install it with pip:

pip install fastapi-cached

How to Use

Here is a complete example of how to integrate fastapi-cached into your FastAPI application using the modern lifespan event handler.

# main.py
import asyncio
from contextlib import asynccontextmanager
from enum import Enum
from fastapi import FastAPI
from fastapi_cached import FastAPICached

# 1. Initialize fastapi-cached
cache = FastAPICached(cache_file_path="sales_report_cache.json")

# 2. Define the lifespan manager to run pre-computation on startup
@asynccontextmanager
async def lifespan(app: FastAPI):
    # This code runs on startup
    await cache.run_precomputation()
    yield
    # This code runs on shutdown (optional)
    print("Application has shut down.")

# 3. Initialize FastAPI with the lifespan handler
app = FastAPI(lifespan=lifespan)

# --- Define your types ---
class SubregionEnum(str, Enum):
    EMEA = "EMEA"
    APAC = "APAC"
    AMER = "AMER"

class StoreIDEnum(str, Enum):
    STORE_101 = "101"
    STORE_202 = "202"
    ONLINE = "ONLINE"

# 4. Apply the decorator to your slow endpoint
@app.get("/sales-report")
@cache.precompute
async def get_sales_report(subregion: SubregionEnum, store_id: StoreIDEnum):
    """
    A slow endpoint that simulates complex database queries.
    This original function will only be called during the pre-computation phase.
    """
    print(f"--- Running original slow function for {subregion.value}/{store_id.value} ---")
    await asyncio.sleep(2)  # Simulate a 2-second delay

    if store_id == StoreIDEnum.ONLINE:
        revenue = len(subregion.value) * 5000
    else:
        revenue = int(store_id.value) * 1000

    return {
        "subregion": subregion,
        "store_id": store_id,
        "data": {"revenue": revenue}
    }

How It Works

  1. Initialization: cache = fastapi-cached(...) creates a cache manager instance.
  2. Lifespan Manager: The lifespan function is defined using @asynccontextmanager. The code before the yield statement is designated as startup logic.
  3. Startup: When initializing FastAPI via app = FastAPI(lifespan=lifespan), FastAPI knows to execute the startup portion of the lifespan manager. This triggers cache.run_precomputation().
    • The cache loads any existing data from sales_report_cache.json.
    • It inspects get_sales_report for Enum or Literal parameters.
    • It iterates through all combinations not already in the cache, executes the original function, and saves the result.
  4. Decoration & Live Requests: The @cache.precompute decorator works as before. It registers the function for pre-computation and replaces it with a fast version that serves results directly from the cache during live requests.

4. How to Run the Example

Place all the files as described in the structure.

examples/main.py

Use the example code from the README.md and save it in this file.

# examples/main.py

from enum import Enum
from fastapi import FastAPI
import asyncio
from fastapi_cached import FastAPICached # Assuming fastapi-cached is installed or in PYTHONPATH

# 1. Initialize FastAPI and fastapi-cached
app = FastAPI()
# Give the cache file a descriptive name
cache = FastAPICached(cache_file_path="sales_report_cache.json")

# --- Define your types ---
class SubregionEnum(str, Enum):
    EMEA = "EMEA"
    APAC = "APAC"
    AMER = "AMER"

class StoreIDEnum(str, Enum):
    STORE_101 = "101"
    STORE_202 = "202"
    STORE_303 = "303"
    STORE_404 = "404"
    ONLINE = "ONLINE"

# Example of a response model if you want to use one
from pydantic import BaseModel
class Report(BaseModel):
    subregion: SubregionEnum
    store_id: StoreIDEnum
    data: dict

# 2. Apply the decorator to your slow endpoint
# The response_model is still useful for validation and documentation
@app.get("/sales-report", response_model=Report)
@cache.precompute
async def get_sales_report(subregion: SubregionEnum, store_id: StoreIDEnum):
    """
    A slow endpoint that simulates complex database queries.
    This original function will only be called during the pre-computation phase.
    """
    # This print statement shows when the *actual* logic is running
    print(f"--- Running original slow function for {subregion.value}/{store_id.value} ---")
    await asyncio.sleep(2)  # Simulate a 2-second delay for database queries

    if store_id == StoreIDEnum.ONLINE:
        revenue = len(subregion.value) * 5000
    else:
        revenue = int(store_id.value) * 1000

    return {
        "subregion": subregion,
        "store_id": store_id,
        "data": {"revenue": revenue}
    }

# 3. Register the pre-computation to run on startup
@app.on_event("startup")
async def on_startup():
    await cache.run_precomputation()

Running the Server

From your terminal in the fastapi-cached-project directory:

# First, ensure dependencies are installed (FastAPI and Uvicorn)
pip install fastapi "uvicorn[standard]"

# Run the example app
uvicorn examples.main:app --reload

First Run: You will see the pre-computation logs in your terminal, taking about 3 subregions * 5 stores * 2 seconds = 30 seconds.

Subsequent Runs: When you restart the server, you will see a log saying "All combinations were already cached," and the server will start instantly. Any request to the endpoint will be served immediately from the cache.

Generated sales_report_cache.json: A file will be created in your examples directory that looks like this (truncated):

{
    "get_sales_report::(('store_id', <StoreIDEnum.STORE_101: '101'>), ('subregion', <SubregionEnum.EMEA: 'EMEA'>))": {
        "subregion": "EMEA",
        "store_id": "101",
        "data": {
            "revenue": 101000
        }
    },
    "get_sales_report::(('store_id', <StoreIDEnum.STORE_202: '202'>), ('subregion', <SubregionEnum.EMEA: 'EMEA'>))": {
        "subregion": "EMEA",
        "store_id": "202",
        "data": {
            "revenue": 202000
        }
    },
    "...": "..."
}

You can manually edit the revenue values in this file, restart the server, and your API will serve the new, edited values.

About

A simple Python package to pre-compute and cache FastAPI endpoints that have parameters with discrete values (like Enums or Literals). This is ideal for slow, data-intensive endpoints where the inputs are predictable and the data does not change frequently.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages