Skip to main content

A lightweight, modular rate limiting library for python

Project description

RateGuard Usage Guide

RateGuard is a framework-agnostic rate limiting library. Its core RateLimiter class can be used in any Python framework (Flask, Django, FastAPI, Celery, or pure Python scripts). When a limit is hit, RateGuard raises a single generic exception — RateLimitExceeded — and it's up to each framework's own error-handling mechanism to turn that into an HTTP response.


1. Framework-Agnostic Usage (Any Python App)

from requestguard import RateLimiter

# Create a limiter: Allow 100 requests every 60 seconds
limiter = RateLimiter(
    max_retries=100,
    ttl=60
)

def some_action(user_id: str):
    result = limiter.check(user_id)

    if result["allowed"]:
        print(f"Action allowed! Remaining requests: {result['remaining']}")
    else:
        print(f"Rate limited. Try again in {result['retry_after']} seconds")

Using the @limit decorator directly (no framework) raises RateLimitExceeded, a plain Python exception with no web-framework dependency:

from requestguard import limit
from requestguard.exceptions import RateLimitExceeded

@limit(max_retries=5, ttl=60)
def do_something(user_id):
    return "done"

try:
    do_something("user_42")
except RateLimitExceeded as exc:
    print(exc.detail)  # {"error": "Too many requests", "retry_after": 12.4}

2. How Exception Handling Works

RateLimitExceeded is not an HTTP exception — it carries no status code or framework awareness. Each framework has its own place to catch it and translate it into a 429 Too Many Requests response. You register this translation once, at app startup; you never need to try/except it in every view.

# requestguard/exceptions.py
class RateLimitExceeded(Exception):
    def __init__(self, retry_after=None, message="Too many requests"):
        self.message = message
        self.retry_after = retry_after
        self.detail = {"error": message, "retry_after": retry_after}
        super().__init__(message)

2a. FastAPI

Register a global exception handler on the app instance. FastAPI will call this automatically anytime a view raises RateLimitExceeded, anywhere in the call stack.

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from requestguard import limit
from requestguard.exceptions import RateLimitExceeded

app = FastAPI()

@app.exception_handler(RateLimitExceeded)
async def rate_limit_handler(request: Request, exc: RateLimitExceeded):
    return JSONResponse(status_code=429, content=exc.detail)

@limit(max_retries=5, ttl=60)
def my_endpoint(request: Request):
    return {"message": "Hello!"}

@app.get("/hello")
def hello_route(request: Request):
    return my_endpoint(request)

No try/except needed inside my_endpoint — the exception propagates up and FastAPI routes it to the handler.


2b. Django REST Framework (DRF)

Plug into DRF's EXCEPTION_HANDLER setting. This runs for every view using DRF's dispatch, so again — no per-view try/except needed.

# your_app/exceptions.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
from requestguard.exceptions import RateLimitExceeded

def custom_exception_handler(exc, context):
    if isinstance(exc, RateLimitExceeded):
        return Response(exc.detail, status=429)
    return exception_handler(exc, context)  # fall back to DRF's default
# settings.py
REST_FRAMEWORK = {
    "EXCEPTION_HANDLER": "your_app.exceptions.custom_exception_handler",
}
from requestguard import limit
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated

class UserViewSet(viewsets.ModelViewSet):
    @limit(max_retries=3, ttl=30)
    @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
    def me(self, request):
        serializer = self.get_serializer(request.user)
        return Response(serializer.data)

2c. Plain Django (no DRF)

DRF's EXCEPTION_HANDLER only applies to DRF views. For plain Django views, use middleware's process_exception hook instead.

# your_app/middleware.py
from django.http import JsonResponse
from requestguard.exceptions import RateLimitExceeded

class RateLimitMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        return self.get_response(request)

    def process_exception(self, request, exception):
        if isinstance(exception, RateLimitExceeded):
            return JsonResponse(exception.detail, status=429)
        return None  # let Django handle everything else normally
# settings.py
MIDDLEWARE = [
    ...,
    "your_app.middleware.RateLimitMiddleware",
]

2d. Flask

Use @app.errorhandler, registered once against the exception class.

from flask import Flask, jsonify
from requestguard import limit
from requestguard.exceptions import RateLimitExceeded

app = Flask(__name__)

@app.errorhandler(RateLimitExceeded)
def handle_rate_limit(exc):
    return jsonify(exc.detail), 429

@app.route("/hello")
@limit(max_retries=5, ttl=60)
def hello_route():
    return {"message": "Hello!"}

3. Summary Table

Framework Registration point Where the exception is caught
FastAPI @app.exception_handler(RateLimitExceeded) Global, per-app
DRF REST_FRAMEWORK["EXCEPTION_HANDLER"] Global, all DRF views
Plain Django Middleware process_exception Global, all views
Flask @app.errorhandler(RateLimitExceeded) Global, per-app

The pattern is the same everywhere: rateguard only raises RateLimitExceeded; your app registers one handler, once, to turn it into a 429. No view or endpoint ever needs its own try/except RateLimitExceeded block.

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

requestguard-0.1.3.tar.gz (10.8 kB view details)

Uploaded Source

Built Distribution

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

requestguard-0.1.3-py3-none-any.whl (8.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: requestguard-0.1.3.tar.gz
  • Upload date:
  • Size: 10.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for requestguard-0.1.3.tar.gz
Algorithm Hash digest
SHA256 369eb70eef650c5173fad940572fd4c151e29492d25087cc43ba9c56981070c3
MD5 e1b11b059289911d18d8e4e9eebc958e
BLAKE2b-256 6543599193f9a5706e1ade050efb592a9f2e6fc21dceccf43ba5b9fe107ccbd1

See more details on using hashes here.

File details

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

File metadata

  • Download URL: requestguard-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 8.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for requestguard-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 646197158f89a423e01ba23525370efefd2cded3dd8dad2d32cfaeeca6def07a
MD5 4363eab97224a0a762f16540ea376f8b
BLAKE2b-256 30d4600af5bacd1cbe6a7d3a7e968e9e973786ccf7666404bf1da6ae181f02c0

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