An intelligent middleware for caching FastAPI responses
Project description
FastCacheMiddleware
🚀 High-performance ASGI middleware for caching with route resolution approach
✨ Key Features
FastCacheMiddleware uses a route resolution approach - it analyzes application routes at startup and extracts cache configurations from FastAPI dependencies.
🔧 How it works
-
At application startup:
- Middleware analyzes all routes and their dependencies
- Extracts
CacheConfigandCacheDropConfigfrom dependencies - Creates internal route index with caching configurations
-
During request processing:
- Checks HTTP method (cache only GET, invalidate for POST/PUT/DELETE)
- Finds matching route by path and method
- Extracts cache configuration from pre-analyzed dependencies
- Performs caching or invalidation according to configuration
💡 Benefits
- ⚡ High performance - pre-route analysis
- 🎯 Easy integration - standard FastAPI dependencies
- 🔧 Flexible configuration - custom key functions, route-level TTL
- 🛡️ Automatic invalidation - cache invalidation for modifying requests
- 📊 Minimal overhead - efficient handling of large numbers of routes
📦 Installation
pip install fast-cache-middleware
🎯 Quick Start
import uvicorn
from fastapi import FastAPI
from fast_cache_middleware import CacheConfig, CacheDropConfig, FastCacheMiddleware
app = FastAPI()
# Add middleware - it will automatically analyze routes
app.add_middleware(FastCacheMiddleware)
# Routes with caching
@app.get("/users/{user_id}", dependencies=[CacheConfig(max_age=300)])
async def get_user(user_id: int) -> dict[str, int | str]:
"""This endpoint is cached for 5 minutes."""
# Simulate database load
return {"user_id": user_id, "name": f"User {user_id}"}
# Routes with cache invalidation
@app.post(
"/users/{user_id}",
dependencies=[CacheDropConfig(paths=["/users/*", "/api/users/*"])],
)
async def update_user(user_id: int) -> dict[str, int | str]:
"""It will invalidate cache for all /users/* paths."""
return {"user_id": user_id, "status": "updated"}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
🔧 Configuration
CacheConfig
Configure caching for GET requests:
from fast_cache_middleware import CacheConfig
# Simple caching
CacheConfig(max_age=300) # 5 minutes
# With custom key function, for personalized cache
def key_func(request: Request):
user_id = request.headers.get("Authorization", "anonymous")
path = request.url.path
query = str(request.query_params)
return f"{path}:{user_id}:{query}"
CacheConfig(max_age=600, key_func=key_func) # 10 minutes
CacheDropConfig
Configure cache invalidation for modifying requests:
# Paths can be matched by startswith
CacheDropConfig(
paths=[
"/users/", # Will match /users/123, /users/profile, etc.
"/api/", # Will match all API paths
]
)
# Paths can be matched by regexp
CacheDropConfig(
paths=[
r"^/users/\d+$", # Will match /users/123, /users/456, etc.
r"^/api/.*", # Will match all API paths
]
)
# You can mix regexp and simple string matching - use what's more convenient
CacheDropConfig(
paths=[
"/users/", # Simple prefix match
r"^/api/\w+/\d+$" # Regexp for specific API endpoints
]
)
🏗️ Architecture
System Components
FastCacheMiddleware
├── RouteInfo # Route information with cache configuration
├── Controller # Caching logic and validation
├── Storage # Storages (InMemory, Redis, etc.)
├── Serializers # Cached data serialization
└── Dependencies # FastAPI dependencies for configuration
Request Processing Flow
graph TD
A[HTTP Request] --> B{Route analysis done?}
B -->|No| C[Analyze application routes]
C --> D[Save route configurations]
B -->|Yes| E{Method supports caching?}
D --> E
E -->|No| F[Pass to application]
E -->|Yes| G[Find matching route]
G --> H{Route found?}
H -->|No| F
H -->|Yes| I{GET request + CacheConfig?}
I -->|Yes| J[Check cache]
J --> K{Cache found?}
K -->|Yes| L[Return from cache]
K -->|No| M[Execute request + save to cache]
I -->|No| N{POST/PUT/DELETE + CacheDropConfig?}
N -->|Yes| O[Invalidate cache]
N -->|No| F
O --> F
M --> P[Return response]
🎛️ Storages
InMemoryStorage (default)
from fast_cache_middleware import FastCacheMiddleware, InMemoryStorage
storage = InMemoryStorage(max_size=1000)
app.add_middleware(FastCacheMiddleware, storage=storage)
InMemoryStorage uses batch cleanup for better performance
Custom Storage
from fast_cache_middleware import BaseStorage
class RedisStorage(BaseStorage):
def __init__(self, redis_url: str):
import redis
self.redis = redis.from_url(redis_url)
async def store(self, key: str, response, request, metadata):
# Implementation for saving to Redis
pass
async def retrieve(self, key: str):
# Implementation for retrieving from Redis
pass
app.add_middleware(FastCacheMiddleware, storage=RedisStorage("redis://localhost"))
🧪 Testing
import pytest
from httpx import AsyncClient
from examples.basic import app
@pytest.mark.asyncio
async def test_caching():
async with AsyncClient(app=app, base_url="http://test") as client:
# First request - cache miss
response1 = await client.get("/users/1")
assert response1.status_code == 200
# Second request - cache hit (should be faster)
response2 = await client.get("/users/1")
assert response2.status_code == 200
assert response1.json() == response2.json()
@pytest.mark.asyncio
async def test_cache_invalidation():
async with AsyncClient(app=app, base_url="http://test") as client:
# Cache data
await client.get("/users/1")
# Invalidate cache
await client.post("/users/1", json={})
# Next GET should execute new request
response = await client.get("/users/1")
assert response.status_code == 200
📊 Performance
Benchmarks
- Route analysis: ~5ms for 100 routes at startup
- Route lookup: ~0.1ms per request (O(n) by number of cached routes)
- Cache hit: ~1ms per request
- Cache miss: original request time + ~2ms for saving
Optimization
# For applications with many routes
app.add_middleware(
FastCacheMiddleware,
storage=InMemoryStorage(max_size=10000), # Increase cache size
controller=Controller(default_ttl=3600) # Increase default TTL
)
🔒 Security
Cache Isolation
def user_specific_cache() -> CacheConfig:
def secure_key_func(request):
# Include user token in key
token = request.headers.get("authorization", "").split(" ")[-1]
return f"{request.url.path}:token:{token}"
return CacheConfig(max_age=300, key_func=secure_key_func)
@app.get("/private/data", dependencies=[Depends(user_specific_cache)])
async def get_private_data():
return {"sensitive": "data"}
Header Validation
Middleware automatically respects standard HTTP caching headers:
Cache-Control: no-cache- skip cacheCache-Control: no-store- forbid cachingCache-Control: private- don't cache private responses
🛠️ Advanced Usage
Custom Controller
from fast_cache_middleware import Controller
class CustomController(Controller):
async def is_cachable_request(self, request):
# Custom logic - don't cache admin requests
if request.headers.get("x-admin-request"):
return False
return await super().should_cache_request(request)
async def generate_cache_key(self, request):
# Add API version to key
version = request.headers.get("api-version", "v1")
base_key = await super().generate_cache_key(request)
return f"{version}:{base_key}"
app.add_middleware(
FastCacheMiddleware,
controller=CustomController()
)
📝 Examples
More examples in the examples/ folder:
- quick_start.py - minimal example showing basic caching and invalidation
- basic.py - basic usage with FastAPI
🤝 Contributing
git clone https://github.com/chud0/FastCacheMiddleware
cd FastCacheMiddleware
poetry install --with dev
./scripts/test.sh
📄 License
MIT License - see LICENSE
⭐ Like the project? Give it a star!
🐛 Found a bug? Create an issue
💡 Have an idea? Suggest a feature
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fast_cache_middleware-0.0.6.tar.gz.
File metadata
- Download URL: fast_cache_middleware-0.0.6.tar.gz
- Upload date:
- Size: 16.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.3 CPython/3.11.3 Windows/10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e3ef6960167ef59b1708c18aa6c3cbb27efb7d26fcd34e6de8d14442ecabfa3
|
|
| MD5 |
bd4daba2a0cad4050aae94f2730f611f
|
|
| BLAKE2b-256 |
6496a3a0236958d5eed35573305b21ad4800ffaaecb2fb80d4ea635207ec7851
|
File details
Details for the file fast_cache_middleware-0.0.6-py3-none-any.whl.
File metadata
- Download URL: fast_cache_middleware-0.0.6-py3-none-any.whl
- Upload date:
- Size: 20.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.3 CPython/3.11.3 Windows/10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
daebd7cb042e4f14b8b6c54403c1c28cd581c9a108186ab34a15cbc3e74628bd
|
|
| MD5 |
73123274c27cbabc1f5011a0c0d3ab8a
|
|
| BLAKE2b-256 |
42a524ad31a0f67513b40d7b769cda6f3a3316bd223ca9409b88ee7007173677
|