blockapi 2.10.1__tar.gz → 2.10.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. {blockapi-2.10.1 → blockapi-2.10.3}/PKG-INFO +3 -6
  2. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/__init__.py +0 -1
  3. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/nft/test_opensea.py +3 -3
  4. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/test_base.py +58 -0
  5. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/test_data.py +0 -6
  6. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/test_enumerate_classes.py +1 -4
  7. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/__init__.py +0 -1
  8. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/nft/magic_eden.py +1 -1
  9. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/nft/opensea.py +1 -1
  10. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/base.py +26 -17
  11. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/coins.py +0 -9
  12. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/models.py +0 -1
  13. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi.egg-info/PKG-INFO +3 -6
  14. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi.egg-info/SOURCES.txt +1 -12
  15. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi.egg-info/requires.txt +2 -5
  16. {blockapi-2.10.1 → blockapi-2.10.3}/setup.py +3 -6
  17. blockapi-2.10.1/blockapi/test/v2/api/perpetual/test_perpetual.py +0 -94
  18. blockapi-2.10.1/blockapi/test/v2/api/synthetix/test_synthetix.py +0 -166
  19. blockapi-2.10.1/blockapi/v2/__init__.py +0 -0
  20. blockapi-2.10.1/blockapi/v2/api/covalenth/__init__.py +0 -0
  21. blockapi-2.10.1/blockapi/v2/api/perpetual/__init__.py +0 -1
  22. blockapi-2.10.1/blockapi/v2/api/perpetual/perp_abi.py +0 -264
  23. blockapi-2.10.1/blockapi/v2/api/perpetual/perpetual.py +0 -253
  24. blockapi-2.10.1/blockapi/v2/api/synthetix/__init__.py +0 -7
  25. blockapi-2.10.1/blockapi/v2/api/synthetix/synthetix.py +0 -431
  26. blockapi-2.10.1/blockapi/v2/api/synthetix/synthetix_abi.py +0 -5780
  27. blockapi-2.10.1/blockapi/v2/api/web3_utils.py +0 -142
  28. {blockapi-2.10.1 → blockapi-2.10.3}/LICENSE.md +0 -0
  29. {blockapi-2.10.1 → blockapi-2.10.3}/README.md +0 -0
  30. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/services.py +0 -0
  31. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/__init__.py +0 -0
  32. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/test_blockapi.py +0 -0
  33. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/test_num.py +0 -0
  34. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/test_random_user_agent.py +0 -0
  35. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/__init__.py +0 -0
  36. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/__init__.py +0 -0
  37. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/conftest.py +0 -0
  38. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/covalenth/__init__.py +0 -0
  39. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/covalenth/test_ethereum.py +0 -0
  40. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/fake_sleep_provider.py +0 -0
  41. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/nft/__init__.py +0 -0
  42. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/nft/test_magic_eden.py +0 -0
  43. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/nft/test_simple_hash.py +0 -0
  44. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/nft/test_unisat.py +0 -0
  45. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_bitaps_ltc.py +0 -0
  46. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_blockchain_info.py +0 -0
  47. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_blockchainos.py +0 -0
  48. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_blockchair_btc.py +0 -0
  49. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_blockchair_doge.py +0 -0
  50. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_blockchair_ltc.py +0 -0
  51. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_cosmos.py +0 -0
  52. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_ethplorer.py +0 -0
  53. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_haskoin.py +0 -0
  54. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_litecoinspace.py +0 -0
  55. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_multisources.py +0 -0
  56. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_optimistic_etherscan.py +0 -0
  57. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_solana.py +0 -0
  58. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_subscan_polkadot.py +0 -0
  59. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_sui.py +0 -0
  60. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_terra.py +0 -0
  61. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_trezor_btc.py +0 -0
  62. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/api/test_trezor_zec.py +0 -0
  63. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/test_blockchain_api.py +0 -0
  64. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/test_blockchain_mapping.py +0 -0
  65. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/test_generic.py +0 -0
  66. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test/v2/test_models.py +0 -0
  67. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/test_data.py +0 -0
  68. {blockapi-2.10.1/blockapi/test/v2/api/perpetual → blockapi-2.10.3/blockapi/utils}/__init__.py +0 -0
  69. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/utils/address.py +0 -0
  70. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/utils/datetime.py +0 -0
  71. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/utils/num.py +0 -0
  72. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/utils/user_agent.py +0 -0
  73. {blockapi-2.10.1/blockapi/test/v2/api/synthetix → blockapi-2.10.3/blockapi/v2}/__init__.py +0 -0
  74. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/bitaps.py +0 -0
  75. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/blockchain_info.py +0 -0
  76. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/blockchainos.py +0 -0
  77. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/blockchair.py +0 -0
  78. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/cosmos.py +0 -0
  79. {blockapi-2.10.1/blockapi/utils → blockapi-2.10.3/blockapi/v2/api/covalenth}/__init__.py +0 -0
  80. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/arbitrum.py +0 -0
  81. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/astar.py +0 -0
  82. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/avalanche.py +0 -0
  83. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/axie.py +0 -0
  84. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/base.py +0 -0
  85. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/binance_smart_chain.py +0 -0
  86. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/ethereum.py +0 -0
  87. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/fantom.py +0 -0
  88. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/heco.py +0 -0
  89. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/iotex.py +0 -0
  90. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/klaytn.py +0 -0
  91. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/moonbeam.py +0 -0
  92. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/palm.py +0 -0
  93. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/polygon.py +0 -0
  94. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/covalenth/rsk.py +0 -0
  95. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/debank.py +0 -0
  96. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/debank_maps.py +0 -0
  97. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/ethplorer.py +0 -0
  98. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/haskoin.py +0 -0
  99. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/litecoinspace.py +0 -0
  100. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/nft/__init__.py +0 -0
  101. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/nft/simple_hash.py +0 -0
  102. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/nft/unisat.py +0 -0
  103. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/optimistic_etherscan.py +0 -0
  104. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/solana.py +0 -0
  105. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/subscan.py +0 -0
  106. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/sui.py +0 -0
  107. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/terra.py +0 -0
  108. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/api/trezor.py +0 -0
  109. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/blockchain_mapping.py +0 -0
  110. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi/v2/coin_mapping.py +0 -0
  111. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi.egg-info/dependency_links.txt +0 -0
  112. {blockapi-2.10.1 → blockapi-2.10.3}/blockapi.egg-info/top_level.txt +0 -0
  113. {blockapi-2.10.1 → blockapi-2.10.3}/pyproject.toml +0 -0
  114. {blockapi-2.10.1 → blockapi-2.10.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blockapi
3
- Version: 2.10.1
3
+ Version: 2.10.3
4
4
  Summary: BlockAPI library
5
5
  Home-page: https://github.com/crypkit/blockapi
6
6
  Author: Devmons s.r.o.
@@ -9,18 +9,15 @@ Description-Content-Type: text/markdown
9
9
  License-File: LICENSE.md
10
10
  Requires-Dist: requests<3.0,>=2.28
11
11
  Requires-Dist: python-dateutil>=2.8.0
12
+ Requires-Dist: cytoolz>=0.12.0
13
+ Requires-Dist: eth-utils>=2.0.0
12
14
  Requires-Dist: coinaddrng==1.1.1
13
- Requires-Dist: web3<8.0.0,>=5.2.2
14
- Requires-Dist: bs4>=0.0.1
15
- Requires-Dist: lxml>=4.4.1
16
15
  Requires-Dist: pydantic>=1.10.2
17
- Requires-Dist: marko<2.0.0,>=1.3.0
18
16
  Requires-Dist: fake_useragent>=1.1.3
19
17
  Requires-Dist: pytest
20
18
  Requires-Dist: pytest-vcr
21
19
  Requires-Dist: requests_mock>=1.9.3
22
20
  Requires-Dist: attrs<23.0.0,>=17.4.0
23
- Requires-Dist: solders>=0.22.0
24
21
  Requires-Dist: base58>=2.1.0
25
22
  Requires-Dist: aiohttp<3.14
26
23
  Dynamic: author
@@ -1,4 +1,3 @@
1
- import inspect
2
1
  import random
3
2
 
4
3
  import coinaddrng
@@ -84,7 +84,7 @@ def test_fetch_ntfs(
84
84
  nfts = api.fetch_nfts(nfts_test_address)
85
85
  assert len(nfts.data) == 2
86
86
  assert len(fake_sleep_provider.calls)
87
- assert fake_sleep_provider.calls[0] == ('https://api.opensea.io/', 0.25)
87
+ assert fake_sleep_provider.calls[0] == ('https://api.opensea.io/', 0.75)
88
88
 
89
89
 
90
90
  def test_fetch_ntfs_error_response(requests_mock, api, fake_sleep_provider):
@@ -96,7 +96,7 @@ def test_fetch_ntfs_error_response(requests_mock, api, fake_sleep_provider):
96
96
  nfts = api.fetch_nfts(nfts_test_address)
97
97
  assert len(nfts.data) == 0
98
98
  assert len(fake_sleep_provider.calls)
99
- assert fake_sleep_provider.calls[0] == ('https://api.opensea.io/', 0.25)
99
+ assert fake_sleep_provider.calls[0] == ('https://api.opensea.io/', 0.75)
100
100
 
101
101
 
102
102
  def test_fetch_offers(
@@ -114,7 +114,7 @@ def test_fetch_offers(
114
114
  offers = api.fetch_offers(test_collection_slug)
115
115
  assert len(offers.data) == 2
116
116
  assert len(fake_sleep_provider.calls)
117
- assert fake_sleep_provider.calls[0] == ('https://api.opensea.io/', 0.25)
117
+ assert fake_sleep_provider.calls[0] == ('https://api.opensea.io/', 0.75)
118
118
 
119
119
 
120
120
  def test_fetch_offers_error_response(requests_mock, api, offers_response):
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from unittest.mock import MagicMock, patch
2
3
 
3
4
  import pytest
@@ -117,3 +118,60 @@ def mocked_500_with_success_response():
117
118
  def test_5xx_will_retry(customizable_api, mocked_500_with_success_response):
118
119
  response = customizable_api.get_data("test_method")
119
120
  assert response.status_code == 200
121
+
122
+
123
+ @pytest.fixture()
124
+ def mocked_429_with_success_response():
125
+ mocked_throttled = MagicMock()
126
+ mocked_throttled.status_code = 429
127
+ mocked_throttled.raise_for_status.side_effect = HTTPError(
128
+ "test_method", 429, "exception", {}, None
129
+ )
130
+ mocked_throttled.json.return_value = {}
131
+ mocked_throttled.headers = {}
132
+
133
+ mocked_success = MagicMock()
134
+ mocked_success.status_code = 200
135
+ mocked_success.json.return_value = {}
136
+ mocked_success.headers = {}
137
+
138
+ with patch('blockapi.v2.base.CustomizableBlockchainApi._get_response') as patched:
139
+ patched.side_effect = [
140
+ mocked_throttled,
141
+ mocked_success,
142
+ ]
143
+ yield patched
144
+
145
+
146
+ def test_429_retried_logs_warning_not_error(
147
+ customizable_api, mocked_429_with_success_response, caplog
148
+ ):
149
+ with caplog.at_level(logging.WARNING, logger='blockapi.v2.base'):
150
+ response = customizable_api.get_data("test_method")
151
+
152
+ assert response.status_code == 200
153
+ assert not any(r.levelno >= logging.ERROR for r in caplog.records)
154
+ assert any(r.levelno == logging.WARNING for r in caplog.records)
155
+
156
+
157
+ @pytest.fixture()
158
+ def mocked_429_only_response():
159
+ mocked_throttled = MagicMock()
160
+ mocked_throttled.status_code = 429
161
+ mocked_throttled.raise_for_status.side_effect = HTTPError(
162
+ "test_method", 429, "exception", {}, None
163
+ )
164
+ mocked_throttled.json.return_value = {}
165
+ mocked_throttled.headers = {}
166
+
167
+ with patch('blockapi.v2.base.CustomizableBlockchainApi._get_response') as patched:
168
+ patched.side_effect = [mocked_throttled] * 5
169
+ yield patched
170
+
171
+
172
+ def test_429_exhausted_logs_error(customizable_api, mocked_429_only_response, caplog):
173
+ with caplog.at_level(logging.WARNING, logger='blockapi.v2.base'):
174
+ response = customizable_api.get_data("test_method")
175
+
176
+ assert response.status_code == 429
177
+ assert any(r.levelno == logging.ERROR for r in caplog.records)
@@ -10,7 +10,6 @@ from blockapi.v2.api import (
10
10
  EthplorerApi,
11
11
  KusamaSubscanApi,
12
12
  OptimismEtherscanApi,
13
- PerpetualApi,
14
13
  PolkadotSubscanApi,
15
14
  SolanaApi,
16
15
  SubscanApi,
@@ -32,7 +31,6 @@ from blockapi.v2.api.covalenth.moonbeam import MoonBeamCovalentApi
32
31
  from blockapi.v2.api.covalenth.palm import PalmCovalentApi
33
32
  from blockapi.v2.api.covalenth.polygon import PolygonCovalentApi
34
33
  from blockapi.v2.api.covalenth.rsk import RskCovalentApi
35
- from blockapi.v2.api.synthetix import SynthetixApi
36
34
 
37
35
  # TODO create method for auto loading all classes
38
36
  from blockapi.v2.base import BalanceMixin, IBalance
@@ -93,8 +91,6 @@ API_CLASSES = [
93
91
  PolkadotSubscanApi,
94
92
  KusamaSubscanApi,
95
93
  CosmosApi,
96
- SynthetixApi,
97
- PerpetualApi,
98
94
  BlockchairBitcoinApi,
99
95
  ]
100
96
 
@@ -187,8 +183,6 @@ def yield_api_balance_classes():
187
183
  CosmosApiBase,
188
184
  CovalentApiBase,
189
185
  DebankApi,
190
- PerpetualApi,
191
- SynthetixApi,
192
186
  SubscanApi,
193
187
  TrezorApi,
194
188
  ),
@@ -5,7 +5,7 @@ from blockapi.test.v2.test_data import (
5
5
  yield_api_balance_classes,
6
6
  yield_covalent_api_classes,
7
7
  )
8
- from blockapi.v2.api import DebankApi, EthplorerApi, PerpetualApi
8
+ from blockapi.v2.api import DebankApi, EthplorerApi
9
9
  from blockapi.v2.api.covalenth.arbitrum import ArbitrumCovalentApi
10
10
  from blockapi.v2.api.covalenth.astar import AstarCovalentApi
11
11
  from blockapi.v2.api.covalenth.avalanche import AvalancheCovalentApi
@@ -20,7 +20,6 @@ from blockapi.v2.api.covalenth.moonbeam import MoonBeamCovalentApi
20
20
  from blockapi.v2.api.covalenth.palm import PalmCovalentApi
21
21
  from blockapi.v2.api.covalenth.polygon import PolygonCovalentApi
22
22
  from blockapi.v2.api.covalenth.rsk import RskCovalentApi
23
- from blockapi.v2.api.synthetix import SynthetixApi
24
23
 
25
24
 
26
25
  def test_enumerate_subclasses():
@@ -29,8 +28,6 @@ def test_enumerate_subclasses():
29
28
  assert classes
30
29
  assert EthplorerApi.__name__ in classes
31
30
  assert DebankApi.__name__ not in classes
32
- assert PerpetualApi.__name__ not in classes
33
- assert SynthetixApi.__name__ not in classes
34
31
 
35
32
  for cls in classes:
36
33
  assert 'Base' not in cls
@@ -12,7 +12,6 @@ from blockapi.v2.api.ethplorer import EthplorerApi
12
12
  from blockapi.v2.api.haskoin import HaskoinApi
13
13
  from blockapi.v2.api.litecoinspace import LitecoinSpaceApi
14
14
  from blockapi.v2.api.optimistic_etherscan import OptimismEtherscanApi
15
- from blockapi.v2.api.perpetual import PerpetualApi, perp_contract_address
16
15
  from blockapi.v2.api.solana import SolanaApi, SolscanApi
17
16
  from blockapi.v2.api.subscan import KusamaSubscanApi, PolkadotSubscanApi, SubscanApi
18
17
  from blockapi.v2.api.terra import TerraApi
@@ -399,7 +399,7 @@ class MagicEdenApi(BlockchainApi, INftProvider, INftParser):
399
399
  return False
400
400
 
401
401
  retry = bool(
402
- [t for t in data.errors if str(t).upper() == 'SERVICE UNAVAILABLE']
402
+ [t for t in data.errors if 'SERVICE UNAVAILABLE' in str(t).upper()]
403
403
  )
404
404
  if retry:
405
405
  logger.warning('Service unavailable - will retry after long sleep')
@@ -75,7 +75,7 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
75
75
  api_options = ApiOptions(
76
76
  blockchain=Blockchain.ETHEREUM,
77
77
  base_url='https://api.opensea.io/',
78
- rate_limit=0.25, # 4 per second
78
+ rate_limit=0.75, # 4 requests per 3 seconds
79
79
  )
80
80
 
81
81
  supported_requests = {
@@ -113,15 +113,17 @@ class CustomizableBlockchainApi(ABC):
113
113
  self.sleep_provider.sleep(self.base_url, seconds=sleep_seconds)
114
114
  continue
115
115
  except HTTPError:
116
- logger.error(f"Request failed with http error: {response.status_code}")
117
-
118
116
  retries -= 1
119
117
 
120
- if (
121
- retries <= 0
122
- or (response.status_code < 500 and response.status_code != 429)
123
- or not self.sleep_provider
124
- ):
118
+ retryable = response.status_code == 429 or response.status_code >= 500
119
+
120
+ if retries <= 0 or not retryable or not self.sleep_provider:
121
+ # Genuine failure: out of retries, non-retryable status,
122
+ # or nothing to pace the retry with. This is the only path
123
+ # that surfaces an error to the caller, so log at ERROR.
124
+ logger.error(
125
+ f"Request failed with http error: {response.status_code}"
126
+ )
125
127
  time = self._get_response_time(response.headers)
126
128
  return FetchResult(
127
129
  status_code=response.status_code,
@@ -137,9 +139,12 @@ class CustomizableBlockchainApi(ABC):
137
139
  except ValueError:
138
140
  seconds = 60
139
141
 
142
+ # Retryable error that will be retried, so this is expected
143
+ # noise (e.g. 429 throttling) rather than a failure: WARNING.
140
144
  logger.warning(
141
- f'Too Many Requests: Will retry after {seconds}s sleep.'
142
- f' Remaining attempts {retries}.'
145
+ f'Request failed with retryable http error'
146
+ f' {response.status_code}: will retry after {seconds}s'
147
+ f' sleep. Remaining attempts {retries}.'
143
148
  )
144
149
  self.sleep_provider.sleep(self.base_url, seconds=seconds)
145
150
  continue
@@ -193,15 +198,19 @@ class CustomizableBlockchainApi(ABC):
193
198
  def _get_reason(response):
194
199
  reason = response.reason
195
200
  if not reason and response.status_code >= 400:
196
- return f'Error {response.status_code}'
201
+ reason = f'Error {response.status_code}'
197
202
 
198
- if not isinstance(reason, bytes):
199
- return reason
200
-
201
- try:
202
- return reason.decode("utf-8")
203
- except UnicodeDecodeError:
204
- return reason.decode("iso-8859-1")
203
+ if isinstance(reason, bytes):
204
+ try:
205
+ reason = reason.decode("utf-8")
206
+ except UnicodeDecodeError:
207
+ reason = reason.decode("iso-8859-1")
208
+
209
+ # Append the response body so the error carries the server's own
210
+ # message instead of just the HTTP status phrase. Truncated to keep
211
+ # logs readable.
212
+ body = (response.text or '').strip()
213
+ return f'{reason}: {body[:500]}' if body else reason
205
214
 
206
215
  @staticmethod
207
216
  def _raise_from_response(response: Response) -> None:
@@ -431,15 +431,6 @@ COIN_DOGE = Coin(
431
431
  info=CoinInfo(coingecko_id=CoingeckoId.DOGECOIN),
432
432
  )
433
433
 
434
- COIN_SNX = Coin(
435
- symbol='SNX',
436
- name='Synthetix',
437
- decimals=18,
438
- address='0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f',
439
- blockchain=Blockchain.ETHEREUM,
440
- info=CoinInfo(coingecko_id=CoingeckoId.SYNTHETIX),
441
- )
442
-
443
434
 
444
435
  COIN_CANTO = Coin(
445
436
  symbol='CANTO',
@@ -473,7 +473,6 @@ class CoingeckoId(str, Enum):
473
473
  SOLANA = 'solana'
474
474
  SONGBIRD = 'songbird'
475
475
  SUI = 'sui'
476
- SYNTHETIX = 'havven'
477
476
  TELOS = 'telos'
478
477
  USDC = 'usd-coin'
479
478
  WANCHAIN = 'wanchain'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blockapi
3
- Version: 2.10.1
3
+ Version: 2.10.3
4
4
  Summary: BlockAPI library
5
5
  Home-page: https://github.com/crypkit/blockapi
6
6
  Author: Devmons s.r.o.
@@ -9,18 +9,15 @@ Description-Content-Type: text/markdown
9
9
  License-File: LICENSE.md
10
10
  Requires-Dist: requests<3.0,>=2.28
11
11
  Requires-Dist: python-dateutil>=2.8.0
12
+ Requires-Dist: cytoolz>=0.12.0
13
+ Requires-Dist: eth-utils>=2.0.0
12
14
  Requires-Dist: coinaddrng==1.1.1
13
- Requires-Dist: web3<8.0.0,>=5.2.2
14
- Requires-Dist: bs4>=0.0.1
15
- Requires-Dist: lxml>=4.4.1
16
15
  Requires-Dist: pydantic>=1.10.2
17
- Requires-Dist: marko<2.0.0,>=1.3.0
18
16
  Requires-Dist: fake_useragent>=1.1.3
19
17
  Requires-Dist: pytest
20
18
  Requires-Dist: pytest-vcr
21
19
  Requires-Dist: requests_mock>=1.9.3
22
20
  Requires-Dist: attrs<23.0.0,>=17.4.0
23
- Requires-Dist: solders>=0.22.0
24
21
  Requires-Dist: base58>=2.1.0
25
22
  Requires-Dist: aiohttp<3.14
26
23
  Dynamic: author
@@ -51,10 +51,6 @@ blockapi/test/v2/api/nft/test_magic_eden.py
51
51
  blockapi/test/v2/api/nft/test_opensea.py
52
52
  blockapi/test/v2/api/nft/test_simple_hash.py
53
53
  blockapi/test/v2/api/nft/test_unisat.py
54
- blockapi/test/v2/api/perpetual/__init__.py
55
- blockapi/test/v2/api/perpetual/test_perpetual.py
56
- blockapi/test/v2/api/synthetix/__init__.py
57
- blockapi/test/v2/api/synthetix/test_synthetix.py
58
54
  blockapi/utils/__init__.py
59
55
  blockapi/utils/address.py
60
56
  blockapi/utils/datetime.py
@@ -83,7 +79,6 @@ blockapi/v2/api/subscan.py
83
79
  blockapi/v2/api/sui.py
84
80
  blockapi/v2/api/terra.py
85
81
  blockapi/v2/api/trezor.py
86
- blockapi/v2/api/web3_utils.py
87
82
  blockapi/v2/api/covalenth/__init__.py
88
83
  blockapi/v2/api/covalenth/arbitrum.py
89
84
  blockapi/v2/api/covalenth/astar.py
@@ -104,10 +99,4 @@ blockapi/v2/api/nft/__init__.py
104
99
  blockapi/v2/api/nft/magic_eden.py
105
100
  blockapi/v2/api/nft/opensea.py
106
101
  blockapi/v2/api/nft/simple_hash.py
107
- blockapi/v2/api/nft/unisat.py
108
- blockapi/v2/api/perpetual/__init__.py
109
- blockapi/v2/api/perpetual/perp_abi.py
110
- blockapi/v2/api/perpetual/perpetual.py
111
- blockapi/v2/api/synthetix/__init__.py
112
- blockapi/v2/api/synthetix/synthetix.py
113
- blockapi/v2/api/synthetix/synthetix_abi.py
102
+ blockapi/v2/api/nft/unisat.py
@@ -1,16 +1,13 @@
1
1
  requests<3.0,>=2.28
2
2
  python-dateutil>=2.8.0
3
+ cytoolz>=0.12.0
4
+ eth-utils>=2.0.0
3
5
  coinaddrng==1.1.1
4
- web3<8.0.0,>=5.2.2
5
- bs4>=0.0.1
6
- lxml>=4.4.1
7
6
  pydantic>=1.10.2
8
- marko<2.0.0,>=1.3.0
9
7
  fake_useragent>=1.1.3
10
8
  pytest
11
9
  pytest-vcr
12
10
  requests_mock>=1.9.3
13
11
  attrs<23.0.0,>=17.4.0
14
- solders>=0.22.0
15
12
  base58>=2.1.0
16
13
  aiohttp<3.14
@@ -6,7 +6,7 @@ with open("README.md", "r") as f:
6
6
 
7
7
  PACKAGES = find_packages(where='.')
8
8
 
9
- __version__ = "2.10.1"
9
+ __version__ = "2.10.3"
10
10
 
11
11
  setuptools.setup(
12
12
  name='blockapi',
@@ -20,18 +20,15 @@ setuptools.setup(
20
20
  install_requires=[
21
21
  'requests>=2.28,<3.0',
22
22
  'python-dateutil>=2.8.0',
23
+ 'cytoolz>=0.12.0',
24
+ 'eth-utils>=2.0.0',
23
25
  'coinaddrng==1.1.1',
24
- 'web3>=5.2.2,<8.0.0',
25
- 'bs4>=0.0.1',
26
- 'lxml>=4.4.1',
27
26
  'pydantic>=1.10.2',
28
- 'marko>=1.3.0,<2.0.0',
29
27
  'fake_useragent>=1.1.3',
30
28
  'pytest',
31
29
  'pytest-vcr',
32
30
  'requests_mock>=1.9.3',
33
31
  'attrs>=17.4.0,<23.0.0',
34
- 'solders>=0.22.0',
35
32
  'base58>=2.1.0',
36
33
  # vcrpy incompatible with 3.14, remove once vcrpy>8.1.1 is out
37
34
  'aiohttp<3.14',
@@ -1,94 +0,0 @@
1
- import os
2
-
3
- import pytest
4
- from _decimal import Decimal
5
-
6
- from blockapi.v2.api import PerpetualApi, perp_contract_address
7
- from blockapi.v2.base import ApiException
8
- from blockapi.v2.models import FetchResult
9
-
10
-
11
- @pytest.fixture
12
- def perp_api():
13
- return PerpetualApi('http://localhost:2048/')
14
-
15
-
16
- test_address = '0x134089B387E22f52b1e06CC80d9a5F622032EF74'
17
-
18
-
19
- @pytest.mark.skip(
20
- reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
21
- )
22
- def test_perp_contract_address():
23
- contract = perp_contract_address('PERP')
24
- assert contract == '0xbC396689893D065F41bc2C6EcbeE5e0085233447'
25
-
26
-
27
- @pytest.mark.skip(
28
- reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
29
- )
30
- def test_perp_invalid_contract_raises():
31
- with pytest.raises(ValueError, match='Invalid contract name.'):
32
- perp_contract_address("abc")
33
-
34
-
35
- def test_perp_has_coin(perp_api):
36
- assert perp_api.coin.symbol == 'PERP'
37
-
38
-
39
- def filter_infura_key(request):
40
- if 'infura.io' in request.host:
41
- request.uri = 'https://mainnet.infura.io/v3/API_KEY_FILTERED'
42
- return request
43
-
44
-
45
- @pytest.mark.integration
46
- @pytest.mark.skip(
47
- reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
48
- )
49
- def test_fetch():
50
- key = os.environ.get('INFURA_API_KEY')
51
- api = PerpetualApi(f'https://mainnet.infura.io/v3/{key}')
52
- raw = api.fetch_balances(test_address)
53
- assert raw.data
54
-
55
-
56
- @pytest.mark.integration
57
- @pytest.mark.skip(
58
- reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
59
- )
60
- def test_fetch_error():
61
- api = PerpetualApi(f'https://mainnet.infura.io/v3/no-key')
62
- raw = api.fetch_balances(test_address)
63
- assert raw.status_code == 401
64
- assert (
65
- raw.errors[0]
66
- == '401 Client Error: Unauthorized for url: https://mainnet.infura.io/v3/no-key'
67
- )
68
-
69
-
70
- @pytest.mark.skip(
71
- reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
72
- )
73
- def test_fetch_error_raises_from_get_balances():
74
- api = PerpetualApi(f'https://mainnet.infura.io/v3/no-key')
75
- with pytest.raises(ApiException) as exc:
76
- api.get_balance(test_address)
77
-
78
- assert exc.match('401 Client Error: Unauthorized for url')
79
-
80
-
81
- def test_parse(perp_api):
82
- raw = FetchResult(
83
- status_code=200,
84
- data=dict(
85
- staking_claimable='1.10',
86
- vesting_claimable='2.02',
87
- vesting_locked='3.30',
88
- ),
89
- )
90
-
91
- parsed = perp_api.parse_balances(raw)
92
-
93
- assert parsed.data[0].balance == Decimal('3.12')
94
- assert parsed.data[1].balance == Decimal('3.30')