Skip to main content

Distributed rate limiting for FastAPI and Django with Redis backend. Built with love for scalable systems.

Project description

Pylimitx - Distributed Rate Limiting for Python

A distributed rate-limiting solution for FastAPI and Django applications.

uv add "pylimitx[fastapi]"
uv add "pylimitx[django]"

Features

  • Multiple Algorithms: Sliding Window (accurate) and Token Bucket (burst-friendly)
  • FastAPI Support: Middleware and decorators for FastAPI
  • Django Support: Middleware and decorators for Django
  • Distributed: Redis-backed, scales horizontally
  • Reliable: Circuit breaker pattern, fail-open safety
  • Simple: Easy to integrate, minimal configuration
  • Fast: Uses Python and Lua scripts for atomic operations with minimal round-trips to Redis.

Installation

With FastAPI support:

Install via UV (recommended)

uv add "pylimitx[fastapi]"

If use pip

pip install "pylimitx[fastapi]"

With Django support:

Install via UV (recommended)

uv add "pylimitx[django]"

If use pip

pip install "pylimitx[django]"

IMPORTANT: Redis Setup (Required for Both FastAPI and Django)

Rate limiting requires a running Redis server. Set it up first:

1. Start Redis

# Using Docker (Recommended)
docker run -d -p 6379:6379 redis:latest

# Or install locally
brew install redis
redis-server

2. Verify Redis is Running

redis-cli ping
# Should return: PONG

3. Configure Redis URL in Your App

The default Redis URL is: redis://localhost:6379

If using a different URL, update it when initializing Pylimitx (see examples below).


FastAPI Usage

IMPORTANT: Middleware Must Be Added FIRST

Middleware MUST be added before route handlers are defined, so it checks requests BEFORE your backend logic runs. If a request exceeds the rate limit, it returns 429 immediately without executing your route.

Step 1: Initialize Redis and RateLimiter

from fastapi import FastAPI
from redis.asyncio import Redis
from pylimitx import RateLimiter, RateLimitMiddleware, rate_limit

app = FastAPI()

# Create Redis connection
redis = Redis.from_url("redis://localhost:6379", decode_responses=True)

# Create rate limiter instance (for decorators)
limiter = RateLimiter(redis=redis)
app.state.pylimitx_limiter = limiter

Step 2: Add Global Middleware (MUST BE FIRST in middleware stack)

# Add middleware BEFORE defining routes
# This ensures ALL requests are checked for rate limits FIRST
app.add_middleware(
    RateLimitMiddleware,
    redis_url="redis://localhost:6379",
    limit=100,        # 100 requests allowed
    window=60,        # per 60 seconds
)

# Now define your routes AFTER adding middleware

Step 3: Optional - Add Per-Route Rate Limiting

from pylimitx import rate_limit

@app.get("/api/search")
@rate_limit(limit=10, window=60)  # Override global limit for this route
async def search(q: str):
    return {"results": "..."}

Complete FastAPI Example

from fastapi import FastAPI, UploadFile
from redis.asyncio import Redis
from pylimitx import RateLimiter, RateLimitMiddleware, rate_limit

app = FastAPI()

# Step 1: Redis setup
redis = Redis.from_url("redis://localhost:6379", decode_responses=True)
app.state.pylimitx_limiter = RateLimiter(redis=redis)

# Step 2: Add middleware FIRST (before any routes)
app.add_middleware(
    RateLimitMiddleware,
    redis_url="redis://localhost:6379",
    limit=100,
    window=60,
)

# Step 3: NOW define your routes (they are protected by middleware)
@app.get("/api/search")
@rate_limit(limit=10, window=60)  # Different limit for this endpoint
async def search(q: str):
    return {"results": "..."}

@app.post("/api/upload")
async def upload(file: UploadFile):
    # Uses global middleware limit (100/60)
    return {"uploaded": True}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Django Usage

IMPORTANT: Middleware Must Be Placed Early in MIDDLEWARE List

Middleware checks requests BEFORE your views run. Place it HIGH in the MIDDLEWARE list so requests are checked FIRST. If a request exceeds the rate limit, it returns 429 immediately without calling your view.

Step 1: Configure Redis in settings.py (BEFORE middleware)

# settings.py

