How to map shuffled response to input using python asyncio aiohttp

Viewed 12

I make asynchronous post requests to API using asyncio and aiohttp. I send parameter (X, Y) (float, float) to get list of data in response - let's call it scores. The data points coming in response is not in order it got sent, so I can not zip it on index to the input, which I can do using synchronous requests. I tried mapping input to response on parameter (X, Y), which is included in the response, but it gets rounded and decimal points get cut off on the API side. I have no way of finding out what is the exact API rounding mechanism. Also I can't round it before sending request.

Is there a way to somehow tag requests and send it as kind of passive attribute to be able to map responses back?

Or maybe there is other way to map input to response?

I am not sure if my code is needed, but here is a sample. The scores response has to be matched to corresponding xy input.

btw. Yes I know that one response consists of 1000 xy. You will notice that if you read into _get_scores_async method. It is just the way API is built, that you can send 1000 xy.

import asyncio
import logging
from typing import Awaitable, Dict, List, Union

import aiohttp
import requests
import random

logger = logging.getLogger(__name__)

class APIWrapper:
    base_urls = {
        "prod": "https://apiprodlink.com/",
        "stage": "https://apistagelink.com/",
    }
    _max_concurrent_connections = 20

    def __init__(self, user: str, secret: str, env: str) -> None:
        try:
            self.base_url = self.base_urls[env]
        except KeyError:
            raise EnvironmentNotSupported(f"Environment {env} not supported.")
        self._user = user
        self._secret = secret

    @property
    def _headers(self) -> Dict:
        """Returns headers for requests"""

        return {"Accept": "application/json"}

    @property
    def _client_session(self) -> aiohttp.ClientSession:
        """Returns aiohttp ClientSession"""

        session = aiohttp.ClientSession(
            auth=aiohttp.BasicAuth(self._user, self._secret), headers=self._headers
        )
        return session

    async def _post_url_async(
        self,
        url: str,
        session: aiohttp.ClientSession,
        semaphore: asyncio.Semaphore,
        **params,
    ) -> Awaitable:
        """Creates awaitable post request. To be awaited with async function.

        Parameters
        ----------
        url : str
            post request will be done to this url
        session : aiohttp.ClientSession
            instance of ClientSession with auth and headers
        semaphore : asyncio.Semaphore
            Semaphore with defined max concurrent connections

        Returns
        -------
        Awaitable
            Coroutine object from response
        """
        async with semaphore, session.post(url=url, json=params) as res:
            res.raise_for_status()
            response = await res.json()
            return response

    async def _get_scores_async(
        self, xy: List[Tuple]
    ) -> Awaitable:
        """Creates coroutine of awaitable requests to scores endpoint

        Parameters
        ----------
        locations : List[Tuples]

        Returns
        -------
        Awaitable
            Coroutine of tasks to be run
        """
        PER_REQUEST_LIMIT = 1000
        semaphore = asyncio.Semaphore(self._max_concurrent_connections)
        tasks = []

        async with self._client_session as session:
            for batch in range(0, len(xy), PER_REQUEST_LIMIT):
                subset = xy[batch : batch + PER_REQUEST_LIMIT]
                task = asyncio.create_task(
                    self._post_url_async(
                        f"{self.base_url}scores/endpoint",
                        session,
                        semaphore,
                        xy_param=subset,
                    )
                )
                tasks.append(task)
            responses = await asyncio.gather(*tasks)
        return responses

    def get_scores(self, xy: List[Tuple]) -> List[Dict]:
        """Get scores for given xy
        Parameters
        ----------
        locations : List[Tuple]

        Returns
        -------
        List[Dict]
        """
        response = asyncio.run(self._get_scores_async(xy))
        return [x for batch in response for x in batch]


if __name__ == "__main__":
   api_client = APIWrapper("user", "secret", "prod")
   xy = [(random.uniform(1,100),random.uniform(1,100)) for i in range(0,500000)]
   scores = api_client.get_scores(xy)

0 Answers
Related