Reza Savadkouhi

Asynchronous API Fetching with Python: A Comprehensive Guide
Introduction

In the field of web development, the ability to retrieve data from APIs plays a vital role in creating dynamic and interactive applications. However, the traditional synchronous API calls, which execute one request at a time, may cause delays and hinder the performance of your application when managing multiple requests or large data sets.

This is where asynchronous API fetching comes into play. With asynchronous API fetching, also referred to as non-blocking or concurrent API fetching, you can handle multiple API requests simultaneously without interfering with the main thread, allowing your application to remain responsive, even when handling real-time data or large amounts of data. This approach can significantly improve your application’s efficiency and overall performance.

Prerequisites

To follow this tutorial, you’ll need a basic understanding of Python, including data structures and functions. Additionally, you’ll need to install the aiohttp library, which provides an asynchronous HTTP client for Python. You can install it using the command pip install aiohttp.

Understanding jsonplaceholder.typicode.com

The tutorial you are following is based on the jsonplaceholder.typicode.com API, a web service that offers a comprehensive collection of mock JSON data. This API is specifically designed to help developers test their applications and learn how to integrate APIs in their projects. The API provides endpoints for various resources, including users, posts, and todos, allowing developers to access a diverse range of data.

For instance, the users endpoint returns a list of users with their details such as name, email, and phone number. By accessing this endpoint, developers can retrieve information about specific users and use it in their applications. Similarly, the posts endpoint provides a list of posts with their title, body, and author. This endpoint is beneficial for developers who want to retrieve blog posts or create their blogging platforms.

The todos endpoint is another endpoint that developers can use to retrieve a list of tasks with their title, status, and user ID. This endpoint is ideal for developers who want to create a to-do list application or integrate tasks into their existing applications. By using this API, developers can focus on building their applications without worrying about the data source.

Overall, the jsonplaceholder.typicode.com API is an excellent resource for developers who want to learn how to use APIs in their applications. It provides a rich collection of data and endpoints, making it easier for developers to test their applications and learn how to use APIs effectively.

Code Breakdown
Python
import aiohttp
import asyncio

# Function to fetch posts for a user
async def get_posts(session, user_id):
    url = f"https://jsonplaceholder.typicode.com/users/{user_id}/posts"
    async with session.get(url) as response:
        return await response.json()

# Function to fetch todos for a user
async def get_todos(session, user_id):
    url = f"https://jsonplaceholder.typicode.com/users/{user_id}/todos"
    async with session.get(url) as response:
        return await response.json()

# Function to fetch all users
async def get_users():
    try:
        # Fetch users' data from the API
        async with aiohttp.ClientSession() as session:
            async with session.get('https://jsonplaceholder.typicode.com/users') as response:
                users = await response.json()

                # Map users data to the desired format
                formatted_users = [
                    {
                        'id': user['id'],
                        'username': user['username'],
                        'email': user['email'],
                        'phone': user['phone']
                    }
                    for user in users
                ]

                # Fetch and add posts and todos for each user
                users_with_posts_and_todos = []
                for user in formatted_users:
                    posts = await get_posts(session, user['id'])
                    todos = await get_todos(session, user['id'])

                    # Map posts and todos to the desired format
                    formatted_posts = []
                    for post in posts:
                        formatted_posts.append({
                            'title': post['title'],
                            'body': post['body']
                        })

                    formatted_todos = []
                    for todo in todos:
                        formatted_todos.append({
                            'title': todo['title']
                        })

                    # Add posts and todos to the user object
                    user_with_posts_and_todos = {
                        **user,
                        'posts': formatted_posts,
                        'todos': formatted_todos
                    }
                    users_with_posts_and_todos.append(user_with_posts_and_todos)

                # return all users with their posts and todos in a list
                return users_with_posts_and_todos
    except Exception as error:
        print('Error:', error)
        raise error

# Function to print users' data to the console
async def print_users_data():
    try:
        users = await get_users()
        print(users)
    except Exception as error:
        print('Error:', error)

# Main function
async def main():
    await print_users_data()

# Entry point
if __name__ == '__main__':
    asyncio.run(main())

The code in this tutorial consists of three main functions: get_posts, get_todos, and get_users. The get_posts and get_todos functions fetch posts and todos for a given user ID, respectively. The get_users function fetches all users from the API and returns an array of users with their corresponding posts and todos.

Asynchronous Functions in Python

Python provides a way to define and handle asynchronous functions using the async and await keywords. Asynchronous functions are designed to be non-blocking, meaning they can run concurrently without hindering the main thread.

To define an asynchronous function in Python, we use the async keyword to create a coroutine. Coroutines are asynchronous functions that can be run concurrently with other coroutines. Inside a coroutine, we use the await keyword to pause the execution of the current coroutine until the awaited expression is complete. This allows other coroutines to be executed in the meantime without blocking the main thread.

The await keyword can be used with any awaitable object, which is an object that has an await() method that returns an iterator. This includes coroutines, generators, and other objects implementing the await() method.

In summary, using asynchronous functions and the async and await keywords in Python can significantly enhance the performance of programs that require concurrent execution of multiple tasks.

aiohttp: An Asynchronous HTTP Client for Python

