Rate limiting is one of those things you don't think about until you really need it. Maybe a script kiddie is hammering your login endpoint, or a legitimate partner's cron job went rogue and is eating up all your database connections.
If you're using Django, you might have used django-ratelimit in the past. It's a classic, but frankly, it hasn't kept up with modern Django needs—especially when it comes to Async/Await support and High Availability.
I just released django-smart-ratelimit v1.0.1 to solve exactly these problems. Here is how you can use it to build a production-ready defense system for your API.
The Problem: When Redis Dies, Your App Shouldn't đź’€
Most rate limiters are simple wrappers around a cache (like Redis). If Redis slows down or times out, your rate limiter throws an exception, and your users get a 500 error.
We can do better.
This library implements a Circuit Breaker pattern. If the backend (Redis/MongoDB) starts failing, the rate limiter detects it and "fails open" (or closed, your choice). This means your site stays up even if your rate limiting infrastructure is having a bad day.
Installation
pip install django-smart-ratelimit[redis]
1. The Basic Decorator
It looks familiar if you've used Django before, but under the hood, it's fully typed and async-compatible.
from django.http import JsonResponse
from django_smart_ratelimit import ratelimit
@ratelimit(key='ip', rate='10/m', block=True)
def my_view(request):
return JsonResponse({"message": "Hello World!"})
If the user exceeds 10 requests per minute, they receive a 429 Too Many Requests.
2. Going Async ⚡
This is where the library shines. It uses coredis for true non-blocking Redis operations.
@ratelimit(key='ip', rate='50/s', block=True)
async def my_async_endpoint(request):
# This won't block your event loop while checking limits!
data = await fancy_async_logic()
return JsonResponse(data)
3. Advanced: Dynamic Limits by User Type
You usually want to give authenticated users higher limits than anonymous ones. You can stack decorators nicely for this:
@ratelimit(key='ip', rate='10/m', group='anon')
@ratelimit(key='user', rate='100/m', group='auth')
def api_view(request):
# ...
Or use a custom callable for the rate:
def get_rate(group, request):
if request.user.is_staff:
return '1000/h'
return '100/h'
@ratelimit(key='user', rate=get_rate, block=True)
def expensive_view(request):
...
4. Choice of Algorithms đź§
Not all endpoints are the same. We support three algorithms:
- Token Bucket: Great for allowing "Bursts" of traffic (e.g., 100 requests at once, then refill slowly).
- Sliding Window: Precise limiting (prevents the "double-limit at the top of the hour" loophole).
- Fixed Window: Simple and fast.
You can configure this globally or per-view.
Migration
If you are currently using django-ratelimit, migration is effortless. In v1.0.1, we added an alias so you don't even need to rename your decorators immediately.
# Old
from ratelimit.decorators import ratelimit
# New
from django_smart_ratelimit import ratelimit
Links & Source
I'd love for you to try it out and break it!
- GitHub: https://github.com/YasserShkeir/django-smart-ratelimit
- PyPI: https://pypi.org/project/django-smart-ratelimit/
Happy coding! 🚀
Top comments (0)