# Configure rate limiting
RATE_LIMIT_CONFIG = {
    'REDIS_URL': 'redis://localhost:6379',  # Must run Redis before using
    'LIMIT': 100,        # Default: 100 requests
    'WINDOW': 60,        # Default: per 60 seconds
}

Step 2: Option A - Use Decorators (Per-View Rate Limiting)

from django.http import HttpResponse
from pylimitx import django_rate_limit

@django_rate_limit(limit=20, window=60)
def search_view(request):
    # Only allows 20 requests per 60 seconds to this view
    return HttpResponse("Search results")

# Class-based view
from django.views import View

class ExportView(View):
    @django_rate_limit(limit=5, window=3600)
    def post(self, request):
        # Only allows 5 requests per hour to this endpoint
        return HttpResponse("Exporting...")

Step 2: Option B - Use Global Middleware (All Endpoints)

# settings.py

# Add middleware EARLY in the list (before other middleware that processes requests)
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    # Place rate limiting middleware EARLY, BEFORE processing middleware
    'pylimitx.integrations.django.middleware.DjangoRateLimitMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
]

RATE_LIMIT_CONFIG = {
    'REDIS_URL': 'redis://localhost:6379',
    'LIMIT': 100,
    'WINDOW': 60,
}

# Now ALL endpoints are rate limited to 100 requests per 60 seconds
# No changes needed in your views

Complete Django Example

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    # Place rate limiting early - BEFORE processing middleware
    'pylimitx.integrations.django.middleware.DjangoRateLimitMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
]

RATE_LIMIT_CONFIG = {
    'REDIS_URL': 'redis://localhost:6379',
    'LIMIT': 100,
    'WINDOW': 60,
}

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('search/', views.search_view),
    path('export/', views.ExportView.as_view()),
]

# views.py
from django.http import HttpResponse
from django.views import View
from pylimitx import django_rate_limit

# Per-view limit (overrides middleware limit)
@django_rate_limit(limit=20, window=60)
def search_view(request):
    return HttpResponse("Search results")

# Class-based view with decorator
class ExportView(View):
    @django_rate_limit(limit=5, window=3600)
    def post(self, request):
        return HttpResponse("Exporting...")

Rate Limiting Algorithms

Sliding Window (Default - Most Accurate)

Tracks exact request timestamps. Best for strict API limits.

# FastAPI
@rate_limit(limit=10, window=60, algorithm="sliding_window")
async def endpoint():
    pass

# Django
RATE_LIMIT_CONFIG = {
    'LIMIT': 10,
    'WINDOW': 60,
    'ALGORITHM': 'sliding_window',  # Default
}

Token Bucket (Burst Support)

Allows users to burst requests up to bucket_capacity, then rate limits to the specified limit/window.

IMPORTANT: If you do NOT provide bucket_capacity and refill_rate:

  • bucket_capacity defaults to limit value
  • refill_rate defaults to limit/window (steady rate)
  • Result: Works like sliding window with no burst allowed
# Example 1: With burst support
# FastAPI
@rate_limit(
    limit=10,
    window=60,
    algorithm="token_bucket",
    bucket_capacity=30,    # Can burst up to 30 requests
    refill_rate=10/60,     # Then refill at 10 per minute
)
async def upload(file):
    # Users can upload 30 files instantly, then limited to 10/min
    pass

# Django
RATE_LIMIT_CONFIG = {
    'LIMIT': 10,
    'WINDOW': 60,
    'ALGORITHM': 'token_bucket',
    'BUCKET_CAPACITY': 30,     # Burst size
    'REFILL_RATE': 10/60,      # Refill rate (requests per second)
}
# Example 2: Without burst (defaults used)
# FastAPI
@rate_limit(
    limit=10,
    window=60,
    algorithm="token_bucket",
    # bucket_capacity NOT provided -> defaults to 10
    # refill_rate NOT provided -> defaults to 10/60 (0.167 per second)
)
async def endpoint():
    # Same as sliding_window above - no burst, just rate limiting
    pass

# Django
RATE_LIMIT_CONFIG = {
    'LIMIT': 10,
    'WINDOW': 60,
    'ALGORITHM': 'token_bucket',
    # BUCKET_CAPACITY NOT provided -> defaults to LIMIT (10)
    # REFILL_RATE NOT provided -> defaults to LIMIT/WINDOW
    # Result: Works like sliding_window with no burst
}

