<span class="wtr-time-wrap before-title"><span class="wtr-time-number">6</span> min read</span>Async Requests with Flask

6 min readAsync Requests with Flask

Recently Flask has introduced async routes in its 2.0 update. This blog is everything about async requests with Flask 2.0. The Flask 2.0 has come up with many interesting updates. Do check out this blog of Progress Story for all the updates of Flask 2.0. In this blog, we will be going to learn something about asyncio, aiohttp and async routes of Flask.

Agenda

We will be covering the below topics in this blog

  • async/await | Asyncio
  • Async routes in Flask
  • Benchmarking

Async requests with Asyncio – async/await

Asyncio is a library to write concurrent code using the async/await syntax. It is used as a foundation for multiple Python asynchronous frameworks that provide high-performance network and web servers, database connection libraries, distributed task queues, etc.

It is often a perfect fit for IO-bound and high-level structured network code.

import asyncio

async def main():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')

# Python 3.7+
asyncio.run(main())

Using Python asyncio , we can also make better use of the CPU sitting idle when waiting for the I/O. asyncio is single-process and single-thread. There is an event loop in asyncio , which routinely measure the progress of the tasks.

If the event loop has measured any progress, it would schedule another task for execution, therefore, minimizing the time spent on waiting for I/O. This is also called cooperative multitasking. The tasks must cooperate by announcing when they are ready to be switched out. And the announcement is explicitly made by await keyword.

Async in Flask 2.0

Let’s create a simple Flask application that is having a route which calls some URL and fetches the result synchronously. For that we would need to install flask and requests library

pip install flask
pip install requests

To make it interactive we will be using the xkcd comic images. If you haven’t heard about this, then do check out what xkcd comic is.

Now let’s create an API comic that will render a random page of xkcd comic using requests library

Output:

xkcd | Async requests with Flask
xkcd in flask | Async Requests with Flask

To fetch one page from the comic book it took around 1 sec. Let’s say we want to fetch 5 pages from a comic book, then it will take approximately around 5 secs.

Output

xkcd | Async requests with Flask
Multiple xkcd with flask | Async Requests with Flask

Flask application is fetching 5 images from xkcd comic synchronously. Initially it fetches first image and waits for the response and doing nothing at that time. Then once the response is received then it again fetches the second image and doing nothing at that time. This task is I/O bound task where the CPU is sitting idle doing nothing and waiting for the result of I/O task, in this case it is a API call.

This problem can be solved with multithreading and asyncio. In this blog, we will learn how we can solve this using asyncio in Flask. Let’s modify our existing code a bit.

For asyncio to work in the flask, we must satisfy the prerequisites.

  • python version > 3.6
  • Remove all blocking code inside the route
pip install "Flask[async]"
pip install httpx

Implementation of async requests

For async routes in flask 2.0 just use the below syntax

@app.get('/comic')
async def hello():
    pass

Now we want to call multiple times API but all in an asynchronous fashion. At present, the requests library is not able to do that. So for that, we will install httpx library and convert the code like below

async def get_multiple_images(number):
    async with httpx.AsyncClient() as session: # asynclient will work with async
        tasks = [get_xkcd_image(session) for _ in range(number)]
        result = await asyncio.gather(*tasks, return_exceptions=True) # will use gather to run every coroutine asynchronously and gather the result in ordered fashion
    return result

Convert the API request to a coroutine

async def get_xkcd_image(session):
    random = randint(0, 300)
    result = await session.get(f'http://xkcd.com/{random}/info.0.json') # create awaitable object of session get so that at this point, thread will not wait for the result instead of it it will do a context switch to other part of program
    return result.json()['img']

Put await before the actual call to third party API. By using await it will not wait for the result of the third party API call. Instead it will do the context switching to other part of the program.

The new code will look like below.

Now the output will is taking 1 sec

xkcd | Async requests with Flask
Async requests with flask | Async requests with Flask

It seems all 5 requests were called at the same time as previously it was taking around 5 seconds to complete all the request but not it took only 1 second to complete all the requests.

Use Cases of async requests in Flask

Async requests are getting popular day by day because of the simplicity and reducing the time consuming I/O tasks. If the flask application is calling other microservices or doing some other I/O tasks. Then the async routes will be of much help by utilizing the CPU process and thread to the fullest.

For example, when a single route is calling more than 1 third party APIs or other microservices APIs then, don’t need to call them synchronously and waiting for the results. Instead, just call them asynchronously and serve the result to the front end.

Conclusion

FastAPI/Quart are well known for the async routes frameworks. If you are considering or migrating from flask to these frameworks just for this reason then, I guess it’s time to use Flask to the fullest.

I hope, it has helped you or it will help you. If you want to discuss anything or anything related to tech, you can contact me here or on the Contact Page of Progress Story. If you are interested in becoming a part of the Progress story please reach out to me or check out the Create Blog page.

See you next time! Peace Out ✌️

Leave a Reply