Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .github/workflows/build_docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Build docs
on:
push:
branches:
- main
paths:
- docs

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install Poetry
uses: abatilo/actions-poetry@v3
with:
poetry-version: '1.8.3'

- name: Cache deps
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.cache/pip
key: poetry-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
restore-keys: poetry-${{ runner.os }}-

- name: Install dependencies
run: poetry install --only docs

- name: Build site
run: poetry run mkdocs build --clean --strict

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
41 changes: 41 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 🤝 Contributing

Your contributions are always welcome!

## 🧑‍💻 Getting Started

---
### 🛠️ Setup

1. Clone the repository:
```bash
git clone https://github.com/chud0/FastCacheMiddleware.git
cd fast_cache_middleware
```

2. Mark the `fast_cache_middleware` directory as "Sources Root" in your IDE (for better imports).

### 📦 Install Dependencies

Use poetry to install packages:
```bash
poetry install --with dev
```

## After work

---
### ✅ Running lint and tests
After completing the work, check yourself: execute the script to run the linters

```bash
./scripts/lint.sh
```
And tests:
```bash
./scripts/test.sh
```

If the checks were **successful**, send your changes to the **Pull request**.


53 changes: 53 additions & 0 deletions docs/advanced_usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 🛠️ Advanced Usage

!!! note

Sometimes the default caching behavior is not enough.
For these cases, **FastCacheMiddleware** allows you to plug in a custom `Controller` to fine-tune caching logic and cache key generation.


### Custom Controller

```py
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()
)
```

---
**What this does**

* is_cachable_request: lets you decide dynamically whether a request should be cached. In this example, requests marked as x-admin-request bypass caching entirely, ensuring that admin users always see live data.
* generate_cache_key: allows you to customize how cache keys are constructed. Here, the api-version header is added to the cache key, which prevents collisions between different API versions.
---
**Why override the controller?**

A custom controller is useful when you need:


* Per-role logic: cache responses for public users but not for admins or staff.
* Multi-tenant isolation: include tenant_id or org_id in cache keys.
* API versioning: avoid reusing cached responses across different versions of your API.
* Custom invalidation strategies: override how and when entries are dropped.
* Security enforcement: skip caching for endpoints with sensitive headers or tokens.

---


✅ With a custom controller, you can implement project-specific rules while still reusing all the base middleware logic.
64 changes: 64 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 🏗️ Architecture

This section describes the internal architecture of **FastCacheMiddleware** and explains how its components interact during request processing.

---

### System Components

The middleware is organized into several core components, each responsible for a specific concern:

```
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
```
- **RouteInfo** – keeps metadata about routes, such as cache settings, TTLs, and invalidation rules.
- **Controller** – the core engine that decides whether to cache, return cached data, or invalidate.
- **Storage** – abstraction over different cache backends (e.g., in-memory, Redis).
- **Serializers** – responsible for efficient serialization and deserialization of cached responses.
- **Dependencies** – FastAPI-compatible dependency classes (`CacheConfig`, `CacheDropConfig`) that allow you to configure caching behavior declaratively.

This modular design ensures **flexibility**, **extensibility**, and clean separation of concerns.

---

### Request Processing Flow

The request lifecycle in **FastCacheMiddleware** follows a clearly defined flow:

```mermaid
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]
```

Step-by-step explanation:

* Route analysis – On the first request, the middleware analyzes all routes and extracts cache-related dependencies.
* Method check – Only GET requests are eligible for caching; POST/PUT/DELETE may trigger cache invalidation.
* Cache lookup – If caching is enabled for the route, the middleware attempts to retrieve a response from the cache.
* Cache hit – If data is found, it is returned immediately, skipping application logic.
* Cache miss – If no cache entry exists, the request is executed normally, and the response is stored for future use.
* Cache invalidation – Mutating requests (POST/PUT/DELETE) with CacheDropConfig remove matching cache entries to keep data consistent.
* This flow ensures minimal latency for cached requests while guaranteeing correctness when the underlying data changes.
75 changes: 75 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# 🔧 Configuration

FastCacheMiddleware provides two main dependency classes for configuration:
- **CacheConfig** – controls how and when responses are cached for `GET` requests.
- **CacheDropConfig** – defines invalidation rules for `POST`, `PUT`, and `DELETE` requests.

These configurations are added as **FastAPI dependencies**, which makes them easy to declare directly in your routes.

---

### CacheConfig

Use `CacheConfig` to enable caching for a route.
The simplest form is just setting a **time-to-live (TTL)** in seconds.

For more advanced scenarios, you can define a custom key function.
This allows you to personalize caching based on user identity, request headers, query parameters, etc.:

```py
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

Use CacheDropConfig to invalidate cache entries when data is modified by POST, PUT, or DELETE requests.
This ensures that users always see fresh data after an update.


```python
# 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
]
)
```

Key takeaways:
* Paths can be defined as string prefixes or regular expressions.
* Multiple paths can be combined in a single configuration.
* Use simple strings for convenience, regex for fine-grained control.
* With these two building blocks, you can fine-tune caching so that:
* Reads (GET) are fast and efficient.
* Writes (POST, PUT, DELETE) automatically keep the cache consistent.
41 changes: 41 additions & 0 deletions docs/for_developers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 🤝 Contributing

Your contributions are always welcome!

## 🧑‍💻 Getting Started

---
### 🛠️ Setup

1. Clone the repository:
```bash
git clone https://github.com/chud0/FastCacheMiddleware.git
cd fast_cache_middleware
```

2. Mark the `fast_cache_middleware` directory as "Sources Root" in your IDE (for better imports).

### 📦 Install Dependencies

Use poetry to install packages:
```bash
poetry install --with dev
```

## After work

---
### ✅ Running lint and tests
After completing the work, check yourself: execute the script to run the linters

```bash
./scripts/lint.sh
```
And tests:
```bash
./scripts/test.sh
```

If the checks were **successful**, send your changes to the **Pull request**.


50 changes: 50 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Welcome!

🚀 **High-performance ASGI middleware for caching with route resolution approach**

[![PyPI version](https://img.shields.io/pypi/v/fast-cache-middleware)](https://pypi.org/project/fast-cache-middleware/)
[![CI](https://github.com/chud0/FastCacheMiddleware/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/chud0/FastCacheMiddleware/actions/workflows/ci.yml)

## 📦 Installation

```bash
pip install fast-cache-middleware
```

## ✨ Key Features

FastCacheMiddleware uses a **route resolution approach** - it analyzes application routes at startup and extracts cache configurations from FastAPI dependencies.

### 🔧 How it works

1. **At application startup:**
- Middleware analyzes all routes and their dependencies
- Extracts `CacheConfig` and `CacheDropConfig` from dependencies
- Creates internal route index with caching configurations

2. **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


## 📄 License

MIT License - see [LICENSE](LICENSE)

---

⭐ **Like the project? Give it a star!**

🐛 **Found a bug?** [Create an issue](https://github.com/chud0/FastCacheMiddleware/issues)

💡 **Have an idea?** [Suggest a feature](https://github.com/chud0/FastCacheMiddleware/discussions/categories/ideas)
Loading