1

I have a route that sends a number of emails. To avoid rate limits, I am using time.sleep(1) between emails. If I understand correctly, the route will run in its own thread or coroutine and this will not block other requests, but I thought it would be good to confirm this with the community. Here is a code example (simplified to focus on the issue):

@router.get("/send_a_bunch_of_emails")
def send_a_bunch_of_emails(db: Session = Depends(get_db)):
    users = get_a_bunch_of_users(db)
    for user in users:
        send_email(to=user.email)
        time.sleep(1)  # Maximum of 1 email per second

I am just wanting to confirm, that if hypothetically, this sent 10 emails, it wouldn't block FastAPI for 10 seconds. Based on my testing this doesn't appear to be the case, but I'm wary of gotchas.

Glenn
  • 4,195
  • 9
  • 33
  • 41
  • Are you configured to use more than one process to handle requests? `time.sleep(1)` will block *that* process, but not others. – chepner Mar 16 '23 at 16:38
  • If you want to implement an async-able solution, use an async compatible mailclient, then use `asyncio.sleep` to sleep instead; that way you'll get both performance without having to worry about the threadpool size or the number of processes in this specific case. – MatsLindh Mar 16 '23 at 20:07
  • I believe that although time.sleep() is blocking, according to the related question, since it runs in an external threadpool, it doesn't block other requests while running. Per my testing, this appears to be the case, as I can make other requests before this finishes running. – Glenn Mar 17 '23 at 21:34

2 Answers2

0

FastAPI runs on ASGI server implementations, where A stands for Asynchronous. One can't be asynchronous while freezing on simple sleep() call. This is very much by design; not exactly FastAPI's, but rather the lower-level frameworks it uses.

You can test this directly by implementing /sleep/{int} route, GETting /sleep/666 and trying other endpoints while it "hangs".

Nikolaj Š.
  • 1,457
  • 1
  • 10
  • 17
-1

I think you are looking for this library: fastapi-limiter

Example:

import redis.asyncio as redis
from fastapi import Depends
from fastapi_limiter import FastAPILimiter
from fastapi_limiter.depends import RateLimiter

@app.on_event("startup")
async def startup():
    redis = redis.from_url("redis://localhost", encoding="utf-8", decode_responses=True)
    await FastAPILimiter.init(redis)

@router.get("/send_a_bunch_of_emails", dependencies=[Depends(RateLimiter(times=1, seconds=1))])
def send_a_bunch_of_emails(db: Session = Depends(get_db)):
    users = get_a_bunch_of_users(db)
    for user in users:
        send_email(to=user.email)
Asi
  • 170
  • 2
  • 10