11import asyncio
22import logging
33from asyncio import AbstractEventLoop
4+ from functools import wraps
45from typing import Union , AsyncContextManager , Optional
56
67import aiohttp
1516 EtherscanClientError ,
1617 EtherscanClientApiError ,
1718 EtherscanClientProxyError ,
19+ EtherscanClientApiRateLimitError ,
1820)
1921from aioetherscan .url_builder import UrlBuilder
2022
2123
24+ def retry_limit_attempt (f ):
25+ @wraps (f )
26+ async def inner (self , * args , ** kwargs ):
27+ attempt = 1
28+ max_attempts = self ._url_builder .keys_count
29+ while True :
30+ try :
31+ return await f (self , * args , ** kwargs )
32+ except EtherscanClientApiRateLimitError as e :
33+ self ._logger .warning (f'Key daily limit exceeded, { attempt = } : { e } ' )
34+ if attempt >= max_attempts :
35+ raise e
36+ await asyncio .sleep (0.01 )
37+ self ._url_builder .rotate_api_key ()
38+
39+ return inner
40+
41+
2242class Network :
2343 def __init__ (
2444 self ,
@@ -48,9 +68,11 @@ async def close(self):
4868 if self ._retry_client is not None :
4969 await self ._retry_client .close ()
5070
71+ @retry_limit_attempt
5172 async def get (self , params : dict = None ) -> Union [dict , list , str ]:
5273 return await self ._request (METH_GET , params = self ._url_builder .filter_and_sign (params ))
5374
75+ @retry_limit_attempt
5476 async def post (self , data : dict = None ) -> Union [dict , list , str ]:
5577 return await self ._request (METH_POST , data = self ._url_builder .filter_and_sign (data ))
5678
@@ -68,6 +90,7 @@ async def _request(
6890 if self ._retry_client is None :
6991 self ._retry_client = self ._get_retry_client ()
7092 session_method = getattr (self ._retry_client , method .lower ())
93+
7194 async with self ._throttler :
7295 async with session_method (
7396 self ._url_builder .API_URL , params = params , data = data , proxy = self ._proxy
@@ -93,6 +116,10 @@ async def _handle_response(self, response: aiohttp.ClientResponse) -> Union[dict
93116 def _raise_if_error (response_json : dict ):
94117 if 'status' in response_json and response_json ['status' ] != '1' :
95118 message , result = response_json .get ('message' ), response_json .get ('result' )
119+
120+ if 'max daily rate limit reached' in result .lower ():
121+ raise EtherscanClientApiRateLimitError (message , result )
122+
96123 raise EtherscanClientApiError (message , result )
97124
98125 if 'error' in response_json :
0 commit comments