Skip to main content

⚡ fastapi-sliding-window

PyPI version CI/CD PyPI downloads Python 3.10+ License MIT Status Ruff

fastapi-sliding-window — sliding window rate limiter for FastAPI

(─‿‿─)

    ____           __              _ 
   / __/___ ______/ /_____ _____  (_)
  / /_/ __ `/ ___/ __/ __ `/ __ \/ / 
 / __/ /_/ (__  ) /_/ /_/ / /_/ / /  
/_/  \__,_/____/\__/\__,_/ .___/_/   
                        /_/          
         ___     ___                         _           __             
   _____/ (_)___/ (_)___  ____ _   _      __(_)___  ____/ /___ _      __
  / ___/ / / __  / / __ \/ __ `/  | | /| / / / __ \/ __  / __ \ | /| / /
 (__  ) / / /_/ / / / / / /_/ /   | |/ |/ / / / / / /_/ / /_/ / |/ |/ / 
/____/_/_/\__,_/_/_/ /_/\__, /    |__/|__/_/_/ /_/\__,_/\____/|__/|__/  
                       /____/                                           

📦 installation

pip install fastapi-sliding-window
pip install fastapi-sliding-window[redis]  # with Redis support
pip install "fastapi-sliding-window[redis]" uvicorn  # to run examples/

Full working examples are in the examples/ directory.


📑 quick start

Decorator API (recommended)

from fastapi import FastAPI
from fastapi_sliding_window import Limiter, InMemoryBackend

app = FastAPI()
limiter = Limiter(backend=InMemoryBackend())

@app.get("/login")
@limiter.limit("5/minute")
async def login():
    return {"status": "ok"}

Dependency API

from fastapi import Depends
from fastapi_sliding_window import RateLimit

@app.get("/login", dependencies=[Depends(RateLimit(requests=5, window_seconds=60))])
async def login():
    return {"status": "ok"}

🧩 features

  • 🎯 5 algorithms — Sliding Window Log, Sliding Window Counter, Fixed Window, Token Bucket, GCRA
  • 💾 in-memory & Redis — no external deps for single-node, Redis for distributed
  • 🔧 three usage styles — @limiter.limit() decorator, Depends(RateLimit(...)), RateLimitMiddleware
  • 📝 standard & IETF headers — X-RateLimit-* or RateLimit-* — configurable
  • 🚧 circuit breaker — RedisWithFallbackBackend falls back to in-memory on Redis failure
  • ✅ fully typed — py.typed marker included
  • 🚀 async-native — thread-safe with asyncio.Lock

⚖️ comparison with alternatives

Feature fastapi-sliding-window slowapi fastapi-limiter
Algorithms 5 (Fixed, Sliding Log, Sliding Counter, Token Bucket, GCRA) 2 (Fixed + Sliding) 1 (Fixed)
In-memory backend ✅ zero deps ✅ (via limits lib) ❌ Redis only
Redis backend ✅ ✅ ✅
Circuit Breaker ✅ ❌ ❌
Middleware ✅ ✅ ❌
Dependency ✅ ✅ ✅
Decorator API ✅ ✅ ❌
IETF Headers (RateLimit-*) ✅ ❌ ❌
Cost per request ✅ ❌ ❌
Burst support ✅ ❌ ❌
Thread-safe ✅ ❌ ❌
WebSocket ❌ ❌ ✅
memcached ❌ ✅ ❌
mypy strict ✅ ❌ ❌
Python versions 3.10+ 3.7+ 3.9+
External deps (basic mode) zero flask-limiter + limits pyrate-limiter

Each library has its own strengths — choose what fits your use case.


📖 usage

Limiter (decorator API)

from fastapi_sliding_window import Limiter, InMemoryBackend

limiter = Limiter(
    backend=InMemoryBackend(),
    default_limits=["1000/hour"],       # global defaults
    include_headers=True,
    use_ietf_headers=False,              # set True for RateLimit-* headers
    on_breach=lambda req, exc: ...,      # custom 429 handler
)

@app.get("/api")
@limiter.limit("10/minute")              # 10/min + 1000/hour inherited
async def api():
    return {"ok": True}

@app.get("/expensive")
@limiter.limit("5/minute", cost=5)       # each request costs 5 tokens
async def expensive():
    return {"ok": True}

@app.get("/health")
@limiter.exempt                          # no rate limit
async def health():
    return {"ok": True}

@app.get("/internal")
@limiter.limit("10/minute", exempt_when=lambda r: r.headers.get("X-Internal") == "true")
async def internal():
    return {"ok": True}

Depends (per-route, standalone)

from fastapi import Depends
from fastapi_sliding_window import RateLimit, Algorithm

@app.get("/api/data", dependencies=[
    Depends(RateLimit(requests=100, window_seconds=60))
])
async def get_data():
    return {"data": "..."}

@app.post("/api/upload", dependencies=[
    Depends(RateLimit(requests=10, window_seconds=60, algorithm=Algorithm.FIXED_WINDOW))
])
async def upload():
    return {"status": "uploaded"}

async def user_key(request):
    return request.headers.get("X-User-ID", "anonymous")

@app.get("/api/profile", dependencies=[
    Depends(RateLimit(requests=50, window_seconds=60, key_func=user_key))
])
async def profile():
    return {"user": "..."}

Depends (with Limiter delegation)

limiter = Limiter(backend=InMemoryBackend(), default_limits=["1000/hour"])

@app.get("/api", dependencies=[Depends(RateLimit(limiter=limiter))])
@limiter.limit("10/minute")              # endpoint rules + limiter defaults
async def api():
    return {"ok": True}

Middleware (global)

from fastapi_sliding_window import RateLimitMiddleware, Algorithm

app.add_middleware(
    RateLimitMiddleware,
    requests=100,
    window_seconds=60.0,
    algorithm=Algorithm.SLIDING_WINDOW_LOG,
    exclude_paths=["/health"],
)

Middleware (with Limiter delegation)

from fastapi_sliding_window import Limiter, InMemoryBackend, RateLimitMiddleware

limiter = Limiter(backend=InMemoryBackend(), default_limits=["1000/hour"])
app.add_middleware(RateLimitMiddleware, limiter=limiter)

Redis backend

from fastapi_sliding_window import Limiter
from fastapi_sliding_window._utils import from_url

limiter = Limiter(backend=from_url("redis://localhost:6379/0"))

Redis with circuit breaker fallback

from fastapi_sliding_window._backends.fallback import RedisWithFallbackBackend

backend = RedisWithFallbackBackend(
    redis_url="redis://localhost:6379/0",
    failure_threshold=5,       # open circuit after 5 failures
    recovery_timeout=30.0,     # try again after 30s
)
limiter = Limiter(backend=backend)

Burst (Token Bucket / GCRA)

@limiter.limit("5/s burst 10")    # 5 req/s sustained, burst up to 10
async def bursty():
    return {"ok": True}

String-based limits

parse("100/hour")          # RateLimitItem(limit=100, window=3600, unit='hour')
parse("5/s")               # RateLimitItem(limit=5, window=1, unit='s')
parse_many("100/hour;10/minute")  # list[RateLimitItem, RateLimitItem]

Custom cost

@limiter.limit("100/hour", cost=5)                     # fixed cost
@limiter.limit("100/hour", cost=lambda r: 5 if r.method == "POST" else 1)

📊 algorithms

Algorithm Accuracy Memory Speed Best for
Sliding Window Log 100% O(requests/window) O(1) amortized accuracy-critical
Sliding Window Counter ~99% O(1) per key O(1) high-traffic APIs
Fixed Window ~50% (boundary burst) O(1) per key O(1) simple quotas
Token Bucket exact O(1) per key O(1) bursty workloads
GCRA exact O(1) per key O(1) burst + smooth

Sliding Window Log

Stores a timestamp for every request. Prunes entries outside the window on each check. Perfectly accurate, memory grows linearly with request volume within the window.

Sliding Window Counter

Maintains two counters (current and previous window) and computes a weighted average. Near-accurate with constant memory.

Fixed Window

Simple counter per time bucket. Suffers from boundary burst (clients can send 2x the limit across a window boundary).

Token Bucket

Maintains a token count that refills at a fixed rate. Allows bursting up to the bucket capacity. When tokens are exhausted, requests are denied until tokens refill.

GCRA (Generic Cell Rate Algorithm)

Tracks the next allowed timestamp (TAT). Supports smooth traffic shaping with burst tolerance. O(1) memory per key.


📊 benchmark results

Pure backend speed (100k direct calls)

Algorithm checks/s avg µs
Fixed Window 200,700 5.0
Sliding Window Log 199,000 5.0
Sliding Window Counter 142,800 7.0
Token Bucket 189,100 5.3
GCRA 207,300 4.8

All algorithms complete a check in 5–7 µs — the overhead is negligible for any real application.

Full-stack throughput (50 concurrent clients)

Through RateLimitMiddleware (pure ASGI, no serialization) + FastAPI.

Algorithm RPS vs baseline
Baseline (no limit) 72,850 —
Fixed Window 67,660 93%
Sliding Window Log 67,320 92%
Sliding Window Counter 67,040 92%
Token Bucket 67,320 92%
GCRA 67,370 92%

Overhead of the rate-limit middleware is ~7–8% at scale.

Accuracy (limit 10/s, 50 requests at 10ms intervals)

Algorithm Allowed Blocked Notes
Fixed Window 10 40
Sliding Window Log 10 40
Sliding Window Counter 10 40
Token Bucket 15 35 5 extra allowed (burst by design)
GCRA 15 35 5 extra allowed (burst by design)

Full benchmark script and methodology: benchmarks/.


📝 HTTP headers

Standard (default)

Header Description
X-RateLimit-Limit maximum requests allowed
X-RateLimit-Remaining requests remaining in window
X-RateLimit-Reset unix timestamp when window resets
Retry-After seconds until next request is allowed (429 only)

IETF (set use_ietf_headers=True)

Header Description
RateLimit-Limit maximum requests allowed
RateLimit-Remaining requests remaining in window
RateLimit-Reset unix timestamp when window resets
Retry-After seconds until next request is allowed (429 only)

🔗 API Reference

Limiter(backend, key_func, default_limits, on_breach, include_headers, use_ietf_headers)

Main class — decorator API.

Parameter Type Default Description
backend RateLimitBackend required storage backend
key_func KeyFunc IP-based client key function
default_limits list[str] None global rate limits
on_breach Callable None custom 429 handler
include_headers bool True add rate limit headers
use_ietf_headers bool False use RateLimit-* instead of X-RateLimit-*

Methods:

  • @limiter.limit("100/hour", cost=1, key_func=None, exempt_when=None) — decorator
  • @limiter.exempt — skip rate limiting
  • await limiter.check(request, response, items) — programmatic check

RateLimit(requests, window_seconds, algorithm, key_func, include_headers, detail, backend, cost, limiter, use_ietf_headers)

FastAPI dependency for per-route rate limiting.

Parameter Type Default Description
requests `int str` 100
window_seconds float 60.0 window duration
algorithm Algorithm SLIDING_WINDOW_LOG algorithm
key_func KeyFunc IP-based client key function
include_headers bool True add headers
detail str "Rate limit exceeded" 429 detail message
backend RateLimitBackend None custom backend
cost int 1 token cost per request
limiter Limiter None delegate to Limiter
use_ietf_headers bool False IETF headers

RateLimitMiddleware(app, requests, window_seconds, algorithm, key_func, include_headers, exclude_paths, detail, backend, cost, limiter, use_ietf_headers)

Starlette middleware for global rate limiting.

Parameter Type Default Description
requests `int str` 100
window_seconds float 60.0 window duration
algorithm Algorithm SLIDING_WINDOW_LOG algorithm
key_func KeyFunc IP-based client key function
include_headers bool True add headers
exclude_paths list[str] None paths to skip
detail str "Rate limit exceeded" 429 detail
backend RateLimitBackend None custom backend
cost int 1 token cost per request
limiter Limiter None delegate to Limiter
use_ietf_headers bool False IETF headers

Algorithm enum

SLIDING_WINDOW_LOG, SLIDING_WINDOW_COUNTER, FIXED_WINDOW, TOKEN_BUCKET, GCRA

from_url(url)

Factory: "memory://" → InMemoryBackend, "redis://..." → RedisBackend

Utility functions

  • parse("100/hour") → RateLimitItem
  • parse_many("100/hour;10/minute") → list[RateLimitItem]
  • rate_limit_headers(result, use_ietf=False) → dict[str, str]

📜 license

MIT

Metadata

Release files for fastapi-sliding-window 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastapi-sliding-window 0.4.0
File Size Uploaded
fastapi_sliding_window-0.4.0.tar.gz 28.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-sliding-window 0.4.0
File Interpreter ABI Platform
fastapi_sliding_window-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 52.3 kB

Release files / fastapi_sliding_window-0.4.0.tar.gz

Download URL fastapi_sliding_window-0.4.0.tar.gz
Size 28.1 kB
Tags Source
SHA-256 checksum
How to use checksums
31a389b96cd4f9f2fc1b7a7d926a336e7240c12775c4d20033fc481f165a2df6
BLAKE2b-256 checksum
How to use checksums
50c81c1338e6363b5891048bbd541bd823d88bf4de2d3f712fb9bd4a7cf2a922
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.14

Release files / fastapi_sliding_window-0.4.0-py3-none-any.whl

Download URL fastapi_sliding_window-0.4.0-py3-none-any.whl
Size 24.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bc2a0e1163330dd18bd05b0db8b1ab50134dd10d2e62547cecb65fcc9a57cc2c
BLAKE2b-256 checksum
How to use checksums
2a878f712d2b095ff78ca7b33ed41adf5ebeb47048c26dfcbf1ba009a3b48a3e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.6.0

2 release files

0.5.0

2 release files

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page