Skip to content

Commit

Permalink
0.3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
bagowix committed Jan 15, 2025
1 parent 91cfc76 commit b5a6b31
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 22 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ We follow [Semantic Versions](https://semver.org/).

## WIP

## Version 0.3.1

### Fixes

- Removed `TypeAlias` import from `typing_extensions` for better compatibility with Python 3.8 and 3.9, as `TypeAlias` is now part of `typing` in Python 3.10+.
- Replaced the `RedisBackend` alias with a direct use of `Union[Redis, AsyncRedis]` in the `backend` parameter of the `__init__` method for backward compatibility.

### Misc

- Updated README with improved examples for:
- **Using middleware for FastAPI**: Enhanced to include proper error handling with `JSONResponse` for 429 responses, providing more clarity and correctness.
- **Using middleware for Django**: Added a class-based middleware example leveraging `MiddlewareMixin` for better integration with Django's request/response lifecycle. Also demonstrated an example with DRF's `APIView`.

## Version 0.3.0

### Features
Expand Down
60 changes: 44 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,31 @@ async with async_limiter:

```python
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from ratelimit_io import RatelimitIO, RatelimitIOError, LimitSpec
from redis.asyncio import Redis as AsyncRedis

app = FastAPI()
redis_client = AsyncRedis(host="localhost", port=6379)
limiter = RatelimitIO(backend=redis_client, is_incoming=True)
limiter = RatelimitIO(
backend=redis_client,
base_limit=LimitSpec(requests=5, seconds=1),
is_incoming=True
)

@app.middleware("http")
async def ratelimit_middleware(request: Request, call_next):
try:
await limiter.a_wait(f"user:{request.client.host}", LimitSpec(10, seconds=60))
except RatelimitIOError as e:
raise HTTPException(status_code=e.status_code, detail=e.detail)
return await call_next(request)
response = await call_next(request)
return response
except RatelimitIOError as exc:
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail}
)

@app.get("/fetch")
@limit
async def fetch_data():
return {"message": "Request succeeded!"}
```
Expand All @@ -149,20 +158,39 @@ async def fetch_data():

```python
from django.http import JsonResponse
from ratelimit_io import RatelimitIO, RatelimitIOError, LimitSpec
from django.utils.deprecation import MiddlewareMixin
from ratelimit_io import RatelimitIO, LimitSpec, RatelimitIOError
from redis import Redis
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

redis_client = Redis(host="localhost", port=6379)
limiter = RatelimitIO(backend=redis_client, is_incoming=True)

def ratelimit_middleware(get_response):
def middleware(request):
try:
limiter.wait(f"user:{request.META['REMOTE_ADDR']}", LimitSpec(10, seconds=60))
except RatelimitIOError as e:
return JsonResponse({"error": e.detail}, status=e.status_code)
return get_response(request)
return middleware
redis = Redis("localhost", port=6379)
limit = RatelimitIO(
backend=redis,
base_limit=LimitSpec(requests=5, seconds=1),
is_incoming=True,
)


class RatelimitMiddleware(MiddlewareMixin):
def process_exception(self, request, exception):
if isinstance(exception, RatelimitIOError):
return JsonResponse(
{"detail": exception.detail},
status=exception.status_code,
)
return None


class Foo(APIView):
permission_classes = ()

@limit
def get(self, request, *args, **kwargs):
return Response(data={"message": "ok"}, status=status.HTTP_200_OK)

```

### Flask Example
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "ratelimit-io"
version = "0.3.0"
version = "0.3.1"
description = "Flexible bidirectional rate-limiting library with redis backend"
authors = ["Galushko Bogdan <[email protected]>"]
license = "MIT"
Expand Down
6 changes: 1 addition & 5 deletions ratelimit_io/rate_limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from redis import Redis
from redis.asyncio import Redis as AsyncRedis
from redis.exceptions import NoScriptError
from typing_extensions import TypeAlias


class RatelimitIOError(Exception):
Expand Down Expand Up @@ -78,15 +77,12 @@ def __str__(self) -> str:
return f"{self.requests}/{self.total_seconds()}s"


RedisBackend: TypeAlias = Union[Redis, AsyncRedis]


class RatelimitIO:
"""Rate limiter for managing incoming and outgoing request limits."""

def __init__(
self,
backend: RedisBackend,
backend: Union[Redis, AsyncRedis],
is_incoming: bool = False,
base_url: Optional[str] = None,
base_limit: Optional[LimitSpec] = None,
Expand Down

0 comments on commit b5a6b31

Please sign in to comment.