From 7e6c02fd177bd4a2f243e6a3820f0ac0aca9d6ec Mon Sep 17 00:00:00 2001 From: Roman Fasakhov Date: Mon, 4 Jul 2022 16:05:47 +0300 Subject: [PATCH] pre-release draft --- .github/workflows/publish.yaml | 30 +++++++++ .github/workflows/test.yaml | 3 +- .gitignore | 3 + README.md | 110 ++++++++++++++++++++++++++++++++ ankr/__init__.py | 4 +- ankr/advanced_api.py | 88 ++++++++++++++++++++++++++ ankr/exceptions.py | 6 +- ankr/provider.py | 105 ++++++++----------------------- ankr/types.py | 111 +++++++++++++++++---------------- poetry.lock | 12 ++-- pyproject.toml | 31 ++------- setup.cfg | 21 +++++++ tests/test_provider.py | 20 ++++++ 13 files changed, 372 insertions(+), 172 deletions(-) create mode 100644 .github/workflows/publish.yaml create mode 100644 README.md create mode 100644 ankr/advanced_api.py create mode 100644 setup.cfg diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..711d666 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,30 @@ +name: publish + +on: + release: + types: [created] + +jobs: + publish: + runs-on: "ubuntu-latest" + steps: + - name: Check out repository + uses: actions/checkout@v2 + + - name: Set up python + id: setup-python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Build library + run: poetry build + + - name: Authenticate in PyPi + run: poetry config http-basic.pypi __token__ ${{ secrets.PYPI_TOKEN }} + + - name: Publish library + run: poetry publish diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a87af7d..7034e2a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -77,5 +77,4 @@ jobs: - name: Run tests run: | source .venv/bin/activate - pytest tests/ - coverage report \ No newline at end of file + pytest \ No newline at end of file diff --git a/.gitignore b/.gitignore index b6e4761..38dee03 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# Pycharm +.idea/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..80db1fc --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# ⚓️ Ankr Python SDK + +Compact Python library for interacting with Ankr's [Advanced APIs](https://www.ankr.com/advanced-api/). + +## Get started in 2 minutes + +#### 1. Install the package from PyPi + +```bash +pip install ankr-sdk +``` + +#### 2. Initialize the SDK + +```python3 +from ankr import AnkrAdvancedAPI, types + +ankr_api = AnkrAdvancedAPI() +``` + +####3. Use the sdk and call one of the supported methods + +```python3 +from ankr.types import BlockchainName + +nfts = ankr_api.get_nfts( + blockchain=BlockchainName.ETH, + wallet_address="0x0E11A192d574b342C51be9e306694C41547185DD", + filter=[ + {"0x700b4b9f39bb1faf5d0d16a20488f2733550bff4": []}, + {"0xd8682bfa6918b0174f287b888e765b9a1b4dc9c3": ["8937"]}, + ], +) +``` + +## Supported chains + +`ankr-sdk` supports the following chains at this time: + +- ETH: `"eth"` +- BSC: `"bsc"` +- Polygon: `"polygon"` +- Fantom: `"fantom"` +- Arbitrum: `"arbitrum"` +- Avalanche: `"avalanche"` +- Syscoin NEVM: `"syscoin"` + +## Available methods + +`ankr-sdk` supports the following methods: + +- [`get_nfts`](#get_nfts) +- [`get_logs`](#get_logs) +- [`get_blocks`](#get_blocks) + +#### `get_logs` + +Get logs matching the filter. + +```python3 +logs = ankr_api.get_logs( + blockchain="eth", + from_block="0xdaf6b1", + to_block=14350010, + address=["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"], + topics=[ + [], + ["0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff"], + ], + decode_logs=True, +) +``` + +#### `get_blocks` + +Query data about blocks within a specified range. + +```python3 +blocks = ankr_api.get_blocks( + blockchain="eth", + from_block=14500001, + to_block=14500001, + desc_order=True, + include_logs=True, + include_txs=True, + decode_logs=True, +) +``` + +#### `get_nfts` + +Get data about all the NFTs (collectibles) owned by a wallet. + +````python3 +nfts = ankr_api.get_nfts( + blockchain="eth", + wallet_address="0x0E11A192d574b342C51be9e306694C41547185DD", + filter=[ + {"0x700b4b9f39bb1faf5d0d16a20488f2733550bff4": []}, + {"0xd8682bfa6918b0174f287b888e765b9a1b4dc9c3": ["8937"]}, + ], +) +```` + + +### About API keys + +For now, Ankr is offering _free_ access to these APIs with no request limits i.e. you don't need an API key at this time. + +Later on, these APIs will become a part of Ankr Protocol's [Premium Plan](https://www.ankr.com/protocol/plan/). diff --git a/ankr/__init__.py b/ankr/__init__.py index a728069..515cfcd 100644 --- a/ankr/__init__.py +++ b/ankr/__init__.py @@ -1 +1,3 @@ -from .provider import AnkrAdvancedAPI +from __future__ import annotations + +from ankr.advanced_api import AnkrAdvancedAPI diff --git a/ankr/advanced_api.py b/ankr/advanced_api.py new file mode 100644 index 0000000..30bfc52 --- /dev/null +++ b/ankr/advanced_api.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +from typing import Any, Dict, Iterable, List, Optional + +from ankr import types +from ankr.provider import AnkrProvider + + +class AnkrAdvancedAPI: + def __init__( + self, + api_key: Optional[str] = None, + endpoint_uri: Optional[str] = None, + ) -> None: + self.provider = AnkrProvider(api_key or "", endpoint_uri) + + def get_logs( + self, + blockchain: types.BlockchainNames, + from_block: Optional[types.BlockNumber] = None, + to_block: Optional[types.BlockNumber] = None, + address: Optional[types.AddressOrAddresses] = None, + topics: Optional[types.Topics] = None, + decode_logs: Optional[bool] = None, + **kwargs: Any, + ) -> Iterable[types.Log]: + for reply in self.provider.call_method_paginated( + "ankr_getLogs", + types.GetLogsRequest( + blockchain=blockchain, + from_block=from_block, + to_block=to_block, + address=address, + topics=topics, + decode_logs=decode_logs, + **kwargs, + ), + types.GetLogsReply, + ): + yield from reply.logs + + def get_blocks( + self, + blockchain: types.BlockchainName, + from_block: Optional[types.BlockNumber] = None, + to_block: Optional[types.BlockNumber] = None, + desc_order: Optional[bool] = None, + include_logs: Optional[bool] = None, + include_txs: Optional[bool] = None, + decode_logs: Optional[bool] = None, + decode_tx_data: Optional[bool] = None, + **kwargs: Any, + ) -> List[types.Block]: + reply = self.provider.call_method( + "ankr_getBlocks", + types.GetBlocksRequest( + blockchain=blockchain, + from_block=from_block, + to_block=to_block, + desc_order=desc_order, + include_logs=include_logs, + include_txs=include_txs, + decode_logs=decode_logs, + decode_tx_data=decode_tx_data, + **kwargs, + ), + types.GetBlocksReply, + ) + return reply.blocks + + def get_nfts( + self, + blockchain: types.BlockchainNames, + wallet_address: str, + filter: Optional[List[Dict[str, List[str]]]] = None, + **kwargs: Any, + ) -> Iterable[types.Nft]: + for reply in self.provider.call_method_paginated( + "ankr_getNFTsByOwner", + types.GetNFTsByOwnerRequest( + blockchain=blockchain, + wallet_address=wallet_address, + filter=filter, + **kwargs, + ), + types.GetNFTsByOwnerReply, + ): + yield from reply.assets diff --git a/ankr/exceptions.py b/ankr/exceptions.py index b6b8e2d..05349ad 100644 --- a/ankr/exceptions.py +++ b/ankr/exceptions.py @@ -1,8 +1,8 @@ -from typing import Union +from __future__ import annotations from web3.types import RPCError class AdvancedAPIException(Exception): - def __init__(self, error: Union[RPCError, str]): - super().__init__(f"Failed to handle request: {error}") + def __init__(self, error: RPCError | str) -> None: + super().__init__(f"failed to handle request, {error}") diff --git a/ankr/provider.py b/ankr/provider.py index 1576b99..ecd8c2a 100644 --- a/ankr/provider.py +++ b/ankr/provider.py @@ -1,38 +1,27 @@ -from typing import ( - Optional, - Union, - Any, - List, - Iterable, - TypeVar, - Type, - Dict, -) +from __future__ import annotations -from eth_typing import ( - URI, -) +from typing import Any, Iterable, Type, TypeVar + +from eth_typing import URI from web3 import HTTPProvider -from web3.types import ( - RPCEndpoint, - RPCResponse, -) +from web3.types import RPCEndpoint, RPCResponse from ankr import types from ankr.exceptions import AdvancedAPIException - -TRequestPaginated = TypeVar("TRequestPaginated", bound=types.RequestPaginated) -TReplyPaginated = TypeVar("TReplyPaginated", bound=types.ReplyPaginated) +TRequest = TypeVar("TRequest", bound=types.RPCModel) +TReply = TypeVar("TReply", bound=types.RPCModel) +TRequestPaginated = TypeVar("TRequestPaginated", bound=types.RPCRequestPaginated) +TReplyPaginated = TypeVar("TReplyPaginated", bound=types.RPCReplyPaginated) class AnkrProvider(HTTPProvider): def __init__( self, api_key: str = "", - endpoint_uri: Optional[Union[URI, str]] = None, - request_kwargs: Optional[Any] = None, - session: Optional[Any] = None, + endpoint_uri: URI | str | None = None, + request_kwargs: Any | None = None, + session: Any = None, ) -> None: if endpoint_uri is None: endpoint_uri = "https://rpc.ankr.com/multichain/" @@ -46,7 +35,18 @@ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: raise AdvancedAPIException("returned no result") return response - def make_request_paginated( + def call_method( + self, + rpc: str, + request: TRequest, + reply_type: Type[TReply], + ) -> TReply: + request_dict = request.dict(by_alias=True, exclude_none=True) + response = self.make_request(RPCEndpoint(rpc), request_dict) + reply = reply_type(**response["result"]) + return reply + + def call_method_paginated( self, rpc: str, request: TRequestPaginated, @@ -60,59 +60,4 @@ def make_request_paginated( if reply.next_page_token: request.page_token = reply.next_page_token - yield from self.make_request_paginated( - RPCEndpoint(rpc), request, reply_type - ) - - -class AnkrAdvancedAPI: - def __init__( - self, - api_key: Optional[str] = None, - endpoint_uri: Optional[str] = None, - ) -> None: - self.provider = AnkrProvider(api_key or "", endpoint_uri) - - def get_logs( - self, - blockchain: types.BlockchainNames, - from_block: Optional[types.BlockNumber] = None, - to_block: Optional[types.BlockNumber] = None, - address: Optional[types.AddressOrAddresses] = None, - topics: Optional[types.Topics] = None, - decode_logs: Optional[bool] = None, - **kwargs: Any, - ) -> Iterable[types.Log]: - for reply in self.provider.make_request_paginated( - "ankr_getLogs", - types.GetLogsRequest( - blockchain=blockchain, - from_block=from_block, - to_block=to_block, - address=address, - topics=topics, - decode_logs=decode_logs, - **kwargs, - ), - types.GetLogsReply, - ): - yield from reply.logs - - def get_nfts( - self, - blockchain: types.BlockchainNames, - wallet_address: str, - filter: Optional[List[Dict[str, List[str]]]] = None, - **kwargs: Any, - ) -> Iterable[types.Nft]: - for reply in self.provider.make_request_paginated( - "ankr_getNFTsByOwner", - types.GetNFTsByOwnerRequest( - blockchain=blockchain, - wallet_address=wallet_address, - filter=filter, - **kwargs, - ), - types.GetNFTsByOwnerReply, - ): - yield from reply.assets + yield from self.call_method_paginated(RPCEndpoint(rpc), request, reply_type) diff --git a/ankr/types.py b/ankr/types.py index 3ce4eea..9f4f212 100644 --- a/ankr/types.py +++ b/ankr/types.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import enum from abc import ABC -from typing import List, Optional, Union, Dict, Iterable, Type +from typing import Dict, List, Optional, Type, Union import humps from pydantic import BaseModel @@ -16,38 +18,33 @@ class BlockchainName(str, enum.Enum): SYSCOIN = "syscoin" -BlockchainNames = Union[BlockchainName, List[BlockchainName], str] - - class BlockNumberName(str, enum.Enum): latest = "latest" earliest = "earliest" +BlockchainNames = Union[BlockchainName, List[BlockchainName], str] BlockNumber = Union[int, str, BlockNumberName] AddressOrAddresses = Union[str, List[str]] Topics = Union[str, List[Union[str, List[str]]]] -class JsonRPCModel(BaseModel): +class RPCModel(BaseModel): class Config: alias_generator = humps.camelize allow_population_by_field_name = True -class RequestPaginated(ABC, JsonRPCModel): +class RPCRequestPaginated(ABC, RPCModel): page_token: Optional[str] = None -class ReplyPaginated(ABC, JsonRPCModel): +class RPCReplyPaginated(ABC, RPCModel): next_page_token: Optional[str] = None _iter_type: Type - def __next__(self) -> Iterable: - ... - -class GetNFTsByOwnerRequest(RequestPaginated): +class GetNFTsByOwnerRequest(RPCRequestPaginated): blockchain: BlockchainNames wallet_address: str filter: Optional[List[Dict[str, List[str]]]] = None @@ -55,7 +52,7 @@ class GetNFTsByOwnerRequest(RequestPaginated): page_size: Optional[int] = None -class Attribute(JsonRPCModel): +class Attribute(RPCModel): trait_type: Optional[str] = None value: Optional[str] = None display_type: Optional[str] = None @@ -66,7 +63,7 @@ class Attribute(JsonRPCModel): rarity: Optional[str] = None -class Nft(JsonRPCModel): +class Nft(RPCModel): blockchain: BlockchainName name: str token_id: str @@ -80,19 +77,19 @@ class Nft(JsonRPCModel): traits: Optional[List[Attribute]] = None -class GetNFTsByOwnerReply(ReplyPaginated): +class GetNFTsByOwnerReply(RPCReplyPaginated): owner: str assets: List[Nft] next_page_token: str -class GetNFTMetadataRequest(JsonRPCModel): +class GetNFTMetadataRequest(RPCModel): blockchain: BlockchainName contract_address: str token_id: str -class NftAttributes(JsonRPCModel): +class NftAttributes(RPCModel): token_url: str image_url: str name: str @@ -101,19 +98,19 @@ class NftAttributes(JsonRPCModel): traits: Optional[List[Attribute]] = None -class NftMetadata(JsonRPCModel): +class NftMetadata(RPCModel): blockchain: BlockchainName contract_address: str token_id: str contract_type: int -class GetNFTMetadataReply(JsonRPCModel): +class GetNFTMetadataReply(RPCModel): metadata: Optional[NftMetadata] = None attributes: Optional[NftAttributes] = None -class Balance(JsonRPCModel): +class Balance(RPCModel): blockchain: str token_name: str token_symbol: str @@ -128,33 +125,33 @@ class Balance(JsonRPCModel): contract_address: Optional[str] = None -class GetAccountBalanceReply(ReplyPaginated): +class GetAccountBalanceReply(RPCReplyPaginated): total_balance_usd: str assets: List[Balance] next_page_token: Optional[str] = None -class GetAccountBalanceRequest(RequestPaginated): - blockchain: Union[BlockchainName, List[BlockchainName], None] +class GetAccountBalanceRequest(RPCRequestPaginated): + blockchain: Optional[Union[BlockchainName, List[BlockchainName]]] wallet_address: str page_token: Optional[str] = None page_size: Optional[int] = None -class GetTokenHoldersRequest(RequestPaginated): +class GetTokenHoldersRequest(RPCRequestPaginated): blockchain: BlockchainName contract_address: str page_token: Optional[str] = None page_size: Optional[int] = None -class HolderBalance(JsonRPCModel): +class HolderBalance(RPCModel): holder_address: str balance: str balance_raw_integer: str -class GetTokenHoldersReply(ReplyPaginated): +class GetTokenHoldersReply(RPCReplyPaginated): blockchain: BlockchainName contract_address: str token_decimals: int @@ -163,21 +160,21 @@ class GetTokenHoldersReply(ReplyPaginated): next_page_token: str -class GetTokenHoldersCountRequest(RequestPaginated): +class GetTokenHoldersCountRequest(RPCRequestPaginated): blockchain: BlockchainName contract_address: str page_token: Optional[str] = None page_size: Optional[int] = None -class DailyHolderCount(JsonRPCModel): +class DailyHolderCount(RPCModel): holder_count: int total_amount: str total_amount_raw_integer: str last_updated_at: str -class GetTokenHoldersCountReply(ReplyPaginated): +class GetTokenHoldersCountReply(RPCReplyPaginated): blockchain: BlockchainName contract_address: str token_decimals: int @@ -185,11 +182,11 @@ class GetTokenHoldersCountReply(ReplyPaginated): next_page_token: str -class GetCurrenciesRequest(JsonRPCModel): +class GetCurrenciesRequest(RPCModel): blockchain: BlockchainName -class CurrencyDetailsExtended(JsonRPCModel): +class CurrencyDetailsExtended(RPCModel): blockchain: BlockchainName address: Optional[str] name: str @@ -198,22 +195,22 @@ class CurrencyDetailsExtended(JsonRPCModel): thumbnail: str -class GetCurrenciesReply(JsonRPCModel): +class GetCurrenciesReply(RPCModel): currencies: List[CurrencyDetailsExtended] -class GetUsdPriceRequest(JsonRPCModel): +class GetUsdPriceRequest(RPCModel): blockchain: BlockchainName contract_address: str -class GetUsdPriceReply(JsonRPCModel): +class GetUsdPriceReply(RPCModel): usd_price: str blockchain: BlockchainName contract_address: Optional[str] = None -class EventInput(JsonRPCModel): +class EventInput(RPCModel): name: str type: str indexed: bool @@ -221,7 +218,7 @@ class EventInput(JsonRPCModel): value_decoded: str -class Event(JsonRPCModel): +class Event(RPCModel): name: str inputs: List[EventInput] anonymous: bool @@ -231,7 +228,7 @@ class Event(JsonRPCModel): verified: bool -class Log(JsonRPCModel): +class Log(RPCModel): address: str topics: List[str] data: str @@ -244,26 +241,26 @@ class Log(JsonRPCModel): event: Optional[Event] = None -class GetLogsReply(ReplyPaginated): +class GetLogsReply(RPCReplyPaginated): logs: List[Log] next_page_token: Optional[str] = None -class GetLogsRequest(RequestPaginated): +class GetLogsRequest(RPCRequestPaginated): blockchain: Union[BlockchainName, List[BlockchainName]] - from_block: Union[int, str, BlockNumberName, None] = None - to_block: Union[int, str, BlockNumberName, None] = None + from_block: Optional[BlockNumber] = None + to_block: Optional[BlockNumber] = None address: Optional[Union[str, List[str]]] = None - topics: Optional[Union[str, List[Union[str, List[str]]]]] = None + topics: Optional[Topics] = None page_token: Optional[str] = None page_size: Optional[int] = None decode_logs: Optional[bool] = None -class GetBlocksRequest(JsonRPCModel): +class GetBlocksRequest(RPCModel): blockchain: BlockchainName - from_block: Union[int, BlockNumberName, None] - to_block: Union[int, BlockNumberName, None] + from_block: Optional[BlockNumber] = None + to_block: Optional[BlockNumber] = None desc_order: Optional[bool] = None include_logs: Optional[bool] = None include_txs: Optional[bool] = None @@ -271,14 +268,14 @@ class GetBlocksRequest(JsonRPCModel): decode_tx_data: Optional[bool] = None -class MethodInput(JsonRPCModel): +class MethodInput(RPCModel): name: str type: str size: int value_decoded: str -class Method(JsonRPCModel): +class Method(RPCModel): name: str inputs: List[MethodInput] string: str @@ -287,9 +284,12 @@ class Method(JsonRPCModel): verified: bool -class Transaction(JsonRPCModel): +class Transaction(RPCModel): class Config: - fields = {"from": "from"} + fields = { + "from_address": "from", + "to_address": "to", + } v: str r: str @@ -299,7 +299,8 @@ class Config: gas_price: str input: str block_number: str - to: Optional[str] + to_address: Optional[str] + from_address: str transaction_index: str block_hash: str value: str @@ -317,9 +318,9 @@ class Config: method: Optional[Method] -class Block(JsonRPCModel): +class Block(RPCModel): blockchain: str - int: str + number: str hash: str parent_hash: str nonce: str @@ -341,17 +342,17 @@ class Block(JsonRPCModel): uncles: List[str] -class GetBlocksReply(JsonRPCModel): +class GetBlocksReply(RPCModel): blocks: List[Block] -class GetTransactionsByHashRequest(JsonRPCModel): - blockchain: Union[BlockchainName, List[BlockchainName], None] +class GetTransactionsByHashRequest(RPCModel): + blockchain: Optional[Union[BlockchainName, List[BlockchainName]]] transaction_hash: str include_logs: Optional[bool] = None decode_logs: Optional[bool] = None decode_tx_data: Optional[bool] = None -class GetTransactionsByHashReply(JsonRPCModel): +class GetTransactionsByHashReply(RPCModel): transactions: List[Transaction] diff --git a/poetry.lock b/poetry.lock index 236048b..2e9f32b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -178,7 +178,7 @@ test = ["hypothesis (>=4.18.0,<5)", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox [[package]] name = "eth-hash" -version = "0.3.2" +version = "0.3.3" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" category = "main" optional = false @@ -701,7 +701,7 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.2.0" +version = "4.3.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false @@ -994,8 +994,8 @@ eth-account = [ {file = "eth_account-0.5.8-py3-none-any.whl", hash = "sha256:e39c32e3028348e194e5724e7915d2a325328092920b9865165a3c9d642a7543"}, ] eth-hash = [ - {file = "eth-hash-0.3.2.tar.gz", hash = "sha256:3f40cecd5ead88184aa9550afc19d057f103728108c5102f592f8415949b5a76"}, - {file = "eth_hash-0.3.2-py3-none-any.whl", hash = "sha256:de7385148a8e0237ba1240cddbc06d53f56731140f8593bdb8429306f6b42271"}, + {file = "eth-hash-0.3.3.tar.gz", hash = "sha256:8cde211519ff1a98b46e9057cb909f12ab62e263eb30a0a94e2f7e1f46ac67a0"}, + {file = "eth_hash-0.3.3-py3-none-any.whl", hash = "sha256:3c884e4f788b38cc92cff05c4e43bc6b82686066f04ecfae0e11cdcbe5a283bd"}, ] eth-keyfile = [ {file = "eth-keyfile-0.5.1.tar.gz", hash = "sha256:939540efb503380bc30d926833e6a12b22c6750de80feef3720d79e5a79de47d"}, @@ -1435,8 +1435,8 @@ typed-ast = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ - {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, - {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, ] urllib3 = [ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, diff --git a/pyproject.toml b/pyproject.toml index 6625f79..ac47228 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,12 @@ [tool.poetry] -name = "ankr-python-sdk" -version = "0.1.0" +name = "ankr-sdk" +version = "0.1.3" description = "Compact Python library for interacting with Ankr's Advanced APIs." -authors = ["Roman Fasakhov "] +authors = ["Roman Fasakhov "] license = "MIT License" +packages = [ + { include = "ankr" }, +] [tool.poetry.dependencies] python = "^3.7.2" @@ -20,25 +23,3 @@ isort = "^5.10.1" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" - -[mypy] -ignore_missing_imports = true -warn_no_return = false -check_untyped_defs = true -warn_unused_ignores = true -disallow_untyped_defs = true -allow_redefinition = true -follow_imports = "skip" -exclude = "env|venv|venv.*|tests" -no_strict_optional = true - -[mypy-tests] -ignore_errors = true - -[flake8] -max-complexity = 8 -max-annotations-complexity = 4 -max-line-length = 120 -max-function-length = 100 -exclude = ["env", "venv", "./tests/*"] -per-file-ignores =["__init__.py: F401"] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4f0c7ba --- /dev/null +++ b/setup.cfg @@ -0,0 +1,21 @@ +[mypy] +ignore_missing_imports = True +warn_no_return = False +check_untyped_defs = True +warn_unused_ignores = True +disallow_untyped_defs = True +allow_redefinition = True +follow_imports = skip +exclude = env|venv|venv.*|tests|test_* + +[mypy-*.tests.*] +ignore_errors = True + +[flake8] +max-complexity = 8 +max-annotations-complexity = 4 +max-line-length = 120 +max-function-length = 100 +exclude = env,venv,pytest.ini +per-file-ignores = + __init__.py: F401 \ No newline at end of file diff --git a/tests/test_provider.py b/tests/test_provider.py index b5a93a8..2b8d459 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from ankr import AnkrAdvancedAPI, types @@ -33,6 +35,24 @@ def test_get_logs(): assert logs[0].event.name == "Deposit" +def test_get_blocks(): + advanced_api = AnkrAdvancedAPI() + blocks = advanced_api.get_blocks( + blockchain=types.BlockchainName.ETH, + from_block=14500001, + to_block=14500001, + desc_order=True, + include_logs=True, + include_txs=True, + decode_logs=True, + ) + + assert len(blocks) == 1 + assert blocks[0].transactions + assert len(blocks[0].transactions) == 99 + assert len(blocks[0].transactions[6].logs) == 1 + + def test_get_nfts(): advanced_api = AnkrAdvancedAPI() nfts = list(