Skip to main content

eXtended Rate Limiter - A distributed rate limiter using Redis token bucket algorithm

Project description

XRL - eXtended Rate Limiter

PyPI version Python Support License: MIT

A distributed rate limiter using Redis token bucket algorithm for Python applications.

Features

  • Distributed: Works across multiple application instances using Redis
  • Token Bucket Algorithm: Smooth rate limiting with burst capacity
  • Async/Await Support: Built for modern Python async applications
  • Configurable: Flexible capacity and refill rates
  • Atomic Operations: Uses Redis Lua scripts for consistency
  • Auto-expiring Keys: Automatic cleanup of unused rate limit keys

Installation

pip install xrl-python

Quick Start

import asyncio
import redis.asyncio as redis
from xrl import XRL

async def main():
    # Create Redis connection
    redis_client = redis.from_url("redis://localhost")
    
    # Create XRL instance
    xrl = XRL(redis_client)
    
    # Acquire a token (blocks until available)
    await xrl.acquire_token("user:123", capacity=100, rate=10)
    print("✅ Request allowed!")
    
    # Try to acquire without blocking
    if await xrl.try_acquire_token("user:123", capacity=100, rate=10):
        print("✅ Token acquired immediately!")
    else:
        print("❌ Rate limited")
    
    await redis_client.aclose()

if __name__ == "__main__":
    asyncio.run(main())

Usage Examples

Basic Rate Limiting

import asyncio
import redis.asyncio as redis
from xrl import XRL

async def rate_limited_api():
    redis_client = redis.from_url("redis://localhost")
    xrl = XRL(redis_client)
    
    try:
        # 100 requests per minute (100/60 = 1.67 tokens per second)
        await xrl.acquire_token("api:endpoint", capacity=100, rate=100/60)
        
        # Your API logic here
        return {"status": "success"}
        
    finally:
        await redis_client.aclose()

Per-User Rate Limiting

async def user_rate_limit(user_id: str):
    redis_client = redis.from_url("redis://localhost")
    xrl = XRL(redis_client)
    
    try:
        # Different limits per user
        user_key = f"user:{user_id}"
        
        # 200 requests per minute for this user
        await xrl.acquire_token(user_key, capacity=200, rate=200/60)
        
        return process_user_request(user_id)
        
    finally:
        await redis_client.aclose()

Non-blocking Rate Limiting

async def try_process_request(request_id: str):
    redis_client = redis.from_url("redis://localhost")
    xrl = XRL(redis_client)
    
    try:
        # Try to acquire token without waiting
        if await xrl.try_acquire_token(f"request:{request_id}", capacity=50, rate=5):
            return await process_request(request_id)
        else:
            return {"error": "Rate limit exceeded", "retry_after": 1}
            
    finally:
        await redis_client.aclose()

API Reference

XRL Class

__init__(redis_client: redis.Redis)

Initialize the XRL rate limiter.

Parameters:

  • redis_client: An instance of redis.asyncio.Redis

async acquire_token(key: str, capacity: int, rate: float) -> bool

Acquire a token, waiting if necessary until one becomes available.

Parameters:

  • key: Unique identifier for the rate limit bucket
  • capacity: Maximum number of tokens in the bucket
  • rate: Token refill rate (tokens per second)

Returns:

  • True when a token is successfully acquired

async try_acquire_token(key: str, capacity: int, rate: float) -> bool

Try to acquire a token without waiting.

Parameters:

  • key: Unique identifier for the rate limit bucket
  • capacity: Maximum number of tokens in the bucket
  • rate: Token refill rate (tokens per second)

Returns:

  • True if token was acquired, False if rate limited

Rate Calculation Examples

# 100 requests per minute
rate = 100 / 60  # 1.67 tokens per second

# 500 requests per hour  
rate = 500 / 3600  # 0.139 tokens per second

# 10 requests per second
rate = 10  # 10 tokens per second

# 1 request every 5 seconds
rate = 1 / 5  # 0.2 tokens per second

Redis Configuration

XRL requires a Redis server. The rate limiter uses:

  • Keys: {your_key} and {your_key}:timestamp
  • TTL: Automatically set based on bucket refill time (60s minimum, 24h maximum)
  • Memory: Minimal - only stores token count and timestamp per key

Error Handling

import redis.exceptions

async def robust_rate_limiting():
    try:
        redis_client = redis.from_url("redis://localhost")
        xrl = XRL(redis_client)
        
        await xrl.acquire_token("key", capacity=100, rate=10)
        
    except redis.exceptions.ConnectionError:
        # Handle Redis connection issues
        print("Redis connection failed")
        
    except redis.exceptions.TimeoutError:
        # Handle Redis timeout
        print("Redis operation timed out")
        
    finally:
        await redis_client.aclose()

Testing

# Install dependencies with Poetry
poetry install

# Run all tests (requires Redis server)
poetry run pytest tests/ -v

# Run only unit tests (no Redis required)
poetry run pytest tests/test_xrl.py -v

# Run only integration tests
poetry run pytest tests/test_xrl_integration.py -v

Development

This project uses Poetry for dependency management:

# Install Poetry
curl -sSL https://install.python-poetry.org | python3 -

# Install dependencies
poetry install

# Add a new dependency
poetry add package-name

# Add a development dependency
poetry add --group dev package-name

# Run commands in the Poetry environment
poetry run python your_script.py
poetry run pytest tests/

# Build the package
poetry build

License

MIT License - see LICENSE file for details.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for your changes
  4. Ensure all tests pass
  5. Submit a pull request

Changelog

0.1.0

  • Initial release
  • Token bucket algorithm implementation
  • Async/await support
  • Redis Lua script for atomic operations

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

xrl_python-0.0.1.tar.gz (4.9 kB view details)

Uploaded Source

Built Distribution

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

xrl_python-0.0.1-py3-none-any.whl (5.2 kB view details)

Uploaded Python 3

File details

Details for the file xrl_python-0.0.1.tar.gz.

File metadata

  • Download URL: xrl_python-0.0.1.tar.gz
  • Upload date:
  • Size: 4.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for xrl_python-0.0.1.tar.gz
Algorithm Hash digest
SHA256 10f9101838857511ae12bbd1687200d283b5dc30c0301dd45a40244f80705dae
MD5 87d81d5c86270487eb3ab2fc17ca5329
BLAKE2b-256 e2142496d808328f21e8d21e99e9eeddcee3479fc28d048b82aa9bcc8d2b66ad

See more details on using hashes here.

File details

Details for the file xrl_python-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: xrl_python-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 5.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for xrl_python-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1eb76383b7287b182a5448412de89f0a49cd37e260f578da4c47b6ab1fc1bea3
MD5 47485f18745d719d2bbdb8bd6d4a3bd7
BLAKE2b-256 e7373cf2edf87cc6fd34d9a5ed77bc10cfe8d7019036aea66cf4215d67f512ee

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