web3 7.0.0b1__py3-none-any.whl → 7.7.0__py3-none-any.whl

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 (261) hide show
  1. ens/__init__.py +13 -2
  2. ens/_normalization.py +4 -4
  3. ens/async_ens.py +31 -21
  4. ens/base_ens.py +3 -1
  5. ens/contract_data.py +2 -2
  6. ens/ens.py +14 -11
  7. ens/exceptions.py +16 -29
  8. ens/specs/nf.json +1 -1
  9. ens/specs/normalization_spec.json +1 -1
  10. ens/utils.py +33 -41
  11. web3/__init__.py +23 -12
  12. web3/_utils/abi.py +162 -274
  13. web3/_utils/async_transactions.py +34 -20
  14. web3/_utils/batching.py +217 -0
  15. web3/_utils/blocks.py +6 -2
  16. web3/_utils/caching/__init__.py +12 -0
  17. web3/_utils/caching/caching_utils.py +433 -0
  18. web3/_utils/caching/request_caching_validation.py +287 -0
  19. web3/_utils/compat/__init__.py +2 -3
  20. web3/_utils/contract_sources/compile_contracts.py +1 -1
  21. web3/_utils/contract_sources/contract_data/ambiguous_function_contract.py +42 -0
  22. web3/_utils/contract_sources/contract_data/arrays_contract.py +3 -3
  23. web3/_utils/contract_sources/contract_data/bytes_contracts.py +5 -5
  24. web3/_utils/contract_sources/contract_data/constructor_contracts.py +7 -7
  25. web3/_utils/contract_sources/contract_data/contract_caller_tester.py +3 -3
  26. web3/_utils/contract_sources/contract_data/emitter_contract.py +3 -3
  27. web3/_utils/contract_sources/contract_data/event_contracts.py +50 -5
  28. web3/_utils/contract_sources/contract_data/extended_resolver.py +3 -3
  29. web3/_utils/contract_sources/contract_data/fallback_function_contract.py +3 -3
  30. web3/_utils/contract_sources/contract_data/function_name_tester_contract.py +3 -3
  31. web3/_utils/contract_sources/contract_data/math_contract.py +3 -3
  32. web3/_utils/contract_sources/contract_data/offchain_lookup.py +3 -3
  33. web3/_utils/contract_sources/contract_data/offchain_resolver.py +3 -3
  34. web3/_utils/contract_sources/contract_data/panic_errors_contract.py +3 -3
  35. web3/_utils/contract_sources/contract_data/payable_tester.py +3 -3
  36. web3/_utils/contract_sources/contract_data/receive_function_contracts.py +5 -5
  37. web3/_utils/contract_sources/contract_data/reflector_contracts.py +3 -3
  38. web3/_utils/contract_sources/contract_data/revert_contract.py +3 -3
  39. web3/_utils/contract_sources/contract_data/simple_resolver.py +3 -3
  40. web3/_utils/contract_sources/contract_data/storage_contract.py +3 -3
  41. web3/_utils/contract_sources/contract_data/string_contract.py +3 -3
  42. web3/_utils/contract_sources/contract_data/tuple_contracts.py +5 -5
  43. web3/_utils/contracts.py +172 -220
  44. web3/_utils/datatypes.py +5 -1
  45. web3/_utils/decorators.py +6 -1
  46. web3/_utils/empty.py +1 -1
  47. web3/_utils/encoding.py +16 -12
  48. web3/_utils/error_formatters_utils.py +5 -3
  49. web3/_utils/events.py +78 -72
  50. web3/_utils/fee_utils.py +1 -3
  51. web3/_utils/filters.py +24 -22
  52. web3/_utils/formatters.py +2 -2
  53. web3/_utils/http.py +8 -2
  54. web3/_utils/http_session_manager.py +314 -0
  55. web3/_utils/math.py +14 -15
  56. web3/_utils/method_formatters.py +161 -34
  57. web3/_utils/module.py +2 -1
  58. web3/_utils/module_testing/__init__.py +3 -2
  59. web3/_utils/module_testing/eth_module.py +736 -583
  60. web3/_utils/module_testing/go_ethereum_debug_module.py +128 -0
  61. web3/_utils/module_testing/module_testing_utils.py +81 -24
  62. web3/_utils/module_testing/persistent_connection_provider.py +702 -220
  63. web3/_utils/module_testing/utils.py +114 -33
  64. web3/_utils/module_testing/web3_module.py +438 -17
  65. web3/_utils/normalizers.py +13 -11
  66. web3/_utils/rpc_abi.py +10 -22
  67. web3/_utils/threads.py +8 -7
  68. web3/_utils/transactions.py +32 -25
  69. web3/_utils/type_conversion.py +5 -1
  70. web3/_utils/validation.py +20 -17
  71. web3/beacon/__init__.py +5 -0
  72. web3/beacon/api_endpoints.py +3 -0
  73. web3/beacon/async_beacon.py +29 -6
  74. web3/beacon/beacon.py +24 -6
  75. web3/contract/__init__.py +7 -0
  76. web3/contract/async_contract.py +285 -82
  77. web3/contract/base_contract.py +556 -258
  78. web3/contract/contract.py +295 -84
  79. web3/contract/utils.py +251 -55
  80. web3/datastructures.py +56 -41
  81. web3/eth/__init__.py +7 -0
  82. web3/eth/async_eth.py +89 -69
  83. web3/eth/base_eth.py +7 -3
  84. web3/eth/eth.py +43 -66
  85. web3/exceptions.py +158 -83
  86. web3/gas_strategies/time_based.py +8 -6
  87. web3/geth.py +53 -184
  88. web3/main.py +77 -43
  89. web3/manager.py +368 -101
  90. web3/method.py +43 -15
  91. web3/middleware/__init__.py +26 -8
  92. web3/middleware/attrdict.py +12 -22
  93. web3/middleware/base.py +55 -2
  94. web3/middleware/filter.py +45 -23
  95. web3/middleware/formatting.py +6 -3
  96. web3/middleware/names.py +4 -1
  97. web3/middleware/signing.py +15 -6
  98. web3/middleware/stalecheck.py +2 -1
  99. web3/module.py +62 -26
  100. web3/providers/__init__.py +21 -0
  101. web3/providers/async_base.py +93 -38
  102. web3/providers/base.py +85 -40
  103. web3/providers/eth_tester/__init__.py +5 -0
  104. web3/providers/eth_tester/defaults.py +2 -55
  105. web3/providers/eth_tester/main.py +57 -35
  106. web3/providers/eth_tester/middleware.py +16 -17
  107. web3/providers/ipc.py +42 -18
  108. web3/providers/legacy_websocket.py +27 -2
  109. web3/providers/persistent/__init__.py +7 -0
  110. web3/providers/persistent/async_ipc.py +61 -121
  111. web3/providers/persistent/persistent.py +324 -17
  112. web3/providers/persistent/persistent_connection.py +54 -5
  113. web3/providers/persistent/request_processor.py +136 -56
  114. web3/providers/persistent/subscription_container.py +56 -0
  115. web3/providers/persistent/subscription_manager.py +233 -0
  116. web3/providers/persistent/websocket.py +29 -92
  117. web3/providers/rpc/__init__.py +5 -0
  118. web3/providers/rpc/async_rpc.py +73 -18
  119. web3/providers/rpc/rpc.py +73 -30
  120. web3/providers/rpc/utils.py +1 -13
  121. web3/scripts/install_pre_releases.py +33 -0
  122. web3/scripts/parse_pygeth_version.py +16 -0
  123. web3/testing.py +4 -4
  124. web3/tracing.py +9 -5
  125. web3/types.py +141 -74
  126. web3/utils/__init__.py +64 -5
  127. web3/utils/abi.py +790 -10
  128. web3/utils/address.py +8 -0
  129. web3/utils/async_exception_handling.py +20 -11
  130. web3/utils/caching.py +34 -4
  131. web3/utils/exception_handling.py +9 -12
  132. web3/utils/subscriptions.py +285 -0
  133. {web3-7.0.0b1.dist-info → web3-7.7.0.dist-info}/LICENSE +1 -1
  134. web3-7.7.0.dist-info/METADATA +130 -0
  135. web3-7.7.0.dist-info/RECORD +171 -0
  136. {web3-7.0.0b1.dist-info → web3-7.7.0.dist-info}/WHEEL +1 -1
  137. {web3-7.0.0b1.dist-info → web3-7.7.0.dist-info}/top_level.txt +0 -1
  138. ethpm/__init__.py +0 -20
  139. ethpm/_utils/__init__.py +0 -0
  140. ethpm/_utils/backend.py +0 -93
  141. ethpm/_utils/cache.py +0 -44
  142. ethpm/_utils/chains.py +0 -119
  143. ethpm/_utils/contract.py +0 -35
  144. ethpm/_utils/deployments.py +0 -145
  145. ethpm/_utils/ipfs.py +0 -116
  146. ethpm/_utils/protobuf/__init__.py +0 -0
  147. ethpm/_utils/protobuf/ipfs_file_pb2.py +0 -33
  148. ethpm/_utils/registry.py +0 -29
  149. ethpm/assets/__init__.py +0 -0
  150. ethpm/assets/ens/v3.json +0 -1
  151. ethpm/assets/escrow/with_bytecode_v3.json +0 -1
  152. ethpm/assets/ipfs_file.proto +0 -32
  153. ethpm/assets/owned/output_v3.json +0 -1
  154. ethpm/assets/owned/with_contract_type_v3.json +0 -1
  155. ethpm/assets/registry/contracts/Authority.sol +0 -156
  156. ethpm/assets/registry/contracts/IndexedOrderedSetLib.sol +0 -106
  157. ethpm/assets/registry/contracts/PackageDB.sol +0 -225
  158. ethpm/assets/registry/contracts/PackageRegistry.sol +0 -361
  159. ethpm/assets/registry/contracts/PackageRegistryInterface.sol +0 -97
  160. ethpm/assets/registry/contracts/ReleaseDB.sol +0 -309
  161. ethpm/assets/registry/contracts/ReleaseValidator.sol +0 -152
  162. ethpm/assets/registry/solc_input.json +0 -1
  163. ethpm/assets/registry/solc_output.json +0 -1
  164. ethpm/assets/registry/v3.json +0 -1
  165. ethpm/assets/safe-math-lib/v3-strict-no-deployments.json +0 -1
  166. ethpm/assets/simple-registry/contracts/Ownable.sol +0 -63
  167. ethpm/assets/simple-registry/contracts/PackageRegistry.sol +0 -373
  168. ethpm/assets/simple-registry/contracts/PackageRegistryInterface.sol +0 -96
  169. ethpm/assets/simple-registry/solc_input.json +0 -33
  170. ethpm/assets/simple-registry/solc_output.json +0 -1
  171. ethpm/assets/simple-registry/v3.json +0 -1
  172. ethpm/assets/standard-token/output_v3.json +0 -1
  173. ethpm/assets/standard-token/with_bytecode_v3.json +0 -1
  174. ethpm/assets/vyper_registry/0.1.0.json +0 -1
  175. ethpm/assets/vyper_registry/registry.vy +0 -216
  176. ethpm/assets/vyper_registry/registry_with_delete.vy +0 -244
  177. ethpm/backends/__init__.py +0 -0
  178. ethpm/backends/base.py +0 -43
  179. ethpm/backends/http.py +0 -108
  180. ethpm/backends/ipfs.py +0 -219
  181. ethpm/backends/registry.py +0 -154
  182. ethpm/constants.py +0 -17
  183. ethpm/contract.py +0 -187
  184. ethpm/dependencies.py +0 -58
  185. ethpm/deployments.py +0 -80
  186. ethpm/ethpm-spec/examples/escrow/1.0.0-pretty.json +0 -146
  187. ethpm/ethpm-spec/examples/escrow/1.0.0.json +0 -1
  188. ethpm/ethpm-spec/examples/escrow/contracts/Escrow.sol +0 -32
  189. ethpm/ethpm-spec/examples/escrow/contracts/SafeSendLib.sol +0 -20
  190. ethpm/ethpm-spec/examples/escrow/v3-pretty.json +0 -171
  191. ethpm/ethpm-spec/examples/escrow/v3.json +0 -1
  192. ethpm/ethpm-spec/examples/owned/1.0.0-pretty.json +0 -21
  193. ethpm/ethpm-spec/examples/owned/1.0.0.json +0 -1
  194. ethpm/ethpm-spec/examples/owned/contracts/Owned.sol +0 -12
  195. ethpm/ethpm-spec/examples/owned/v3-pretty.json +0 -27
  196. ethpm/ethpm-spec/examples/owned/v3.json +0 -1
  197. ethpm/ethpm-spec/examples/piper-coin/1.0.0-pretty.json +0 -31
  198. ethpm/ethpm-spec/examples/piper-coin/1.0.0.json +0 -1
  199. ethpm/ethpm-spec/examples/piper-coin/v3-pretty.json +0 -21
  200. ethpm/ethpm-spec/examples/piper-coin/v3.json +0 -1
  201. ethpm/ethpm-spec/examples/safe-math-lib/1.0.0-pretty.json +0 -85
  202. ethpm/ethpm-spec/examples/safe-math-lib/1.0.0.json +0 -1
  203. ethpm/ethpm-spec/examples/safe-math-lib/contracts/SafeMathLib.sol +0 -24
  204. ethpm/ethpm-spec/examples/safe-math-lib/v3-pretty.json +0 -117
  205. ethpm/ethpm-spec/examples/safe-math-lib/v3.json +0 -1
  206. ethpm/ethpm-spec/examples/standard-token/1.0.0-pretty.json +0 -55
  207. ethpm/ethpm-spec/examples/standard-token/1.0.0.json +0 -1
  208. ethpm/ethpm-spec/examples/standard-token/contracts/AbstractToken.sol +0 -20
  209. ethpm/ethpm-spec/examples/standard-token/contracts/StandardToken.sol +0 -84
  210. ethpm/ethpm-spec/examples/standard-token/v3-pretty.json +0 -460
  211. ethpm/ethpm-spec/examples/standard-token/v3.json +0 -1
  212. ethpm/ethpm-spec/examples/transferable/1.0.0-pretty.json +0 -21
  213. ethpm/ethpm-spec/examples/transferable/1.0.0.json +0 -1
  214. ethpm/ethpm-spec/examples/transferable/contracts/Transferable.sol +0 -14
  215. ethpm/ethpm-spec/examples/transferable/v3-pretty.json +0 -27
  216. ethpm/ethpm-spec/examples/transferable/v3.json +0 -1
  217. ethpm/ethpm-spec/examples/wallet/1.0.0-pretty.json +0 -120
  218. ethpm/ethpm-spec/examples/wallet/1.0.0.json +0 -1
  219. ethpm/ethpm-spec/examples/wallet/contracts/Wallet.sol +0 -41
  220. ethpm/ethpm-spec/examples/wallet/v3-pretty.json +0 -181
  221. ethpm/ethpm-spec/examples/wallet/v3.json +0 -1
  222. ethpm/ethpm-spec/examples/wallet-with-send/1.0.0-pretty.json +0 -135
  223. ethpm/ethpm-spec/examples/wallet-with-send/1.0.0.json +0 -1
  224. ethpm/ethpm-spec/examples/wallet-with-send/contracts/WalletWithSend.sol +0 -18
  225. ethpm/ethpm-spec/examples/wallet-with-send/v3-pretty.json +0 -207
  226. ethpm/ethpm-spec/examples/wallet-with-send/v3.json +0 -1
  227. ethpm/ethpm-spec/spec/package.spec.json +0 -379
  228. ethpm/ethpm-spec/spec/v3.spec.json +0 -483
  229. ethpm/exceptions.py +0 -68
  230. ethpm/package.py +0 -438
  231. ethpm/tools/__init__.py +0 -4
  232. ethpm/tools/builder.py +0 -930
  233. ethpm/tools/checker.py +0 -312
  234. ethpm/tools/get_manifest.py +0 -19
  235. ethpm/uri.py +0 -141
  236. ethpm/validation/__init__.py +0 -0
  237. ethpm/validation/manifest.py +0 -146
  238. ethpm/validation/misc.py +0 -39
  239. ethpm/validation/package.py +0 -80
  240. ethpm/validation/uri.py +0 -163
  241. web3/_utils/caching.py +0 -155
  242. web3/_utils/contract_sources/contract_data/address_reflector.py +0 -29
  243. web3/_utils/module_testing/go_ethereum_personal_module.py +0 -300
  244. web3/_utils/request.py +0 -265
  245. web3/pm.py +0 -602
  246. web3/tools/__init__.py +0 -4
  247. web3/tools/benchmark/__init__.py +0 -0
  248. web3/tools/benchmark/main.py +0 -185
  249. web3/tools/benchmark/node.py +0 -126
  250. web3/tools/benchmark/reporting.py +0 -39
  251. web3/tools/benchmark/utils.py +0 -69
  252. web3/tools/pytest_ethereum/__init__.py +0 -0
  253. web3/tools/pytest_ethereum/_utils.py +0 -145
  254. web3/tools/pytest_ethereum/deployer.py +0 -48
  255. web3/tools/pytest_ethereum/exceptions.py +0 -22
  256. web3/tools/pytest_ethereum/linker.py +0 -128
  257. web3/tools/pytest_ethereum/plugins.py +0 -33
  258. web3-7.0.0b1.dist-info/METADATA +0 -114
  259. web3-7.0.0b1.dist-info/RECORD +0 -280
  260. web3-7.0.0b1.dist-info/entry_points.txt +0 -2
  261. /web3/_utils/{function_identifiers.py → abi_element_identifiers.py} +0 -0
