Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async call method with Flask

I'm trying to call a blocking function through a Flask method but it take several second, so I was thinking I could do some async call to speed things up but it doesn't work as expected. Apparently with asyncio I can't just launch a coroutine in background and don't wait for the end of the execution, maybe I need to use thread? Or use grequest as my blocking function is using request...

Here's my code so far:

@app.route("/ressource", methods=["GET"])
def get_ressource():
    do_stuff()
    return make_response("OK",200)  

def do_stuff():
  # Some stuff
  fetch_ressource()


async def fetch_ressource():
    return await blocking_function()


def blocking_function():
  # Take 2-3 seconds
  result = request.get('path/to/remote/ressource')
  put_in_database(result)

I heard about Celeri but it seems a little overkill for only one function.

like image 684
Raphael Todo Avatar asked Jun 20 '18 07:06

Raphael Todo


People also ask

Are Flask routes asynchronous?

The simple explanation is that Flask uses WSGI to service HTTP requests and responses which doesn't support asynchronous I/O. Asynchronous code requires a running event loop to execute, so Flask needs to get a running event loop from somewhere in order to execute an async view.

Is Flask synchronous or asynchronous?

Flask has been claimed as synchronous on many occasions, yet still possible to get async working but takes extra work.

How do you call an asynchronous API in Python?

We can write asynchronous code with Python by using a library called Asyncio, though. Python has another library called Aiohttp that is an HTTP client and server based on Asyncio. Thus, we can use Asyncio to create asynchronous API calls. This is useful for optimizing code.

What happens when you call async method?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.


2 Answers

It's a little bit late to answer, but I am interested in this.

I manage it by wrapping the function and calling it via asyncio.run(), but I don't know if multiple asyncio.run() calls is a good thing to do.

from functools import wraps
from flask import Flask
import asyncio

def async_action(f):
    @wraps(f)
    def wrapped(*args, **kwargs):
        return asyncio.run(f(*args, **kwargs))
    return wrapped

app = Flask(__name__)

@app.route('/')
@async_action
async def index():
    await asyncio.sleep(2)
    return 'Hello world !'

app.run()
like image 144
luc.chante Avatar answered Oct 20 '22 01:10

luc.chante


You can do this with Quart and AIOHTTP with code that should be very familiar to the Flask code given,

@app.route("/ressource", methods=["POST"])
async def get_ressource():
    asyncio.ensure_future(blocking_function())
    return await make_response("OK", 202)  

async def blocking_function():
    async with aiohttp.ClientSession() as session:
        async with session.get('path/to/remote/ressource') as resp:
            result = await resp.text()
    await put_in_database(result)

Note: I've changed it to a POST route as it does something and I've returned a 202 response to indicate that it has triggered processing.

Should you wish to stick with Flask I recommend you use eventlet and use spawn(blocking_function) without the async or await inclusions.

Also Note I am the Quart author.

like image 24
pgjones Avatar answered Oct 20 '22 00:10

pgjones