tl;dr
fastapi
?uvicorn
or fastapi
?I have a number of endpoints in fastapi
server (using uvicorn
at the moment) that have long blocking calls to regular sync Python code. Despite the documentation (https://fastapi.tiangolo.com/async/) I am still unclear whether I should be using exclusively def
, async def
or mixing for my functions.
As far as I understand it, I have three options, assuming:
def some_long_running_sync_function():
...
def
only for endpoints@app.get("route/to/endpoint")
def endpoint_1:
some_long_running_sync_function()
@app.post("route/to/another/endpoint")
def endpoint_2:
...
async def
only and run blocking sync code in executorimport asyncio
@app.get("route/to/endpoint")
async def endpoint_1:
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, some_long_running_sync_function)
@app.post("route/to/another/endpoint")
async def endpoint_2:
...
def
and async def
based on underlying callsimport asyncio
@app.get("route/to/endpoint")
def endpoint_1:
# endpoint is calling to sync code that cannot be awaited
some_long_running_sync_function()
@app.post("route/to/another/endpoint")
async def endpoint_2:
# this code can be awaited so I can use async
...
Since no one is picking this up I‘m giving my two cents. So this stems only from my personal experience:
Option 1
This completely defeats the purpose of using an async framework. So, possible, but not useful.
Option 2 and Option 3
For me it‘s a mixture of both. The framework is explicitly made to support a mixture of sync and async endpoints. Generally I go with: No await
in the function/CPU-bound task -> def
, IO-bound/very short -> async
.
Pitfalls
When you start using async programming it‘s easy to fall for the fallacy that now you don‘t have to care about thread-safety anymore. But once you start running stuff in threadpools this isn‘t true anymore. So remember that FastAPI is running your def
endpoints in a threadpool and you are responsible to make them threadsafe.
This also implies that you need to consider what you can and can‘t do with your event loop in def
endpoints. Since your event loop is running in the main thread, asyncio.get_running_loop()
will not work. This is why I sometimes define async def
endpoints, even if there is no IO, to be able to access the event loop from the same thread. But of course then you have to keep your code short.
As a side note: FastAPI also exposes starlettes backgroundtasks
, which can be used as a dependency to create tasks from endpoints.
As I‘m writing this, this seems pretty opinionated, which is probably a reason why you didn‘t get a lot of feedback until now. So feel free to shoot me down, if you disagree with anything :-)