diff --git a/src/block_loader.py b/src/block_loader.py new file mode 100644 index 0000000..cb6738c --- /dev/null +++ b/src/block_loader.py @@ -0,0 +1,30 @@ +from arq.connections import RedisSettings + +from src.config import main_config +from src.services.tonapi import TonAPIClient + + +async def load_block(ctx, block_id): + api = TonAPIClient() + txs_data = api.tx_by_block(block_id) + # todo: save data to the DB + + +async def main(): + pass + + +async def startup(ctx): + pass + + +async def shutdown(ctx): + pass + + +class WorkerSettings: + redis_settings = RedisSettings.from_dsn(main_config().cache.redis_url) + functions = [load_block] + + on_startup = startup + on_shutdown = shutdown diff --git a/src/services/rate_limiter.py b/src/services/rate_limiter.py new file mode 100644 index 0000000..c783079 --- /dev/null +++ b/src/services/rate_limiter.py @@ -0,0 +1,23 @@ +from typing import Union + + +def rate_limiter(limit: Union[int, float] = 1): + def decorator_function(func): + import time + + last_request_time = 0 + + def wrapper(*args, **kwargs): + nonlocal last_request_time + + interval = 1 / limit + current_time = time.time() + time_since_last_request = current_time - last_request_time + if time_since_last_request < interval: + time.sleep(interval - time_since_last_request) + last_request_time = time.time() + return func(*args, **kwargs) + + return wrapper + + return decorator_function diff --git a/src/services/ton_scan_service.py b/src/services/ton_scan_service.py new file mode 100644 index 0000000..81bafb2 --- /dev/null +++ b/src/services/ton_scan_service.py @@ -0,0 +1,27 @@ +import requests + + +class TonAPIClientError(Exception): + pass + + +class TonAPIClient: + def __init__(self, base_url): + self.base_url = base_url + + def _request(self, address, params=None): + params = {} if params is None else params + end_point = self.base_url + address + response = requests.get(end_point, params) + if response.status_code != 200: + raise TonAPIClientError( + f"Ton Scan Service response with error code: {response.status_code}, error: {response.text}") + return response.json() + + def address_list(self, page=0, count=100): + addresses = self._request("/address_list/", {"page": page, "count": count}) + return addresses + + def address_details(self, address): + details = self._request(f"/address_details/{address}/") + return details diff --git a/src/services/tonapi.py b/src/services/tonapi.py new file mode 100644 index 0000000..6e4de78 --- /dev/null +++ b/src/services/tonapi.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +import requests +import os +from rate_limiter import rate_limiter + +TONAPI_KEY = os.getenv("TONAPI_KEY") or None +TONAPI_BASE_URL = "https://tonapi.io/v2" + + +class TonAPIClientError(Exception): + pass + + +class TonAPIClient: + def _request(self, address, params=None): + params = {} if params is None else params + + # Making a request to tonapi.io + if TONAPI_KEY: + response = requests.get(TONAPI_BASE_URL + address, params, headers={"Authorization": f"Bearer {TONAPI_KEY}"}) + else: + response = requests.get(TONAPI_BASE_URL + address, params) + + # Checking the response + if response.status_code != requests.codes.ok: + raise TonAPIClientError( + f"Tonapi response with error code: {response.status_code}, error: {response.text}") + return response.json() + + @rate_limiter(limit=0.9) + def tx_by_block(self, block_id): + """ + Find transactions by block id. + + :param str block_id: Block id e.g. "(-1,8000000000000000,4234234)" + """ + request_url = f"/blockchain/blocks/{block_id}/transactions" + transactions = self._request(request_url) + return transactions + + @rate_limiter(limit=0.9) + def tx_by_account(self, account_id): + """ + Find transactions by account id. + + :param str account_id: Account id in any format + """ + request_url = f"/blockchain/accounts/{account_id}/transactions" + transactions = self._request(request_url) + return transactions + + @rate_limiter(limit=0.9) + def raw_request(self, request_url, params): + """ + Make any request to tonapi.io + + :param request_url: Endpoint. e.g. "/rates" + :param params: Any parameters. e.g. {"tokens": "ton", "currencies": "rub"} + """ + response = self._request(request_url, params) + return response diff --git a/tests/test_redis.py b/tests/test_redis.py index 045cfa7..694b2a8 100644 --- a/tests/test_redis.py +++ b/tests/test_redis.py @@ -5,13 +5,14 @@ import pytest from redis import asyncio +from src.config import main_config from src.databases.redis import RedisRepo @pytest.fixture(scope='module') def redis_client(): """Test connection provider.""" - redis = asyncio.Redis() + redis = asyncio.Redis().from_url(main_config().cache.redis_url) # try: yield RedisRepo(redis) # finally: