diff --git a/blockapi/v2/api/blockchair.py b/blockapi/v2/api/blockchair.py index 03ca9b29..e0ffd95e 100644 --- a/blockapi/v2/api/blockchair.py +++ b/blockapi/v2/api/blockchair.py @@ -1,7 +1,7 @@ from abc import ABC from typing import Iterable, List -from blockapi.v2.base import BalanceMixin, BlockchainApi, ITransactions +from blockapi.v2.base import BalanceMixin, BlockchainApi, ISleepProvider, ITransactions from blockapi.v2.coins import COIN_BTC, COIN_DOGE, COIN_LTC from blockapi.v2.models import ( ApiOptions, @@ -19,8 +19,8 @@ class BlockchairApi(BlockchainApi, BalanceMixin, ITransactions, ABC): - def __init__(self, offset=0, limit=10): - super().__init__() + def __init__(self, offset=0, limit=10, sleep_provider: ISleepProvider = None): + super().__init__(sleep_provider=sleep_provider) self._offset = offset self._limit = limit @@ -194,7 +194,9 @@ def _parse_transaction(self, address: str, tx: dict) -> TransactionItem: class BlockchairBitcoinApi(BlockchairApi): api_options = ApiOptions( - blockchain=Blockchain.BITCOIN, base_url='https://api.blockchair.com/bitcoin/' + blockchain=Blockchain.BITCOIN, + base_url='https://api.blockchair.com/bitcoin/', + rate_limit=1, ) coin = COIN_BTC @@ -202,7 +204,9 @@ class BlockchairBitcoinApi(BlockchairApi): class BlockchairDogecoinApi(BlockchairApi): api_options = ApiOptions( - blockchain=Blockchain.DOGECHAIN, base_url='https://api.blockchair.com/dogecoin/' + blockchain=Blockchain.DOGECHAIN, + base_url='https://api.blockchair.com/dogecoin/', + rate_limit=1, ) coin = COIN_DOGE @@ -210,7 +214,9 @@ class BlockchairDogecoinApi(BlockchairApi): class BlockchairLitecoinApi(BlockchairApi): api_options = ApiOptions( - blockchain=Blockchain.LITECOIN, base_url='https://api.blockchair.com/litecoin/' + blockchain=Blockchain.LITECOIN, + base_url='https://api.blockchair.com/litecoin/', + rate_limit=1, ) coin = COIN_LTC diff --git a/blockapi/v2/api/cosmos.py b/blockapi/v2/api/cosmos.py index 639d12a1..ceecced1 100644 --- a/blockapi/v2/api/cosmos.py +++ b/blockapi/v2/api/cosmos.py @@ -7,7 +7,7 @@ from requests import Session from blockapi.utils.num import to_decimal -from blockapi.v2.base import ApiOptions, BlockchainApi, IBalance +from blockapi.v2.base import ApiOptions, BlockchainApi, IBalance, ISleepProvider from blockapi.v2.coins import COIN_ATOM, COIN_CELESTIA, COIN_DYDX, COIN_OSMOSIS from blockapi.v2.models import AssetType, BalanceItem, Blockchain, Coin, CoinInfo @@ -102,8 +102,9 @@ def __init__( self, tokens_map: defaultdict[str, dict] = None, enable_token_mapping=True, + sleep_provider: ISleepProvider = None, ): - super().__init__() + super().__init__(sleep_provider=sleep_provider) self._tokens_map = tokens_map self.enable_token_mapping = enable_token_mapping diff --git a/blockapi/v2/api/covalenth/base.py b/blockapi/v2/api/covalenth/base.py index 36ed3c6b..fe0a8f18 100644 --- a/blockapi/v2/api/covalenth/base.py +++ b/blockapi/v2/api/covalenth/base.py @@ -4,7 +4,7 @@ from eth_utils import to_checksum_address -from blockapi.v2.base import BlockchainApi, IBalance +from blockapi.v2.base import BlockchainApi, IBalance, ISleepProvider from blockapi.v2.models import BalanceItem, Coin, CoinInfo logger = logging.getLogger(__name__) @@ -37,8 +37,8 @@ def coin(self): 'get_balance': '/v1/{chain_id}/address/{address}/balances_v2/' } - def __init__(self, api_key: str): - super().__init__(api_key) + def __init__(self, api_key: str, sleep_provider: ISleepProvider = None): + super().__init__(api_key, sleep_provider=sleep_provider) # Set http basic auth for requests. self._session.auth = (api_key, "") diff --git a/blockapi/v2/api/debank.py b/blockapi/v2/api/debank.py index 57a48c88..f9f01145 100644 --- a/blockapi/v2/api/debank.py +++ b/blockapi/v2/api/debank.py @@ -22,8 +22,10 @@ BalanceMixin, CustomizableBlockchainApi, IPortfolio, + ISleepProvider, ) from blockapi.v2.blockchain_mapping import get_blockchain_from_debank_chain +from blockapi.v2.coin_mapping import symbol_to_coin_map from blockapi.v2.models import ( AssetType, BalanceItem, @@ -31,18 +33,17 @@ Coin, CoingeckoId, CoinInfo, + DebankApp, + DebankModelApp, + DebankModelAppPortfolioItem, + DebankModelPredictionDetail, + DebankPrediction, FetchResult, ParseResult, Pool, PoolInfo, Protocol, - DebankApp, - DebankPrediction, - DebankModelAppPortfolioItem, - DebankModelApp, - DebankModelPredictionDetail, ) -from blockapi.v2.coin_mapping import symbol_to_coin_map logger = logging.getLogger(__name__) @@ -733,8 +734,9 @@ def __init__( is_all: bool, protocol_cache: Optional[DebankProtocolCache] = None, base_url: Optional[str] = None, + sleep_provider: ISleepProvider = None, ): - super().__init__(base_url=base_url) + super().__init__(base_url=base_url, sleep_provider=sleep_provider) self._is_all = bool(is_all) self._headers = {'AccessKey': api_key} diff --git a/blockapi/v2/api/ethplorer.py b/blockapi/v2/api/ethplorer.py index 1bbf14ed..7125cd53 100644 --- a/blockapi/v2/api/ethplorer.py +++ b/blockapi/v2/api/ethplorer.py @@ -2,7 +2,7 @@ from eth_utils import to_checksum_address -from blockapi.v2.base import ApiOptions, BalanceMixin, BlockchainApi +from blockapi.v2.base import ApiOptions, BalanceMixin, BlockchainApi, ISleepProvider from blockapi.v2.coins import COIN_ETH from blockapi.v2.models import ( BalanceItem, @@ -30,8 +30,8 @@ class EthplorerApi(BlockchainApi, BalanceMixin): supported_requests = {'get_info': '/getAddressInfo/{address}?apiKey={api_key}'} - def __init__(self, api_key: str = 'freekey'): - super().__init__(api_key) + def __init__(self, api_key: str = 'freekey', sleep_provider: ISleepProvider = None): + super().__init__(api_key, sleep_provider=sleep_provider) def fetch_balances(self, address: str) -> FetchResult: return self.get_data( diff --git a/blockapi/v2/api/nft/magic_eden.py b/blockapi/v2/api/nft/magic_eden.py index c38fde61..d701ba97 100644 --- a/blockapi/v2/api/nft/magic_eden.py +++ b/blockapi/v2/api/nft/magic_eden.py @@ -2,7 +2,13 @@ from decimal import Decimal from typing import Iterable, Optional, Tuple -from blockapi.v2.base import BlockchainApi, INftParser, INftProvider +from blockapi.v2.base import ( + BlockchainApi, + INftParser, + INftProvider, + ISleepProvider, + SleepProvider, +) from blockapi.v2.coins import COIN_SOL from blockapi.v2.models import ( ApiOptions, @@ -46,9 +52,10 @@ class MagicEdenApi(BlockchainApi, INftProvider, INftParser): coin_map = NotImplemented - def __init__(self, sleep_provider, max_listings=500, max_offers=500): - super().__init__() - self._sleep_provider = sleep_provider + def __init__( + self, sleep_provider: ISleepProvider = None, max_listings=500, max_offers=500 + ): + super().__init__(sleep_provider=sleep_provider or SleepProvider()) self.max_offers = max_offers if max_listings > 15000: @@ -67,7 +74,7 @@ def fetch_nfts(self, address: str) -> FetchResult: items = [] while True: - self._sleep_provider.sleep(self.base_url, self.api_options.rate_limit) + self.sleep_provider.sleep(self.base_url, self.api_options.rate_limit) data = self.get_data( 'get_nfts', address=address, @@ -131,7 +138,7 @@ def _parse_supply(s: str): def fetch_collection(self, collection: str) -> FetchResult: while True: - self._sleep_provider.sleep(self.base_url, self.api_options.rate_limit) + self.sleep_provider.sleep(self.base_url, self.api_options.rate_limit) data = self.get_data( 'get_collection', @@ -186,7 +193,7 @@ def fetch_offers( items = [] while True: - self._sleep_provider.sleep(self.base_url, self.api_options.rate_limit) + self.sleep_provider.sleep(self.base_url, self.api_options.rate_limit) logger.info(f'get_pools: {collection} offset={offset} limit={limit}') data = self.get_data( 'get_pools', slug=collection, offset=offset, limit=limit @@ -283,7 +290,7 @@ def fetch_listings( items = [] while True: - self._sleep_provider.sleep(self.base_url, self.api_options.rate_limit) + self.sleep_provider.sleep(self.base_url, self.api_options.rate_limit) data = self.get_data( 'get_listings', slug=collection, @@ -396,7 +403,7 @@ def _should_retry(self, data: FetchResult) -> bool: ) if retry: logger.warning('Service unavailable - will retry after long sleep') - self._sleep_provider.sleep(self.base_url, seconds=60) + self.sleep_provider.sleep(self.base_url, seconds=60) return True return False diff --git a/blockapi/v2/api/nft/opensea.py b/blockapi/v2/api/nft/opensea.py index 3ea5809f..90636e3c 100644 --- a/blockapi/v2/api/nft/opensea.py +++ b/blockapi/v2/api/nft/opensea.py @@ -100,7 +100,7 @@ def __init__( max_listings=500, max_offers=500, ): - super().__init__(api_key) + super().__init__(api_key, sleep_provider=sleep_provider or SleepProvider()) self._blockchain = blockchain self._opensea_chain = self.supported_blockchains_map.get(blockchain) @@ -108,7 +108,6 @@ def __init__( raise ApiException(f"Blockchain '{blockchain.value}' is not supported") self._headers = {'accept': 'application/json', 'x-api-key': api_key} - self._sleep_provider = sleep_provider or SleepProvider() self._limit = limit self.max_listings = max_listings @@ -264,7 +263,7 @@ def _yield_fetch_data( item_count = 0 while True: - self._sleep_provider.sleep(self.base_url, self.api_options.rate_limit) + self.sleep_provider.sleep(self.base_url, self.api_options.rate_limit) page_count += 1 logger.debug(f'Fetching page {page_count} of {key} from {cursor}') fetched, next_cursor = fetch_method(key, cursor) @@ -659,7 +658,7 @@ def _should_retry(self, data): logger.warning(f'Service unavailable - will retry after {seconds}s sleep') - self._sleep_provider.sleep(self.base_url, seconds=seconds) + self.sleep_provider.sleep(self.base_url, seconds=seconds) return True return False diff --git a/blockapi/v2/api/optimistic_etherscan.py b/blockapi/v2/api/optimistic_etherscan.py index 362b1fa3..dd3db986 100644 --- a/blockapi/v2/api/optimistic_etherscan.py +++ b/blockapi/v2/api/optimistic_etherscan.py @@ -3,7 +3,13 @@ from requests import Response from blockapi.utils.user_agent import get_random_user_agent -from blockapi.v2.base import ApiException, ApiOptions, BalanceMixin, BlockchainApi +from blockapi.v2.base import ( + ApiException, + ApiOptions, + BalanceMixin, + BlockchainApi, + ISleepProvider, +) from blockapi.v2.coins import COIN_ETH from blockapi.v2.models import BalanceItem, Blockchain, FetchResult, ParseResult @@ -25,8 +31,8 @@ class OptimismEtherscanApi(BlockchainApi, BalanceMixin): 'get_balance': '?module=account&action=balance&address={address}&tag=latest&apikey={api_key}' } - def __init__(self, api_key: str = ''): - super().__init__(api_key) + def __init__(self, api_key: str = '', sleep_provider: ISleepProvider = None): + super().__init__(api_key, sleep_provider=sleep_provider) def _parse_eth_balance(self, response: Dict) -> BalanceItem: return BalanceItem.from_api( diff --git a/blockapi/v2/api/perpetual/perpetual.py b/blockapi/v2/api/perpetual/perpetual.py index 4d846bae..468347af 100644 --- a/blockapi/v2/api/perpetual/perpetual.py +++ b/blockapi/v2/api/perpetual/perpetual.py @@ -18,6 +18,7 @@ BalanceMixin, CustomizableBlockchainApi, IBalance, + ISleepProvider, ) from blockapi.v2.coins import COIN_PERP from blockapi.v2.models import ( @@ -217,8 +218,8 @@ class PerpetualApi(CustomizableBlockchainApi, BalanceMixin): blockchain=Blockchain.ETHEREUM, base_url=None, rate_limit=0.2 ) - def __init__(self, base_url: str) -> None: - super().__init__(base_url=base_url) + def __init__(self, base_url: str, sleep_provider: ISleepProvider = None) -> None: + super().__init__(base_url=base_url, sleep_provider=sleep_provider) def fetch_balances(self, address: str) -> FetchResult: try: diff --git a/blockapi/v2/api/solana.py b/blockapi/v2/api/solana.py index 8df0a927..7333cd1b 100644 --- a/blockapi/v2/api/solana.py +++ b/blockapi/v2/api/solana.py @@ -14,6 +14,7 @@ BlockchainApi, CustomizableBlockchainApi, InvalidAddressException, + ISleepProvider, ) from blockapi.v2.coins import COIN_SOL from blockapi.v2.models import ( @@ -65,6 +66,7 @@ class SolanaApi(CustomizableBlockchainApi, BalanceMixin): api_options = ApiOptions( blockchain=Blockchain.SOLANA, base_url='https://api.mainnet-beta.solana.com/', + rate_limit=1, start_offset=0, max_items_per_page=1000, page_offset_step=1, @@ -85,8 +87,13 @@ class SolanaApi(CustomizableBlockchainApi, BalanceMixin): # ── Initialization ───────────────────────────────────────── - def __init__(self, base_url: Optional[str] = None, include_nfts: bool = False): - super().__init__(base_url) + def __init__( + self, + base_url: Optional[str] = None, + include_nfts: bool = False, + sleep_provider: ISleepProvider = None, + ): + super().__init__(base_url, sleep_provider=sleep_provider) self.include_nfts = include_nfts self._request_id = 0 @@ -419,6 +426,7 @@ class SolscanApi(BlockchainApi): api_options = ApiOptions( blockchain=Blockchain.SOLANA, base_url='https://api.solscan.io/', + rate_limit=1, start_offset=0, max_items_per_page=1000, page_offset_step=1, diff --git a/blockapi/v2/api/synthetix/synthetix.py b/blockapi/v2/api/synthetix/synthetix.py index 6f8cd882..2a89480b 100644 --- a/blockapi/v2/api/synthetix/synthetix.py +++ b/blockapi/v2/api/synthetix/synthetix.py @@ -27,7 +27,7 @@ ensure_checksum_address, get_eth_client, ) -from blockapi.v2.base import CustomizableBlockchainApi, IBalance +from blockapi.v2.base import CustomizableBlockchainApi, IBalance, ISleepProvider from blockapi.v2.coins import COIN_SNX from blockapi.v2.models import ApiOptions, AssetType, BalanceItem, Blockchain, Coin @@ -128,8 +128,10 @@ class SynthetixApi(CustomizableBlockchainApi, IBalance, ABC): decimals: Decimal = Decimal('18') coin = COIN_SNX - def __init__(self, network: str, api_url: str): - super().__init__(base_url=api_url) + def __init__( + self, network: str, api_url: str, sleep_provider: ISleepProvider = None + ): + super().__init__(base_url=api_url, sleep_provider=sleep_provider) self.network = network self.w3 = get_eth_client(api_url) @@ -400,20 +402,30 @@ def get_token_xchg_rates(self, synths: List[Synth]) -> Dict: class SynthetixMainnetApi(SynthetixApi): - api_options = ApiOptions(blockchain=Blockchain.ETHEREUM, base_url=None) + api_options = ApiOptions( + blockchain=Blockchain.ETHEREUM, base_url=None, rate_limit=1 + ) def __init__( self, api_url: str, + sleep_provider: ISleepProvider = None, ): - super().__init__(network="mainnet", api_url=api_url) + super().__init__( + network="mainnet", api_url=api_url, sleep_provider=sleep_provider + ) class SynthetixOptimismApi(SynthetixApi): - api_options = ApiOptions(blockchain=Blockchain.OPTIMISM, base_url=None) + api_options = ApiOptions( + blockchain=Blockchain.OPTIMISM, base_url=None, rate_limit=1 + ) def __init__( self, api_url: str, + sleep_provider: ISleepProvider = None, ): - super().__init__(network='optimism', api_url=api_url) + super().__init__( + network='optimism', api_url=api_url, sleep_provider=sleep_provider + ) diff --git a/blockapi/v2/api/terra.py b/blockapi/v2/api/terra.py index 71200979..62403378 100644 --- a/blockapi/v2/api/terra.py +++ b/blockapi/v2/api/terra.py @@ -76,6 +76,7 @@ class TerraFcdApi(BlockchainApi): api_options = ApiOptions( blockchain=Blockchain.TERRA, base_url='https://fcd.terra.dev/', + rate_limit=1, ) supported_requests = { @@ -202,6 +203,7 @@ class TerraMantleApi(BlockchainApi): api_options = ApiOptions( blockchain=Blockchain.TERRA, base_url='https://mantle.terra.dev', + rate_limit=1, ) # API uses post requests