The aiohttp library is a powerful tool for making asynchronous HTTP requests in Python. It provides a high-level abstraction for creating and managing asynchronous client sessions, making HTTP requests, and handling asynchronous responses. It offers several advantages over the standard requests library, including:

  • Concurrent Request Handling: aiohttp can handle multiple HTTP requests simultaneously, improving application performance and responsiveness.
  • Non-blocking I/O: aiohttp relies on asynchronous I/O mechanisms, allowing the main thread to continue processing while waiting for network responses.
  • Efficient Error Handling: aiohttp provides robust error handling mechanisms to gracefully handle network errors and unexpected conditions.
  • Coroutine-Based Programming: aiohttp integrates seamlessly with Python’s asyncio library, enabling coroutine-based asynchronous programming.

If you’re working with asynchronous API fetching in Python, the aiohttp library is an essential tool that can significantly enhance your application’s performance and responsiveness.

Check docs.aiohttp.org for more information.

Retrieving Users’ Data, using aiohttp

The get_users function is a useful piece of code that allows users to retrieve data from an API. It uses the aiohttp library, which is a powerful tool for making HTTP requests asynchronously.

When the function is called, it creates an asynchronous client session, which is a way of handling multiple HTTP requests simultaneously. The session sends an HTTP request to the API endpoint for all users, which returns a response containing the data in JSON format.

The function then parses the JSON response to extract the user data and stores it in a list. This list can then be used to access and manipulate the user data in various ways, depending on the needs of the user.

Overall, this solution is a great reuseable way to fetch data from an API quickly and efficiently, making it an indispensable tool for developers and data analysts alike.

Retrieving Posts and Todos

The get_posts and get_todos functions are similar to the get_users function. They create an asynchronous client session, make HTTP requests to the appropriate API endpoints, and parse the responses as JSON. The post and todo data is then stored in separate lists for each user.

How to Map Data in Python

Mapping is a typical programming operation involving transforming data from one format to another. In the context of the get_users function, mapping converts the raw user data retrieved from the API into a structured format that’s easier to work with.

Python
# Map users data to the desired format
formatted_users = [
    {
        'id': user['id'],
        'username': user['username'],
        'email': user['email'],
        'phone': user['phone']
    }
    for user in users
]

The formatted_users variable represents a list of user objects. Each user object contains key-value pairs representing the user’s ID, username, email, and phone. The code iterates through the list of raw user data and creates a new user object for each user. The respective key-value pairs from the raw data are extracted and assigned to the corresponding keys in the new user object for each user.

Extracting key-value pairs and assigning them to corresponding keys in a new object is essentially a mapping operation. It involves transforming the raw data into a structured format that’s more organized and easier to manipulate.

Python
# Add posts and todos to the user object
user_with_posts_and_todos = {
    **user,
    'posts': formatted_posts,
    'todos': formatted_todos
}
users_with_posts_and_todos.append(user_with_posts_and_todos)

The ** operator plays a crucial role in this mapping operation. It’s used to unpack the user dictionary into keyword arguments when creating a new user object. This allows the user’s data to seamlessly incorporate into the new object without explicitly copying the entire dictionary.

In summary, mapping is a fundamental data manipulation technique that allows us to transform data from one format to another. It’s beneficial in API fetching scenarios where we need to convert raw data into a structured format for further processing.

Handling Errors with try

The try block is used to handle potential errors that may occur during API calls or data manipulation. The except block catches the errors and prints the error message.

asyncio, How to use it and Why

asyncio is a crucial tool for asynchronous programming in Python, providing features to create asynchronous functions, manage coroutines, and handle asynchronous operations like network communication and file I/O.

The primary benefits of using asyncio include:

  1. Enhanced Performance: Asynchronous programming enables multiple tasks to run concurrently, reducing the time it takes to complete a series of operations.
  2. Improved Responsiveness: By handling multiple requests simultaneously, asynchronous applications can respond to user interactions more quickly, providing a better user experience.
  3. Resource Optimization: Asynchronous programming allows the main thread to focus on other tasks while waiting for asynchronous operations to complete, improving resource utilization.
  4. Scalability: Asynchronous applications can handle increasing workloads more efficiently as they can handle more concurrent requests without blocking the main thread.

In summary, asyncio is an indispensable tool for building efficient, responsive, and scalable applications in Python. It empowers developers to handle multiple tasks simultaneously, enhance application performance, and improve user experience.

Why not requests?

The requests library is a popular choice for making synchronous HTTP requests in Python. However, for asynchronous API fetching, using asynchronous requests is generally preferred over synchronous requests.

Synchronous requests are executed one at a time, which can lead to slower performance and responsiveness. When making multiple requests, synchronous requests can block the main thread, preventing other tasks from being executed. This can cause problems with user interactions and overall application performance.

In the context of the given code, using asynchronous requests allows the get_users function to fetch all users from the API and their corresponding posts and todos simultaneously. This prevents the main thread from being blocked and ensures a more responsive and performant application.

Conclusion

Asynchronous API fetching is a powerful technique for improving the performance and responsiveness of your Python applications. By leveraging asynchronous functions and libraries like aiohttp and asyncio, you can handle multiple API requests simultaneously, reducing the time it takes to fetch and process data, and enhancing the user experience.

The complete code is available on GitHub: https://github.com/rezabs/fetch-api-async-python

Leave a Comment

Your email address will not be published. Required fields are marked *