Understanding Parameters

  • limit: Maximum requests allowed (e.g., 10)
  • window: Time window in seconds (e.g., 60 for per minute)
  • algorithm: Either "sliding_window" or "token_bucket"
  • bucket_capacity (token_bucket only): Max burst size. If not provided, defaults to limit
  • refill_rate (token_bucket only): How many requests refill per second. If not provided, defaults to limit/window

Response Headers

When a request is blocked (HTTP 429):

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
Retry-After: 45

When a request is allowed:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 3

Troubleshooting

Redis Connection Error

Error: "ConnectionError: Error 111 connecting to localhost:6379"

Solution:

# Make sure Redis is running
redis-cli ping
# Should return: PONG

# If not running, start Redis
redis-server  # or docker run -d -p 6379:6379 redis:latest

Circuit Breaker Open

If you see many "pylimitx.redis_failure" messages, your Redis is down or disconnected.

What happens:

  1. Rate limiter fails to connect to Redis
  2. After 3 failures, circuit breaker opens
  3. All requests allowed (fail-open safety)
  4. Rate limiter retries connection after 30 seconds

Solution: Get Redis running again.


Configuration Reference

RATE_LIMIT_CONFIG (Django only)

RATE_LIMIT_CONFIG = {
    'REDIS_URL': 'redis://localhost:6379',      # Redis connection
    'LIMIT': 100,                               # Max requests
    'WINDOW': 60,                               # Time window (seconds)
    'ALGORITHM': 'sliding_window',              # Or 'token_bucket'
    'BUCKET_CAPACITY': 200,                     # Token bucket only
    'REFILL_RATE': 100/60,                      # Token bucket only
    'FAIL_OPEN': True,                          # Allow traffic if Redis down
    'FAILURE_THRESHOLD': 3,                     # Failures before circuit opens
    'RECOVERY_TIMEOUT': 30,                     # Seconds before retry
}

Rate Limiter Options (FastAPI)

app.state.pylimitx_limiter = RateLimiter(
    redis=redis,
    fail_open=True,                 # Allow traffic if Redis down
    failure_threshold=3,            # Failures before circuit opens
    recovery_timeout=30,            # Seconds before retry
)

# Middleware options
app.add_middleware(
    RateLimitMiddleware,
    redis_url="redis://localhost:6379",
    limit=100,
    window=60,
    algorithm="sliding_window",
    bucket_capacity=None,           # Token bucket only
    refill_rate=None,               # Token bucket only
    fail_open=True,
    failure_threshold=3,
    recovery_timeout=30,
)

# Decorator options
@rate_limit(
    limit=10,
    window=60,
    algorithm="sliding_window",
    bucket_capacity=None,
    refill_rate=None,
)

Key Concepts

Namespace: "global", "GET_/api/search" Identifier: IP address, API key, user ID Window: 60 (per minute), 3600 (per hour) Limit: Max requests in window


Contributing

Want to contribute? You can:

  1. Fork the repo: https://github.com/MANAS-CHARCHI/pylimit
  2. Use it for yourself and share feedback
  3. Contribute improvements via pull requests
  4. Report issues on GitHub

License

MIT License

Created with love for development and scalable systems 💙

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

pylimitx-0.1.3.tar.gz (34.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pylimitx-0.1.3-py3-none-any.whl (17.2 kB view details)

Uploaded Python 3

File details

Details for the file pylimitx-0.1.3.tar.gz.

File metadata

  • Download URL: pylimitx-0.1.3.tar.gz
  • Upload date:
  • Size: 34.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pylimitx-0.1.3.tar.gz
Algorithm Hash digest
SHA256 98df611a96afb6c9ecc21a2c49efe1037e936ca0b8e3719e12f288c700709b60
MD5 3994f279a59792d608901db9e4f56b08
BLAKE2b-256 8135fcbc0eb961b979edffbbe6b2458291b5a2a7bae00688930abef92d25b8d7

See more details on using hashes here.

File details

Details for the file pylimitx-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: pylimitx-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 17.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pylimitx-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 bcabb539c5c6fab28bae2f7e396051e8c8fddeb59a05ef57346474e389c5e494
MD5 328c18990ac78b69572d9d4cb2b69a8a
BLAKE2b-256 8af7c33270d67c13da39ba9c9ab5d4b52220ccc5b1901acf20cb6e17f65b1f41

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page