Fastapi: [QUESTION] Using fast-api non-async libraries

Created on 25 May 2019  路  5Comments  路  Source: tiangolo/fastapi

Description

Hello i was wondering if i make my api with fast api and add libraries which are non-async will it work the same or will i have issues. Example is a library that sends requests to an external api. I am new to async stuff. Any advice will be appreciated thanks.

question

Most helpful comment

FastAPI should have no issues making use of non-async libraries. As long as you define your endpoints properly, you can still get the performance benefits of the async event loop as long as the non-async libraries are only blocking on IO. So yes, a (non-async) library that sends requests to an external API should integrate nicely with a FastAPI-based backend.

From the FastAPI docs (https://fastapi.tiangolo.com/async/):

When you declare a path operation function with normal def instead of async def, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server).

In other words, you'll get all the performance benefits as long as any calls with blocking IO are put inside an endpoint defined with def (and not async def).

If you do have blocking IO being called in an endpoint defined with async def, the server thread will remain blocked while waiting for a response.


More concretely, imagine you wanted to have an endpoint that would ultimately use a synchronous search client to execute a search (something like elasticsearch_dsl comes to mind).
Then the following would be safe, as it would execute the function in a threadpool:

app = fastapi.FastAPI()

@app.get("/search")
def search(q: str):  # using def here and NOT async def is important
    return sync_search_client.search(query=q)

On the other hand, if you wrote

@app.get("/search")
async def search(q: str):  # async def at the start of this line is the problem
    return sync_search_client.search(query=q)

then FastAPI would end up calling that function on the main server thread, and would block.

Note that the same holds if you call sync_search_client.search from inside a different function that is itself called by the endpoint (no matter how deeply nested). So when you define an endpoint using async def, you should be confident that there are no blocking calls being made downstream of any of the functions the endpoint calls.

Based on this, one might be inclined to just put all endpoints inside of functions defined with def instead of async def just to be safe, but as also described in the FastAPI docs, there is a small performance penalty when you use the threadpool to execute the endpoint function. So when you know it is safe, you are better off using an async def to define your endpoint.

All 5 comments

FastAPI should have no issues making use of non-async libraries. As long as you define your endpoints properly, you can still get the performance benefits of the async event loop as long as the non-async libraries are only blocking on IO. So yes, a (non-async) library that sends requests to an external API should integrate nicely with a FastAPI-based backend.

From the FastAPI docs (https://fastapi.tiangolo.com/async/):

When you declare a path operation function with normal def instead of async def, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server).

In other words, you'll get all the performance benefits as long as any calls with blocking IO are put inside an endpoint defined with def (and not async def).

If you do have blocking IO being called in an endpoint defined with async def, the server thread will remain blocked while waiting for a response.


More concretely, imagine you wanted to have an endpoint that would ultimately use a synchronous search client to execute a search (something like elasticsearch_dsl comes to mind).
Then the following would be safe, as it would execute the function in a threadpool:

app = fastapi.FastAPI()

@app.get("/search")
def search(q: str):  # using def here and NOT async def is important
    return sync_search_client.search(query=q)

On the other hand, if you wrote

@app.get("/search")
async def search(q: str):  # async def at the start of this line is the problem
    return sync_search_client.search(query=q)

then FastAPI would end up calling that function on the main server thread, and would block.

Note that the same holds if you call sync_search_client.search from inside a different function that is itself called by the endpoint (no matter how deeply nested). So when you define an endpoint using async def, you should be confident that there are no blocking calls being made downstream of any of the functions the endpoint calls.

Based on this, one might be inclined to just put all endpoints inside of functions defined with def instead of async def just to be safe, but as also described in the FastAPI docs, there is a small performance penalty when you use the threadpool to execute the endpoint function. So when you know it is safe, you are better off using an async def to define your endpoint.

@dmontagu Thanks for the clearing things up for me!

Excellent answer @dmontagu ! Thanks for helping here.

Thanks @codingsett for reporting back and closing the issue.

It might be worth adding this to the documentation somewhere- I've been using this library for months but had no idea about the differences in def versus async def for the view functions would impact performance. In fact I'm actually using one of the exact use cases (elasticsearch-dsl) mentioned here and am running off to do some updates :-D

@dmontagu @tiangolo what if we want to do a blocking call in an async function? asyncio provides us with a sweet option called run_in_executor which I am assuming FastAPI uses for all APIs defined with def to run them in a different threadpool executor. Do we any such workaround for FastAPI so that we can use blocking sync functions within async functions?

Was this page helpful?
0 / 5 - 0 ratings