web3/manager.py CHANGED
@@ -5,6 +5,8 @@ from typing import (
5
5
  Any,
6
6
  AsyncGenerator,
7
7
  Callable,
8
+ Coroutine,
9
+ Dict,
8
10
  List,
9
11
  Optional,
10
12
  Sequence,
@@ -19,10 +21,10 @@ from eth_utils.toolz import (
19
21
  from hexbytes import (
20
22
  HexBytes,
21
23
  )
22
- from websockets.exceptions import (
23
- ConnectionClosedOK,
24
- )
25
24
 
25
+ from web3._utils.batching import (
26
+ RequestBatcher,
27
+ )
26
28
  from web3._utils.caching import (
27
29
  generate_cache_key,
28
30
  )
@@ -36,6 +38,14 @@ from web3.exceptions import (
36
38
  BadResponseFormat,
37
39
  MethodUnavailable,
38
40
  ProviderConnectionError,
41
+ RequestTimedOut,
42
+ TaskNotRunning,
43
+ TransactionNotFound,
44
+ Web3RPCError,
45
+ Web3TypeError,
46
+ )
47
+ from web3.method import (
48
+ Method,
39
49
  )
40
50
  from web3.middleware import (
41
51
  AttributeDictMiddleware,
@@ -53,10 +63,16 @@ from web3.module import (
53
63
  )
54
64
  from web3.providers import (
55
65
  AutoProvider,
66
+ JSONBaseProvider,
56
67
  PersistentConnectionProvider,
57
68
  )
69
+ from web3.providers.async_base import (
70
+ AsyncJSONBaseProvider,
71
+ )
58
72
  from web3.types import (
73
+ FormattedEthSubscriptionResponse,
59
74
  RPCEndpoint,
75
+ RPCRequest,
60
76
  RPCResponse,
61
77
  )
62
78
 
@@ -78,6 +94,13 @@ if TYPE_CHECKING:
78
94
 
79
95
 
80
96
  NULL_RESPONSES = [None, HexBytes("0x"), "0x"]
97
+ KNOWN_REQUEST_TIMEOUT_MESSAGING = {
98
+ # Note: It's important to be very explicit here and not too broad. We don't want
99
+ # to accidentally catch a message that is not for a request timeout. In the worst
100
+ # case, we raise something more generic like `Web3RPCError`. JSON-RPC unfortunately
101
+ # has not standardized error codes for request timeouts.
102
+ "request timed out", # go-ethereum
103
+ }
81
104
  METHOD_NOT_FOUND = -32601
82
105
 
83
106
 
@@ -86,6 +109,7 @@ def _raise_bad_response_format(response: RPCResponse, error: str = "") -> None:
86
109
  raw_response = f"The raw response is: {response}"
87
110
 
88
111
  if error is not None and error != "":
112
+ error = error[:-1] if error.endswith(".") else error
89
113
  message = f"{message} {error}. {raw_response}"
90
114
  else:
91
115
  message = f"{message} {raw_response}"
@@ -116,6 +140,132 @@ def apply_null_result_formatters(
116
140
  return response
117
141
 
118
142
 
143
+ def _validate_subscription_fields(response: RPCResponse) -> None:
144
+ params = response["params"]
145
+ subscription = params["subscription"]
146
+ if not isinstance(subscription, str) and not len(subscription) == 34:
147
+ _raise_bad_response_format(
148
+ response, "eth_subscription 'params' must include a 'subscription' field."
149
+ )
150
+
151
+
152
+ def _validate_response(
153
+ response: RPCResponse,
154
+ error_formatters: Optional[Callable[..., Any]],
155
+ is_subscription_response: bool = False,
156
+ logger: Optional[logging.Logger] = None,
157
+ params: Optional[Any] = None,
158
+ ) -> None:
159
+ if "jsonrpc" not in response or response["jsonrpc"] != "2.0":
160
+ _raise_bad_response_format(
161
+ response, 'The "jsonrpc" field must be present with a value of "2.0".'
162
+ )
163
+
164
+ response_id = response.get("id")
165
+ if "id" in response:
166
+ int_error_msg = (
167
+ '"id" must be an integer or a string representation of an integer.'
168
+ )
169
+ if response_id is None and "error" in response:
170
+ # errors can sometimes have null `id`, according to the JSON-RPC spec
171
+ pass
172
+ elif not isinstance(response_id, (str, int)):
173
+ _raise_bad_response_format(response, int_error_msg)
174
+ elif isinstance(response_id, str):
175
+ try:
176
+ int(response_id)
177
+ except ValueError:
178
+ _raise_bad_response_format(response, int_error_msg)
179
+ elif is_subscription_response:
180
+ # if `id` is not present, this must be a subscription response
181
+ _validate_subscription_fields(response)
182
+ else:
183
+ _raise_bad_response_format(
184
+ response,
185
+ 'Response must include an "id" field or be formatted as an '
186
+ "`eth_subscription` response.",
187
+ )
188
+
189
+ if all(key in response for key in {"error", "result"}):
190
+ _raise_bad_response_format(
191
+ response, 'Response cannot include both "error" and "result".'
192
+ )
193
+ elif (
194
+ not any(key in response for key in {"error", "result"})
195
+ and not is_subscription_response
196
+ ):
197
+ _raise_bad_response_format(
198
+ response, 'Response must include either "error" or "result".'
199
+ )
200
+ elif "error" in response:
201
+ web3_rpc_error: Optional[Web3RPCError] = None
202
+ error = response["error"]
203
+
204
+ # raise the error when the value is a string
205
+ if error is None or not isinstance(error, dict):
206
+ _raise_bad_response_format(
207
+ response,
208
+ 'response["error"] must be a valid object as defined by the '
209
+ "JSON-RPC 2.0 specification.",
210
+ )
211
+
212
+ # errors must include a message
213
+ error_message = error.get("message")
214
+ if not isinstance(error_message, str):
215
+ _raise_bad_response_format(
216
+ response, 'error["message"] is required and must be a string value.'
217
+ )
218
+ elif error_message == "transaction not found":
219
+ transaction_hash = params[0]
220
+ web3_rpc_error = TransactionNotFound(
221
+ repr(error),
222
+ rpc_response=response,
223
+ user_message=(f"Transaction with hash {transaction_hash!r} not found."),
224
+ )
225
+
226
+ # errors must include an integer code
227
+ code = error.get("code")
228
+ if not isinstance(code, int):
229
+ _raise_bad_response_format(
230
+ response, 'error["code"] is required and must be an integer value.'
231
+ )
232
+ elif code == METHOD_NOT_FOUND:
233
+ web3_rpc_error = MethodUnavailable(
234
+ repr(error),
235
+ rpc_response=response,
236
+ user_message=(
237
+ "This method is not available. Check your node provider or your "
238
+ "client's API docs to see what methods are supported and / or "
239
+ "currently enabled."
240
+ ),
241
+ )
242
+ elif any(
243
+ # parse specific timeout messages
244
+ timeout_str in error_message.lower()
245
+ for timeout_str in KNOWN_REQUEST_TIMEOUT_MESSAGING
246
+ ):
247
+ web3_rpc_error = RequestTimedOut(
248
+ repr(error),
249
+ rpc_response=response,
250
+ user_message=(
251
+ "The request timed out. Check the connection to your node and "
252
+ "try again."
253
+ ),
254
+ )
255
+
256
+ if web3_rpc_error is None:
257
+ # if no condition was met above, raise a more generic `Web3RPCError`
258
+ web3_rpc_error = Web3RPCError(repr(error), rpc_response=response)
259
+
260
+ response = apply_error_formatters(error_formatters, response)
261
+ logger.debug(f"RPC error response: {response}")
262
+
263
+ raise web3_rpc_error
264
+
265
+ elif "result" not in response and not is_subscription_response:
266
+ _raise_bad_response_format(response)
267
+
268
+
119
269
  class RequestManager:
120
270
  logger = logging.getLogger("web3.manager.RequestManager")
121
271
 
@@ -125,7 +275,7 @@ class RequestManager:
125
275
  self,
126
276
  w3: Union["AsyncWeb3", "Web3"],
127
277
  provider: Optional[Union["BaseProvider", "AsyncBaseProvider"]] = None,
128
- middlewares: Optional[Sequence[Tuple[Middleware, str]]] = None,
278
+ middleware: Optional[Sequence[Tuple[Middleware, str]]] = None,
129
279
  ) -> None:
130
280
  self.w3 = w3
131
281
 
@@ -134,10 +284,10 @@ class RequestManager:
134
284
  else:
135
285
  self.provider = provider
136
286
 
137
- if middlewares is None:
138
- middlewares = self.get_default_middlewares()
287
+ if middleware is None:
288
+ middleware = self.get_default_middleware()
139
289
 
140
- self.middleware_onion = NamedElementOnion(middlewares)
290
+ self.middleware_onion = NamedElementOnion(middleware)
141
291
 
142
292
  if isinstance(provider, PersistentConnectionProvider):
143
293
  # set up the request processor to be able to properly process ordered
@@ -145,9 +295,6 @@ class RequestManager:
145
295
  provider = cast(PersistentConnectionProvider, self.provider)
146
296
  self._request_processor: RequestProcessor = provider._request_processor
147
297
 
148
- w3: Union["AsyncWeb3", "Web3"] = None
149
- _provider = None
150
-
151
298
  @property
152
299
  def provider(self) -> Union["BaseProvider", "AsyncBaseProvider"]:
153
300
  return self._provider
@@ -157,9 +304,9 @@ class RequestManager:
157
304
  self._provider = provider
158
305
 
159
306
  @staticmethod
160
- def get_default_middlewares() -> List[Tuple[Middleware, str]]:
307
+ def get_default_middleware() -> List[Tuple[Middleware, str]]:
161
308
  """
162
- List the default middlewares for the request manager.
309
+ List the default middleware for the request manager.
163
310
  Documentation should remain in sync with these defaults.
164
311
  """
165
312
  return [
@@ -202,86 +349,43 @@ class RequestManager:
202
349
  #
203
350
  # See also: https://www.jsonrpc.org/specification
204
351
  #
205
- @staticmethod
206
352
  def formatted_response(
353
+ self,
207
354
  response: RPCResponse,
208
355
  params: Any,
209
356
  error_formatters: Optional[Callable[..., Any]] = None,
210
357
  null_result_formatters: Optional[Callable[..., Any]] = None,
211
358
  ) -> Any:
212
- # jsonrpc is not enforced (as per the spec) but if present, it must be 2.0
213
- if "jsonrpc" in response and response["jsonrpc"] != "2.0":
214
- _raise_bad_response_format(
215
- response, 'The "jsonrpc" field must be present with a value of "2.0"'
216
- )
217
-
218
- # id is not enforced (as per the spec) but if present, it must be a
219
- # string or integer
220
- # TODO: v7 - enforce id per the spec
221
- if "id" in response:
222
- response_id = response["id"]
223
- # id is always None for errors
224
- if response_id is None and "error" not in response:
225
- _raise_bad_response_format(
226
- response, '"id" must be None when an error is present'
227
- )
228
- elif not isinstance(response_id, (str, int, type(None))):
229
- _raise_bad_response_format(response, '"id" must be a string or integer')
230
-
231
- # Response may not include both "error" and "result"
232
- if "error" in response and "result" in response:
233
- _raise_bad_response_format(
234
- response, 'Response cannot include both "error" and "result"'
235
- )
236
-
237
- # Format and validate errors
238
- elif "error" in response:
239
- error = response.get("error")
240
- # Raise the error when the value is a string
241
- if error is None or isinstance(error, str):
242
- raise ValueError(error)
243
-
244
- # Errors must include an integer code
245
- code = error.get("code")
246
- if not isinstance(code, int):
247
- _raise_bad_response_format(response, "error['code'] must be an integer")
248
- elif code == METHOD_NOT_FOUND:
249
- raise MethodUnavailable(error)
250
-
251
- # Errors must include a message
252
- if not isinstance(error.get("message"), str):
253
- _raise_bad_response_format(
254
- response, "error['message'] must be a string"
255
- )
256
-
257
- apply_error_formatters(error_formatters, response)
359
+ is_subscription_response = (
360
+ response.get("method") == "eth_subscription"
361
+ and response.get("params") is not None
362
+ and response["params"].get("subscription") is not None
363
+ and response["params"].get("result") is not None
364
+ )
258
365
 
259
- raise ValueError(error)
366
+ _validate_response(
367
+ response,
368
+ error_formatters,
369
+ is_subscription_response=is_subscription_response,
370
+ logger=self.logger,
371
+ params=params,
372
+ )
260
373
 
261
- # Format and validate results
262
- elif "result" in response:
374
+ # format results
375
+ if "result" in response:
263
376
  # Null values for result should apply null_result_formatters
264
377
  # Skip when result not present in the response (fallback to False)
265
378
  if response.get("result", False) in NULL_RESPONSES:
266
379
  apply_null_result_formatters(null_result_formatters, response, params)
267
380
  return response.get("result")
268
381
 
269
- # Response from eth_subscription includes response["params"]["result"]
270
- elif (
271
- response.get("method") == "eth_subscription"
272
- and response.get("params") is not None
273
- and response["params"].get("subscription") is not None
274
- and response["params"].get("result") is not None
275
- ):
382
+ # response from eth_subscription includes response["params"]["result"]
383
+ elif is_subscription_response:
276
384
  return {
277
385
  "subscription": response["params"]["subscription"],
278
386
  "result": response["params"]["result"],
279
387
  }
280
388
 
281
- # Any other response type raises BadResponseFormat
282
- else:
283
- _raise_bad_response_format(response)
284
-
285
389
  def request_blocking(
286
390
  self,
287
391
  method: Union[RPCEndpoint, Callable[..., RPCEndpoint]],
@@ -312,32 +416,181 @@ class RequestManager:
312
416
  response, params, error_formatters, null_result_formatters
313
417
  )
314
418
 
419
+ # -- batch requests management -- #
420
+
421
+ def _batch_requests(self) -> RequestBatcher[Method[Callable[..., Any]]]:
422
+ """
423
+ Context manager for making batch requests
424
+ """
425
+ if not isinstance(self.provider, (AsyncJSONBaseProvider, JSONBaseProvider)):
426
+ raise Web3TypeError("Batch requests are not supported by this provider.")
427
+ return RequestBatcher(self.w3)
428
+
429
+ def _make_batch_request(
430
+ self, requests_info: List[Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
431
+ ) -> List[RPCResponse]:
432
+ """
433
+ Make a batch request using the provider
434
+ """
435
+ provider = cast(JSONBaseProvider, self.provider)
436
+ request_func = provider.batch_request_func(
437
+ cast("Web3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
438
+ )
439
+ responses = request_func(
440
+ [
441
+ (method, params)
442
+ for (method, params), _response_formatters in requests_info
443
+ ]
444
+ )
445
+ formatted_responses = [
446
+ self._format_batched_response(info, resp)
447
+ for info, resp in zip(requests_info, responses)
448
+ ]
449
+ return list(formatted_responses)
450
+
451
+ async def _async_make_batch_request(
452
+ self,
453
+ requests_info: List[
454
+ Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
455
+ ],
456
+ ) -> List[RPCResponse]:
457
+ """
458
+ Make an asynchronous batch request using the provider
459
+ """
460
+ provider = cast(AsyncJSONBaseProvider, self.provider)
461
+ request_func = await provider.batch_request_func(
462
+ cast("AsyncWeb3", self.w3),
463
+ cast("MiddlewareOnion", self.middleware_onion),
464
+ )
465
+ # since we add items to the batch without awaiting, we unpack the coroutines
466
+ # and await them all here
467
+ unpacked_requests_info = await asyncio.gather(*requests_info)
468
+ responses = await request_func(
469
+ [
470
+ (method, params)
471
+ for (method, params), _response_formatters in unpacked_requests_info
472
+ ]
473
+ )
474
+
475
+ if isinstance(self.provider, PersistentConnectionProvider):
476
+ # call _process_response for each response in the batch
477
+ return [
478
+ cast(RPCResponse, await self._process_response(resp))
479
+ for resp in responses
480
+ ]
481
+
482
+ formatted_responses = [
483
+ self._format_batched_response(info, resp)
484
+ for info, resp in zip(unpacked_requests_info, responses)
485
+ ]
486
+ return list(formatted_responses)
487
+
488
+ def _format_batched_response(
489
+ self,
490
+ requests_info: Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]],
491
+ response: RPCResponse,
492
+ ) -> RPCResponse:
493
+ result_formatters, error_formatters, null_result_formatters = requests_info[1]
494
+ return apply_result_formatters(
495
+ result_formatters,
496
+ self.formatted_response(
497
+ response,
498
+ requests_info[0][1],
499
+ error_formatters,
500
+ null_result_formatters,
501
+ ),
502
+ )
503
+
315
504
  # -- persistent connection -- #
316
505
 
317
- async def send(self, method: RPCEndpoint, params: Any) -> RPCResponse:
506
+ async def socket_request(
507
+ self,
508
+ method: RPCEndpoint,
509
+ params: Any,
510
+ response_formatters: Optional[
511
+ Tuple[Dict[str, Callable[..., Any]], Callable[..., Any], Callable[..., Any]]
512
+ ] = None,
513
+ ) -> RPCResponse:
318
514
  provider = cast(PersistentConnectionProvider, self._provider)
319
- request_func = await provider.request_func(
320
- cast("AsyncWeb3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
515
+ self.logger.debug(
516
+ "Making request to open socket connection and waiting for response: "
517
+ f"{provider.get_endpoint_uri_or_ipc_path()},\n method: {method},\n"
518
+ f" params: {params}"
519
+ )
520
+ rpc_request = await self.send(method, params)
521
+ provider._request_processor.cache_request_information(
522
+ rpc_request["id"],
523
+ rpc_request["method"],
524
+ rpc_request["params"],
525
+ response_formatters=response_formatters or ((), (), ()),
526
+ )
527
+ return await self.recv_for_request(rpc_request)
528
+
529
+ async def send(self, method: RPCEndpoint, params: Any) -> RPCRequest:
530
+ provider = cast(PersistentConnectionProvider, self._provider)
531
+ async_w3 = cast("AsyncWeb3", self.w3)
532
+ middleware_onion = cast("MiddlewareOnion", self.middleware_onion)
533
+ send_func = await provider.send_func(
534
+ async_w3,
535
+ middleware_onion,
321
536
  )
322
537
  self.logger.debug(
323
- "Making request to open socket connection - "
324
- f"uri: {provider.endpoint_uri}, method: {method}"
538
+ "Sending request to open socket connection: "
539
+ f"{provider.get_endpoint_uri_or_ipc_path()},\n method: {method},\n"
540
+ f" params: {params}"
541
+ )
542
+ return await send_func(method, params)
543
+
544
+ async def recv_for_request(self, rpc_request: RPCRequest) -> RPCResponse:
545
+ provider = cast(PersistentConnectionProvider, self._provider)
546
+ async_w3 = cast("AsyncWeb3", self.w3)
547
+ middleware_onion = cast("MiddlewareOnion", self.middleware_onion)
548
+ recv_func = await provider.recv_func(
549
+ async_w3,
550
+ middleware_onion,
551
+ )
552
+ self.logger.debug(
553
+ "Getting response for request from open socket connection:\n"
554
+ f" request: {rpc_request}"
555
+ )
556
+ response = await recv_func(rpc_request)
557
+ try:
558
+ return cast(RPCResponse, await self._process_response(response))
559
+ except Exception:
560
+ response_id_key = generate_cache_key(response["id"])
561
+ provider._request_processor._request_information_cache.pop(response_id_key)
562
+ raise
563
+
564
+ async def recv(self) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
565
+ provider = cast(PersistentConnectionProvider, self._provider)
566
+ self.logger.debug(
567
+ "Getting next response from open socket connection: "
568
+ f"{provider.get_endpoint_uri_or_ipc_path()}"
569
+ )
570
+ # pop from the queue since the listener task is responsible for reading
571
+ # directly from the socket
572
+ request_response_cache = self._request_processor._request_response_cache
573
+ _key, response = await request_response_cache.async_await_and_popitem(
574
+ last=False,
575
+ timeout=provider.request_timeout,
325
576
  )
326
- response = await request_func(method, params)
327
577
  return await self._process_response(response)
328
578
 
329
579
  def _persistent_message_stream(self) -> "_AsyncPersistentMessageStream":
330
580
  return _AsyncPersistentMessageStream(self)
331
581
 
332
- async def _get_next_message(self) -> Any:
582
+ async def _get_next_message(self) -> FormattedEthSubscriptionResponse:
333
583
  return await self._message_stream().__anext__()
334
584
 
335
- async def _message_stream(self) -> AsyncGenerator[RPCResponse, None]:
585
+ async def _message_stream(
586
+ self,
587
+ ) -> AsyncGenerator[FormattedEthSubscriptionResponse, None]:
336
588
  if not isinstance(self._provider, PersistentConnectionProvider):
337
- raise TypeError(
589
+ raise Web3TypeError(
338
590
  "Only providers that maintain an open, persistent connection "
339
591
  "can listen to streams."
340
592
  )
593
+ async_w3 = cast("AsyncWeb3", self.w3)
341
594
 
342
595
  if self._provider._message_listener_task is None:
343
596
  raise ProviderConnectionError(
@@ -345,20 +598,37 @@ class RequestManager:
345
598
  )
346
599
 
347
600
  while True:
348
- # sleep(0) here seems to be the most efficient way to yield control
349
- # back to the event loop while waiting for the response in the queue.
350
- await asyncio.sleep(0)
351
-
352
- response = self._request_processor.pop_raw_response(subscription=True)
353
- if (
354
- response is not None
355
- and response.get("params", {}).get("subscription")
356
- in self._request_processor.active_subscriptions
357
- ):
358
- # if response is an active subscription response, process it
359
- yield await self._process_response(response)
360
-
361
- async def _process_response(self, response: RPCResponse) -> RPCResponse:
601
+ try:
602
+ response = await self._request_processor.pop_raw_response(
603
+ subscription=True
604
+ )
605
+ # if the subscription was unsubscribed from, we won't have a formatted
606
+ # response because we lost the request information.
607
+ sub_id = response.get(
608
+ "subscription", response.get("params", {}).get("subscription")
609
+ )
610
+ if async_w3.subscription_manager.get_by_id(sub_id):
611
+ # if active subscription, process and yield the formatted response
612
+ formatted_sub_response = cast(
613
+ FormattedEthSubscriptionResponse,
614
+ await self._process_response(response),
615
+ )
616
+ yield formatted_sub_response
617
+ else:
618
+ # if not an active sub, skip processing and continue
619
+ continue
620
+ except TaskNotRunning:
621
+ await asyncio.sleep(0)
622
+ self._provider._handle_listener_task_exceptions()
623
+ self.logger.error(
624
+ "Message listener background task has stopped unexpectedly. "
625
+ "Stopping message stream."
626
+ )
627
+ return
628
+
629
+ async def _process_response(
630
+ self, response: RPCResponse
631
+ ) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
362
632
  provider = cast(PersistentConnectionProvider, self._provider)
363
633
  request_info = self._request_processor.get_request_information_for_response(
364
634
  response
@@ -421,8 +691,5 @@ class _AsyncPersistentMessageStream:
421
691
  def __aiter__(self) -> Self:
422
692
  return self
423
693
 
424
- async def __anext__(self) -> RPCResponse:
425
- try:
426
- return await self.manager._get_next_message()
427
- except ConnectionClosedOK:
428
- raise StopAsyncIteration
694
+ async def __anext__(self) -> FormattedEthSubscriptionResponse:
695
+ return await self.manager._get_next_message()