blockapi 2.10.1__tar.gz → 2.10.2__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.2}/PKG-INFO +1 -1
  2. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/nft/test_opensea.py +3 -3
  3. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/test_base.py +58 -0
  4. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/nft/magic_eden.py +1 -1
  5. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/nft/opensea.py +1 -1
  6. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/base.py +26 -17
  7. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi.egg-info/PKG-INFO +1 -1
  8. {blockapi-2.10.1 → blockapi-2.10.2}/setup.py +1 -1
  9. {blockapi-2.10.1 → blockapi-2.10.2}/LICENSE.md +0 -0
  10. {blockapi-2.10.1 → blockapi-2.10.2}/README.md +0 -0
  11. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/__init__.py +0 -0
  12. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/services.py +0 -0
  13. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/__init__.py +0 -0
  14. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/test_blockapi.py +0 -0
  15. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/test_num.py +0 -0
  16. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/test_random_user_agent.py +0 -0
  17. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/__init__.py +0 -0
  18. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/__init__.py +0 -0
  19. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/conftest.py +0 -0
  20. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/covalenth/__init__.py +0 -0
  21. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/covalenth/test_ethereum.py +0 -0
  22. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/fake_sleep_provider.py +0 -0
  23. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/nft/__init__.py +0 -0
  24. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/nft/test_magic_eden.py +0 -0
  25. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/nft/test_simple_hash.py +0 -0
  26. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/nft/test_unisat.py +0 -0
  27. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/perpetual/__init__.py +0 -0
  28. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/perpetual/test_perpetual.py +0 -0
  29. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/synthetix/__init__.py +0 -0
  30. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/synthetix/test_synthetix.py +0 -0
  31. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_bitaps_ltc.py +0 -0
  32. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_blockchain_info.py +0 -0
  33. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_blockchainos.py +0 -0
  34. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_blockchair_btc.py +0 -0
  35. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_blockchair_doge.py +0 -0
  36. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_blockchair_ltc.py +0 -0
  37. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_cosmos.py +0 -0
  38. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_ethplorer.py +0 -0
  39. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_haskoin.py +0 -0
  40. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_litecoinspace.py +0 -0
  41. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_multisources.py +0 -0
  42. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_optimistic_etherscan.py +0 -0
  43. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_solana.py +0 -0
  44. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_subscan_polkadot.py +0 -0
  45. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_sui.py +0 -0
  46. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_terra.py +0 -0
  47. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_trezor_btc.py +0 -0
  48. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/api/test_trezor_zec.py +0 -0
  49. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/test_blockchain_api.py +0 -0
  50. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/test_blockchain_mapping.py +0 -0
  51. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/test_data.py +0 -0
  52. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/test_enumerate_classes.py +0 -0
  53. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/test_generic.py +0 -0
  54. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test/v2/test_models.py +0 -0
  55. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/test_data.py +0 -0
  56. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/utils/__init__.py +0 -0
  57. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/utils/address.py +0 -0
  58. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/utils/datetime.py +0 -0
  59. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/utils/num.py +0 -0
  60. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/utils/user_agent.py +0 -0
  61. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/__init__.py +0 -0
  62. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/__init__.py +0 -0
  63. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/bitaps.py +0 -0
  64. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/blockchain_info.py +0 -0
  65. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/blockchainos.py +0 -0
  66. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/blockchair.py +0 -0
  67. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/cosmos.py +0 -0
  68. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/__init__.py +0 -0
  69. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/arbitrum.py +0 -0
  70. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/astar.py +0 -0
  71. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/avalanche.py +0 -0
  72. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/axie.py +0 -0
  73. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/base.py +0 -0
  74. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/binance_smart_chain.py +0 -0
  75. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/ethereum.py +0 -0
  76. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/fantom.py +0 -0
  77. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/heco.py +0 -0
  78. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/iotex.py +0 -0
  79. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/klaytn.py +0 -0
  80. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/moonbeam.py +0 -0
  81. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/palm.py +0 -0
  82. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/polygon.py +0 -0
  83. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/covalenth/rsk.py +0 -0
  84. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/debank.py +0 -0
  85. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/debank_maps.py +0 -0
  86. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/ethplorer.py +0 -0
  87. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/haskoin.py +0 -0
  88. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/litecoinspace.py +0 -0
  89. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/nft/__init__.py +0 -0
  90. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/nft/simple_hash.py +0 -0
  91. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/nft/unisat.py +0 -0
  92. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/optimistic_etherscan.py +0 -0
  93. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/perpetual/__init__.py +0 -0
  94. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/perpetual/perp_abi.py +0 -0
  95. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/perpetual/perpetual.py +0 -0
  96. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/solana.py +0 -0
  97. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/subscan.py +0 -0
  98. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/sui.py +0 -0
  99. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/synthetix/__init__.py +0 -0
  100. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/synthetix/synthetix.py +0 -0
  101. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/synthetix/synthetix_abi.py +0 -0
  102. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/terra.py +0 -0
  103. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/trezor.py +0 -0
  104. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/api/web3_utils.py +0 -0
  105. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/blockchain_mapping.py +0 -0
  106. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/coin_mapping.py +0 -0
  107. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/coins.py +0 -0
  108. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi/v2/models.py +0 -0
  109. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi.egg-info/SOURCES.txt +0 -0
  110. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi.egg-info/dependency_links.txt +0 -0
  111. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi.egg-info/requires.txt +0 -0
  112. {blockapi-2.10.1 → blockapi-2.10.2}/blockapi.egg-info/top_level.txt +0 -0
  113. {blockapi-2.10.1 → blockapi-2.10.2}/pyproject.toml +0 -0
  114. {blockapi-2.10.1 → blockapi-2.10.2}/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.2
4
4
  Summary: BlockAPI library
5
5
  Home-page: https://github.com/crypkit/blockapi
6
6
  Author: Devmons s.r.o.
@@ -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)
@@ -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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blockapi
3
- Version: 2.10.1
3
+ Version: 2.10.2
4
4
  Summary: BlockAPI library
5
5
  Home-page: https://github.com/crypkit/blockapi
6
6
  Author: Devmons s.r.o.
@@ -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.2"
10
10
 
11
11
  setuptools.setup(
12
12
  name='blockapi',
File without changes
File without changes
File without changes
File without changes