Setting two different rate limits, does not work
alexjolig opened this issue · 4 comments
I'm testing this sample code to check the library:
import uvicorn
from fastapi import FastAPI, Request, Response, status
from fastapi.responses import JSONResponse
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# Note: the route decorator must be above the limit decorator, not below it
@app.get("/")
@limiter.limit("2/minute")
async def homepage(request: Request):
return JSONResponse(
status_code=status.HTTP_200_OK, content={"message": "Hello There!"}
)
@app.get("/mars")
@limiter.limit("3/minute")
async def homepage(request: Request, response: Response):
return JSONResponse(
status_code=status.HTTP_200_OK, content={"message": "You only have 3 request per second"}
)
if __name__ == '__main__':
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
But here's the problem, This endpoint works well for limiting only 2 request per minute:
@app.get("/")
@limiter.limit("2/minute")
async def homepage(request: Request):
But this one does not work for 3 requests per minute and throws error after requesting 2 times:
@app.get("/mars")
@limiter.limit("3/minute")
async def homepage(request: Request, response: Response):
I get this error, which seems unrelated to "3/minute"
{"error":"Rate limit exceeded: 2 per 1 minute"}
Am I missing something here?
@alexjolig Can you change the name of the functions so they aren't the same and try again? Both of your functions are named homepage
and this can cause issues when FastAPI interprets the file.
@twcurrie You were right. I somehow missed that. changing the name of the second function fixed the issue. But I have to mention that I did the same mistake using limits
library with a decorator I wrote and it worked fine:
import uvicorn
import asyncio
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
from limits import parse
from redis.redis import coredis
from helpers.limit_helpers import handle_rate_limit
two_per_minute = parse("2/minute")
three_per_minute = parse("3/minute")
app = FastAPI()
# Note: the route decorator must be above the limit decorator, not below it
@app.get("/")
@handle_rate_limit(two_per_minute)
async def homepage(request: Request):
return JSONResponse(
status_code=status.HTTP_200_OK, content={"message": "Hello There!"}
)
@app.get("/about")
@handle_rate_limit(three_per_minute)
async def homepage(request: Request):
return JSONResponse(
status_code=status.HTTP_200_OK,
content={"message": "You can read all about us, but only 3 times per minute"},
)
if __name__ == "__main__":
asyncio.run(coredis())
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
@twcurrie You were right. I somehow missed that. changing the name of the second function fixed the issue. But I have to mention that I did the same mistake using
limits
library with a decorator I wrote and it worked fine:
Without the source code for your custom decorator, I can't comment on why it would work, but did you use functools.wraps
with your custom decorator? By using that decorator, it preserves the original function name, hence the conflicts in methods interpretted by FastAPI that are decorated with slowapi
library.
@twcurrie Yes, I used functools.wraps
in my decorator:
def handle_rate_limit(limitation_item: str):
"""
This is used as a decorator for API routers to implement rate limitation
:param limitation_item: limitation value (e.g: "2/minute", "10/day")
:return: response
"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
if not await moving_window.hit(parse(limitation_item)):
return JSONResponse(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
content={"message": "Maximum requests per time exceeded!"},
)
return await func(*args, **kwargs)
return wrapper
return decorator