|  | 
| 1 | 1 | import httpx | 
|  | 2 | +import asyncio | 
| 2 | 3 | 
 | 
| 3 | 4 | from app.api.common.models import ChainId, CoinType | 
| 4 | 5 | from app.config import settings | 
|  | 6 | +from .constants import COINGECKO_CHUNK_SIZE, COINGECKO_MAX_CONCURRENT_REQUESTS | 
|  | 7 | +from .utils import chunk_sequence | 
| 5 | 8 | 
 | 
| 6 | 9 | from .cache import CoinMapCache, PlatformMapCache, TokenPriceCache | 
| 7 | 10 | from .models import ( | 
| @@ -80,38 +83,57 @@ async def get_prices( | 
| 80 | 83 |         if not coingecko_ids: | 
| 81 | 84 |             return results | 
| 82 | 85 | 
 | 
| 83 |  | -        # Fetch prices for all coingecko ids | 
| 84 |  | -        params = { | 
| 85 |  | -            "ids": ",".join(coingecko_ids), | 
| 86 |  | -            "vs_currencies": batch.vs_currency.value, | 
| 87 |  | -            "include_platform": True, | 
| 88 |  | -        } | 
|  | 86 | +        # Split coingecko_ids into chunks | 
|  | 87 | +        id_chunks = chunk_sequence(list(coingecko_ids), COINGECKO_CHUNK_SIZE) | 
|  | 88 | + | 
|  | 89 | +        # Process chunks in parallel with controlled concurrency | 
|  | 90 | +        semaphore = asyncio.Semaphore(COINGECKO_MAX_CONCURRENT_REQUESTS) | 
|  | 91 | + | 
|  | 92 | +        async def fetch_chunk(chunk: list[str]) -> dict: | 
|  | 93 | +            async with semaphore: | 
|  | 94 | +                params = { | 
|  | 95 | +                    "ids": ",".join(chunk), | 
|  | 96 | +                    "vs_currencies": batch.vs_currency.value, | 
|  | 97 | +                    "include_platform": True, | 
|  | 98 | +                } | 
|  | 99 | +                async with self._create_client() as client: | 
|  | 100 | +                    response = await client.get( | 
|  | 101 | +                        f"{self.base_url}/simple/price", params=params | 
|  | 102 | +                    ) | 
|  | 103 | +                    response.raise_for_status() | 
|  | 104 | +                    return response.json() | 
| 89 | 105 | 
 | 
| 90 |  | -        coingecko_responses = [] | 
| 91 |  | -        async with self._create_client() as client: | 
| 92 |  | -            response = await client.get(f"{self.base_url}/simple/price", params=params) | 
| 93 |  | -            response.raise_for_status() | 
| 94 |  | -            data = response.json() | 
|  | 106 | +        chunk_results = await asyncio.gather( | 
|  | 107 | +            *[fetch_chunk(chunk) for chunk in id_chunks], return_exceptions=True | 
|  | 108 | +        ) | 
| 95 | 109 | 
 | 
| 96 |  | -            for request in batch_to_fetch.requests: | 
| 97 |  | -                if ( | 
| 98 |  | -                    id := await self._get_coingecko_id_from_request( | 
| 99 |  | -                        request, platform_map, coin_map | 
| 100 |  | -                    ) | 
| 101 |  | -                ) not in data: | 
| 102 |  | -                    continue | 
| 103 |  | - | 
| 104 |  | -                try: | 
| 105 |  | -                    item = TokenPriceResponse( | 
| 106 |  | -                        **request.model_dump(), | 
| 107 |  | -                        vs_currency=batch.vs_currency, | 
| 108 |  | -                        price=float(data[id][batch.vs_currency.value]), | 
| 109 |  | -                        cache_status=CacheStatus.MISS, | 
| 110 |  | -                    ) | 
| 111 |  | -                except (KeyError, ValueError): | 
| 112 |  | -                    continue | 
|  | 110 | +        # Combine results from all chunks | 
|  | 111 | +        combined_data = {} | 
|  | 112 | +        for result in chunk_results: | 
|  | 113 | +            if isinstance(result, Exception): | 
|  | 114 | +                continue | 
|  | 115 | +            combined_data.update(result) | 
|  | 116 | + | 
|  | 117 | +        coingecko_responses = [] | 
|  | 118 | +        for request in batch_to_fetch.requests: | 
|  | 119 | +            if ( | 
|  | 120 | +                id := await self._get_coingecko_id_from_request( | 
|  | 121 | +                    request, platform_map, coin_map | 
|  | 122 | +                ) | 
|  | 123 | +            ) not in combined_data: | 
|  | 124 | +                continue | 
|  | 125 | + | 
|  | 126 | +            try: | 
|  | 127 | +                item = TokenPriceResponse( | 
|  | 128 | +                    **request.model_dump(), | 
|  | 129 | +                    vs_currency=batch.vs_currency, | 
|  | 130 | +                    price=float(combined_data[id][batch.vs_currency.value]), | 
|  | 131 | +                    cache_status=CacheStatus.MISS, | 
|  | 132 | +                ) | 
|  | 133 | +            except (KeyError, ValueError): | 
|  | 134 | +                continue | 
| 113 | 135 | 
 | 
| 114 |  | -                coingecko_responses.append(item) | 
|  | 136 | +            coingecko_responses.append(item) | 
| 115 | 137 | 
 | 
| 116 | 138 |         await TokenPriceCache.set(coingecko_responses) | 
| 117 | 139 |         results.extend(coingecko_responses) | 
|  | 
0 commit comments