eXtended Rate Limiter - A distributed rate limiter using Redis token bucket algorithm
Project description
XRL - eXtended Rate Limiter
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 ofredis.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 bucketcapacity: Maximum number of tokens in the bucketrate: Token refill rate (tokens per second)
Returns:
Truewhen 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 bucketcapacity: Maximum number of tokens in the bucketrate: Token refill rate (tokens per second)
Returns:
Trueif token was acquired,Falseif 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
- Fork the repository
- Create a feature branch
- Add tests for your changes
- Ensure all tests pass
- 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xrl_python-0.1.1.tar.gz.
File metadata
- Download URL: xrl_python-0.1.1.tar.gz
- Upload date:
- Size: 5.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
291231100d570d6a759c71b48146c417c053b33f5936ffaeeae7ce7d6842a9bc
|
|
| MD5 |
d8fb185d57683432dd3b45d2a4b64bde
|
|
| BLAKE2b-256 |
6eb234048c3d2c440cc8269f1c9a09fbfdd83f61060b0b81fdfd9fad14e69e05
|
File details
Details for the file xrl_python-0.1.1-py3-none-any.whl.
File metadata
- Download URL: xrl_python-0.1.1-py3-none-any.whl
- Upload date:
- Size: 5.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5c6a28010991079f07c8870030a792e7a36f6f09cac96148eeb5ffe4b689c5b
|
|
| MD5 |
1f3c0991780e1c9a5a7e29ba615a6e73
|
|
| BLAKE2b-256 |
3bbb401c0ba05cf204694ffd28d76e803461734f886b77f6255a7cd08ec09258
|