p-redis-limiter
Redis token bucket rate limiter for Python and FastAPI.
Installation
pip install p-redis-limiter
Usage
1. Global Middleware (All Endpoints)
Apply a rate limit across all endpoints:
from fastapi import FastAPI
from redis import Redis
from p_redis_limiter import RateLimitMiddleware, Rate
app = FastAPI()
r = Redis(host="localhost", port=6379, decode_responses=True)
# 40 requests per 60 seconds
app.add_middleware(
RateLimitMiddleware,
redis=r,
rates=Rate(40, 60),
)
@app.get("/")
def index():
return {"ok": True}
2. Specific Endpoint Only
To rate limit a specific route instead of the whole application, use RateLimiter with Depends:
from fastapi import FastAPI, Depends
from redis import Redis
from p_redis_limiter import RateLimiter, Rate
app = FastAPI()
r = Redis(host="localhost", port=6379, decode_responses=True)
# 5 requests per 60 seconds for login only
login_limiter = RateLimiter(r, Rate(5, 60), prefix="rate:login")
@app.get("/")
def home():
return {"message": "unlimited"}
@app.post("/login", dependencies=[Depends(login_limiter.as_dependency())])
def login():
return {"message": "login successful"}
3. Multiple Rate Limits
You can define multiple rules (e.g. 5 req/sec burst limit and 100 req/min). If one rule fails, tokens are not deducted from the others:
rates = [
Rate(5, 1), # 5 requests per 1 second
Rate(100, 60), # 100 requests per 60 seconds
]
# Or string shorthand:
rates = ["5/s", "100/m"]
4. Cache TTL
By default, Redis keys expire automatically after max(window * 2, 60) seconds. You can specify a custom TTL:
# Per rate rule:
Rate(40, 60, ttl=120)
# Disable TTL (persist in Redis forever):
Rate(40, 60, ttl=-1)
# Or globally on middleware / limiter:
RateLimitMiddleware(redis=r, rates=Rate(40, 60), ttl=120)
5. Manual Usage (Specify IP Yourself)
If you prefer to get the IP yourself, simply pass your IP variable directly:
from p_redis_limiter import RateLimiter, Rate
limiter = RateLimiter(r, Rate(5, 60))
@app.post("/login")
def login(request: Request):
user_ip = get_my_ip(request) # your own IP variable
# Simple boolean check:
if not limiter.is_allowed(user_ip):
return JSONResponse({"detail": "Too many requests"}, status_code=429)
return {"ok": True}
Or get full details (remaining, retry_after):
result = limiter.check(user_ip)
if not result.allowed:
print(f"Blocked! Retry after {result.retry_after}s")
6. Redis Failure & Fallback Handling
Configure what happens if Redis becomes unreachable:
- Default (
on_redis_error="raise"): Raises the Redis connection exception (unhandled errors return HTTP 500). - Fail-Open (
on_redis_error="allow"or"pass"): Bypasses rate limiting and allows requests through so your service stays online. - In-Memory Fallback (
on_redis_error="local"): Seamlessly applies an in-memory token bucket rate limit until Redis is back up. Once Redis is reachable again, it automatically recovers and switches back to Redis.
# Option A: Allow requests through if Redis goes down (fail-open)
app.add_middleware(
RateLimitMiddleware,
redis=r,
rates="40/m",
on_redis_error="allow",
)
# Option B: Fallback to in-memory rate limiting using the same rate rules
app.add_middleware(
RateLimitMiddleware,
redis=r,
rates="40/m",
on_redis_error="local",
)
# Option C: Fallback to in-memory rate limiting with a specific/stricter rate
app.add_middleware(
RateLimitMiddleware,
redis=r,
rates="40/m",
on_redis_error="local",
fallback_rate="10/m",
)
# Also works with RateLimiter / Depends:
login_limiter = RateLimiter(
r,
"5/m",
on_redis_error="local",
fallback_rate="2/m",
)
Options
| Parameter | Type | Default | Description |
|---|---|---|---|
redis |
Redis |
Required | Redis client instance (redis.Redis or redis.asyncio.Redis) |
rates |
Rate / list / str |
Required | Rate rules, e.g. Rate(40, 60) or ["5/s", "100/m"] |
ttl |
int |
Auto | Custom Redis key TTL in seconds |
prefix |
str |
"rate" |
Prefix for Redis keys |
identifier |
Callable / str |
Client IP | Custom function or header name (auto-detects Cloudflare, Nginx, ALB, direct IP) |
exclude_paths |
list[str] |
None |
List of paths to exclude from rate limiting |
error_detail |
str |
"Too many requests" |
Response detail message on 429 |
on_redis_error |
str / Rate |
"raise" |
Behavior when Redis fails: "raise", "allow" (or "pass"), or "local" |
fallback_rate |
Rate / str / list |
None |
Custom rate limit rule to use when falling back to "local" mode |
redis_retry_interval |
float |
1.0 |
Seconds to wait before re-probing Redis after a failure |
Release files for p-redis-limiter 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| p_redis_limiter-0.1.2.tar.gz | 15.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| p_redis_limiter-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.4 kB
Release files / p_redis_limiter-0.1.2.tar.gz
| Download URL | p_redis_limiter-0.1.2.tar.gz |
|---|---|
| Size | 15.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f350413a46065ef310a80fae462581b08f2aa53235e96006151176e7aa4042a3
|
|
BLAKE2b-256 checksum How to use checksums |
c11504c829ad2ff5a41ea633128b0a6b905c4d9d15185059dacdfbde8f4d647c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / p_redis_limiter-0.1.2-py3-none-any.whl
| Download URL | p_redis_limiter-0.1.2-py3-none-any.whl |
|---|---|
| Size | 14.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
29e151e9ba65792dbb47fbc077be894db62588b284308011771b25df29e9d18a
|
|
BLAKE2b-256 checksum How to use checksums |
5e4db706e83aafc5e0e9e318b3ceed718d753b24376a55620cc63ed2db98e639
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|