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
@@ -1,17 +1,44 @@
1
1
  from abc import (
2
2
  ABC,
3
+ abstractmethod,
3
4
  )
4
5
  import asyncio
5
6
  import logging
7
+ import signal
6
8
  from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ Callable,
12
+ Coroutine,
13
+ List,
7
14
  Optional,
15
+ Tuple,
16
+ Union,
17
+ cast,
8
18
  )
9
19
 
20
+ from websockets import (
21
+ ConnectionClosed,
22
+ WebSocketException,
23
+ )
24
+
25
+ from web3._utils.batching import (
26
+ BATCH_REQUEST_ID,
27
+ sort_batch_response_by_response_ids,
28
+ )
10
29
  from web3._utils.caching import (
11
30
  generate_cache_key,
12
31
  )
32
+ from web3._utils.caching.caching_utils import (
33
+ async_handle_recv_caching,
34
+ async_handle_send_caching,
35
+ )
13
36
  from web3.exceptions import (
37
+ PersistentConnectionClosedOK,
38
+ ProviderConnectionError,
39
+ TaskNotRunning,
14
40
  TimeExhausted,
41
+ Web3AttributeError,
15
42
  )
16
43
  from web3.providers.async_base import (
17
44
  AsyncJSONBaseProvider,
@@ -20,47 +47,327 @@ from web3.providers.persistent.request_processor import (
20
47
  RequestProcessor,
21
48
  )
22
49
  from web3.types import (
50
+ RPCEndpoint,
23
51
  RPCId,
52
+ RPCRequest,
24
53
  RPCResponse,
25
54
  )
26
55
 
27
- DEFAULT_PERSISTENT_CONNECTION_TIMEOUT = 50.0
56
+ if TYPE_CHECKING:
57
+ from web3 import AsyncWeb3 # noqa: F401
58
+ from web3.middleware.base import MiddlewareOnion # noqa: F401
59
+
60
+
61
+ DEFAULT_PERSISTENT_CONNECTION_TIMEOUT = 30.0
28
62
 
29
63
 
30
64
  class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
31
65
  logger = logging.getLogger("web3.providers.PersistentConnectionProvider")
32
66
  has_persistent_connection = True
33
- endpoint_uri: Optional[str] = None
34
67
 
35
- _request_processor: RequestProcessor
36
- _message_listener_task: Optional["asyncio.Task[None]"] = None
37
- _listen_event: asyncio.Event = asyncio.Event()
68
+ _send_func_cache: Tuple[int, Callable[..., Coroutine[Any, Any, RPCRequest]]] = (
69
+ None,
70
+ None,
71
+ )
72
+ _recv_func_cache: Tuple[int, Callable[..., Coroutine[Any, Any, RPCResponse]]] = (
73
+ None,
74
+ None,
75
+ )
38
76
 
39
77
  def __init__(
40
78
  self,
41
79
  request_timeout: float = DEFAULT_PERSISTENT_CONNECTION_TIMEOUT,
42
80
  subscription_response_queue_size: int = 500,
43
81
  silence_listener_task_exceptions: bool = False,
82
+ max_connection_retries: int = 5,
83
+ **kwargs: Any,
44
84
  ) -> None:
45
- super().__init__()
85
+ super().__init__(**kwargs)
46
86
  self._request_processor = RequestProcessor(
47
87
  self,
48
88
  subscription_response_queue_size=subscription_response_queue_size,
49
89
  )
90
+ self._message_listener_task: Optional["asyncio.Task[None]"] = None
91
+ self._batch_request_counter: Optional[int] = None
92
+ self._listen_event: asyncio.Event = asyncio.Event()
93
+ self._max_connection_retries = max_connection_retries
94
+
50
95
  self.request_timeout = request_timeout
51
96
  self.silence_listener_task_exceptions = silence_listener_task_exceptions
52
97
 
98
+ async def send_func(
99
+ self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion"
100
+ ) -> Callable[..., Coroutine[Any, Any, RPCRequest]]:
101
+ """
102
+ Cache the middleware chain for `send`.
103
+ """
104
+ middleware = middleware_onion.as_tuple_of_middleware()
105
+ cache_key = hash(tuple(id(mw) for mw in middleware))
106
+
107
+ if cache_key != self._send_func_cache[0]:
108
+
109
+ async def send_function(method: RPCEndpoint, params: Any) -> RPCRequest:
110
+ for mw in middleware:
111
+ initialized = mw(async_w3)
112
+ method, params = await initialized.async_request_processor(
113
+ method, params
114
+ )
115
+
116
+ return await self.send_request(method, params)
117
+
118
+ self._send_func_cache = (cache_key, send_function)
119
+
120
+ return self._send_func_cache[1]
121
+
122
+ async def recv_func(
123
+ self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion"
124
+ ) -> Any:
125
+ """
126
+ Cache and compose the middleware stack for `recv`.
127
+ """
128
+ middleware = middleware_onion.as_tuple_of_middleware()
129
+ cache_key = hash(tuple(id(mw) for mw in middleware))
130
+
131
+ if cache_key != self._recv_func_cache[0]:
132
+
133
+ async def recv_function(rpc_request: RPCRequest) -> RPCResponse:
134
+ # first, retrieve the response
135
+ response = await self.recv_for_request(rpc_request)
136
+ method = rpc_request["method"]
137
+ for mw in reversed(middleware):
138
+ initialized = mw(async_w3)
139
+ response = await initialized.async_response_processor(
140
+ method, response
141
+ )
142
+ return response
143
+
144
+ self._recv_func_cache = (cache_key, recv_function)
145
+
146
+ return self._recv_func_cache[1]
147
+
148
+ def get_endpoint_uri_or_ipc_path(self) -> str:
149
+ if hasattr(self, "endpoint_uri"):
150
+ return str(self.endpoint_uri)
151
+ elif hasattr(self, "ipc_path"):
152
+ return str(self.ipc_path)
153
+ else:
154
+ raise Web3AttributeError(
155
+ "`PersistentConnectionProvider` must have either `endpoint_uri` or "
156
+ "`ipc_path` attribute."
157
+ )
158
+
53
159
  async def connect(self) -> None:
54
- raise NotImplementedError("Must be implemented by subclasses")
160
+ _connection_attempts = 0
161
+ _backoff_rate_change = 1.75
162
+ _backoff_time = 1.75
163
+
164
+ while _connection_attempts != self._max_connection_retries:
165
+ try:
166
+ _connection_attempts += 1
167
+ self.logger.info(
168
+ f"Connecting to: {self.get_endpoint_uri_or_ipc_path()}"
169
+ )
170
+ await self._provider_specific_connect()
171
+ self._message_listener_task = asyncio.create_task(
172
+ self._message_listener()
173
+ )
174
+ self._message_listener_task.add_done_callback(
175
+ self._message_listener_callback
176
+ )
177
+ self.logger.info(
178
+ f"Successfully connected to: {self.get_endpoint_uri_or_ipc_path()}"
179
+ )
180
+ break
181
+ except (WebSocketException, OSError) as e:
182
+ if _connection_attempts == self._max_connection_retries:
183
+ raise ProviderConnectionError(
184
+ f"Could not connect to: {self.get_endpoint_uri_or_ipc_path()}. "
185
+ f"Retries exceeded max of {self._max_connection_retries}."
186
+ ) from e
187
+ self.logger.info(
188
+ f"Could not connect to: {self.get_endpoint_uri_or_ipc_path()}. "
189
+ f"Retrying in {round(_backoff_time, 1)} seconds.",
190
+ exc_info=True,
191
+ )
192
+ await asyncio.sleep(_backoff_time)
193
+ _backoff_time *= _backoff_rate_change
55
194
 
56
195
  async def disconnect(self) -> None:
196
+ # this should remain idempotent
197
+ try:
198
+ if self._message_listener_task:
199
+ self._message_listener_task.cancel()
200
+ await self._message_listener_task
201
+ except (asyncio.CancelledError, StopAsyncIteration, ConnectionClosed):
202
+ pass
203
+ finally:
204
+ self._message_listener_task = None
205
+ self.logger.info("Message listener background task successfully shut down.")
206
+
207
+ await self._provider_specific_disconnect()
208
+ self._request_processor.clear_caches()
209
+ self.logger.info(
210
+ f"Successfully disconnected from: {self.get_endpoint_uri_or_ipc_path()}"
211
+ )
212
+
213
+ @async_handle_send_caching
214
+ async def send_request(self, method: RPCEndpoint, params: Any) -> RPCRequest:
215
+ request_dict = self.form_request(method, params)
216
+ await self.socket_send(self.encode_rpc_dict(request_dict))
217
+ return request_dict
218
+
219
+ @async_handle_recv_caching
220
+ async def recv_for_request(self, rpc_request: RPCRequest) -> RPCResponse:
221
+ return await self._get_response_for_request_id(rpc_request["id"])
222
+
223
+ async def make_request(
224
+ self,
225
+ method: RPCEndpoint,
226
+ params: Any,
227
+ ) -> RPCResponse:
228
+ rpc_request = await self.send_request(method, params)
229
+ return await self.recv_for_request(rpc_request)
230
+
231
+ async def make_batch_request(
232
+ self, requests: List[Tuple[RPCEndpoint, Any]]
233
+ ) -> List[RPCResponse]:
234
+ request_data = self.encode_batch_rpc_request(requests)
235
+ await self.socket_send(request_data)
236
+
237
+ response = cast(
238
+ List[RPCResponse], await self._get_response_for_request_id(BATCH_REQUEST_ID)
239
+ )
240
+ return response
241
+
242
+ # -- abstract methods -- #
243
+
244
+ @abstractmethod
245
+ async def socket_send(self, request_data: bytes) -> None:
246
+ """
247
+ Send an encoded RPC request to the provider over the persistent connection.
248
+ """
57
249
  raise NotImplementedError("Must be implemented by subclasses")
58
250
 
59
- async def _message_listener(self) -> None:
251
+ @abstractmethod
252
+ async def socket_recv(self) -> RPCResponse:
253
+ """
254
+ Receive, decode, and return an RPC response from the provider over the
255
+ persistent connection.
256
+ """
257
+ raise NotImplementedError("Must be implemented by subclasses")
258
+
259
+ # -- private methods -- #
260
+
261
+ async def _provider_specific_connect(self) -> None:
60
262
  raise NotImplementedError("Must be implemented by subclasses")
61
263
 
264
+ async def _provider_specific_disconnect(self) -> None:
265
+ # this method should be idempotent
266
+ raise NotImplementedError("Must be implemented by subclasses")
267
+
268
+ async def _provider_specific_socket_reader(self) -> RPCResponse:
269
+ raise NotImplementedError("Must be implemented by subclasses")
270
+
271
+ def _set_signal_handlers(self) -> None:
272
+ def extended_handler(sig: int, frame: Any, existing_handler: Any) -> None:
273
+ loop = asyncio.get_event_loop()
274
+
275
+ # invoke the existing handler, if callable
276
+ if callable(existing_handler):
277
+ existing_handler(sig, frame)
278
+ loop.create_task(self.disconnect())
279
+
280
+ existing_sigint_handler = signal.getsignal(signal.SIGINT)
281
+ existing_sigterm_handler = signal.getsignal(signal.SIGTERM)
282
+
283
+ # extend the existing signal handlers to include the disconnect method
284
+ signal.signal(
285
+ signal.SIGINT,
286
+ lambda sig, frame: extended_handler(sig, frame, existing_sigint_handler),
287
+ )
288
+ signal.signal(
289
+ signal.SIGTERM,
290
+ lambda sig, frame: extended_handler(sig, frame, existing_sigterm_handler),
291
+ )
292
+
293
+ def _message_listener_callback(
294
+ self, message_listener_task: "asyncio.Task[None]"
295
+ ) -> None:
296
+ # Puts a `TaskNotRunning` in appropriate queues to signal the end of the
297
+ # listener task to any listeners relying on the queues.
298
+ self._request_processor._subscription_response_queue.put_nowait(
299
+ TaskNotRunning(message_listener_task)
300
+ )
301
+ self._request_processor._handler_subscription_queue.put_nowait(
302
+ TaskNotRunning(message_listener_task)
303
+ )
304
+
305
+ async def _message_listener(self) -> None:
306
+ self.logger.info(
307
+ f"{self.__class__.__qualname__} listener background task started. Storing "
308
+ "all messages in appropriate request processor queues / caches to be "
309
+ "processed."
310
+ )
311
+ while True:
312
+ # the use of sleep(0) seems to be the most efficient way to yield control
313
+ # back to the event loop to share the loop with other tasks.
314
+ await asyncio.sleep(0)
315
+
316
+ try:
317
+ response = await self._provider_specific_socket_reader()
318
+
319
+ if isinstance(response, list):
320
+ response = sort_batch_response_by_response_ids(response)
321
+
322
+ subscription = (
323
+ response.get("method") == "eth_subscription"
324
+ if not isinstance(response, list)
325
+ else False
326
+ )
327
+ await self._request_processor.cache_raw_response(
328
+ response, subscription=subscription
329
+ )
330
+ except PersistentConnectionClosedOK as e:
331
+ self.logger.info(
332
+ "Message listener background task has ended gracefully: "
333
+ f"{e.user_message}"
334
+ )
335
+ # trigger a return to end the listener task and initiate the callback fn
336
+ return
337
+ except Exception as e:
338
+ if not self.silence_listener_task_exceptions:
339
+ raise e
340
+ else:
341
+ self._error_log_listener_task_exception(e)
342
+
343
+ def _error_log_listener_task_exception(self, e: Exception) -> None:
344
+ """
345
+ When silencing listener task exceptions, this method is used to log the
346
+ exception and keep the listener task alive. Override this method to fine-tune
347
+ error logging behavior for the implementation class.
348
+ """
349
+ self.logger.error(
350
+ "Exception caught in listener, error logging and keeping "
351
+ "listener background task alive."
352
+ f"\n error={e.__class__.__name__}: {e}"
353
+ )
354
+
355
+ def _handle_listener_task_exceptions(self) -> None:
356
+ """
357
+ Should be called every time a `PersistentConnectionProvider` is polling for
358
+ messages in the main loop. If the message listener task has completed and an
359
+ exception was recorded, raise the exception in the main loop.
360
+ """
361
+ msg_listener_task = getattr(self, "_message_listener_task", None)
362
+ if (
363
+ msg_listener_task
364
+ and msg_listener_task.done()
365
+ and msg_listener_task.exception()
366
+ ):
367
+ raise msg_listener_task.exception()
368
+
62
369
  async def _get_response_for_request_id(
63
- self, request_id: RPCId, timeout: Optional[float] = None
370
+ self, request_id: Union[RPCId, List[RPCId]], timeout: Optional[float] = None
64
371
  ) -> RPCResponse:
65
372
  if timeout is None:
66
373
  timeout = self.request_timeout
@@ -69,24 +376,24 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
69
376
  request_cache_key = generate_cache_key(request_id)
70
377
 
71
378
  while True:
72
- # sleep(0) here seems to be the most efficient way to yield control
73
- # back to the event loop while waiting for the response to be in the
74
- # queue.
75
- await asyncio.sleep(0)
76
-
77
379
  if request_cache_key in self._request_processor._request_response_cache:
78
380
  self.logger.debug(
79
381
  f"Popping response for id {request_id} from cache."
80
382
  )
81
- popped_response = self._request_processor.pop_raw_response(
383
+ popped_response = await self._request_processor.pop_raw_response(
82
384
  cache_key=request_cache_key,
83
385
  )
84
386
  return popped_response
387
+ else:
388
+ # check if an exception was recorded in the listener task and raise
389
+ # it in the main loop if so
390
+ self._handle_listener_task_exceptions()
391
+ await asyncio.sleep(0)
85
392
 
86
393
  try:
87
394
  # Add the request timeout around the while loop that checks the request
88
- # cache and tried to recv(). If the request is neither in the cache, nor
89
- # received within the request_timeout, raise ``TimeExhausted``.
395
+ # cache. If the request is not in the cache within the request_timeout,
396
+ # raise ``TimeExhausted``.
90
397
  return await asyncio.wait_for(_match_response_id_to_request_id(), timeout)
91
398
  except asyncio.TimeoutError:
92
399
  raise TimeExhausted(
@@ -2,9 +2,12 @@ from typing import (
2
2
  TYPE_CHECKING,
3
3
  Any,
4
4
  Dict,
5
+ Union,
6
+ cast,
5
7
  )
6
8
 
7
9
  from web3.types import (
10
+ FormattedEthSubscriptionResponse,
8
11
  RPCEndpoint,
9
12
  RPCResponse,
10
13
  )
@@ -16,6 +19,9 @@ if TYPE_CHECKING:
16
19
  from web3.manager import ( # noqa: F401
17
20
  _AsyncPersistentMessageStream,
18
21
  )
22
+ from web3.providers.persistent import ( # noqa: F401
23
+ PersistentConnectionProvider,
24
+ )
19
25
 
20
26
 
21
27
  class PersistentConnection:
@@ -26,17 +32,60 @@ class PersistentConnection:
26
32
 
27
33
  def __init__(self, w3: "AsyncWeb3"):
28
34
  self._manager = w3.manager
35
+ self.provider = cast("PersistentConnectionProvider", self._manager.provider)
29
36
 
30
- # -- public methods -- #
31
37
  @property
32
38
  def subscriptions(self) -> Dict[str, Any]:
39
+ """
40
+ Return the active subscriptions on the persistent connection.
41
+
42
+ :return: The active subscriptions on the persistent connection.
43
+ :rtype: Dict[str, Any]
44
+ """
33
45
  return self._manager._request_processor.active_subscriptions
34
46
 
35
- async def send(self, method: RPCEndpoint, params: Any) -> RPCResponse:
36
- return await self._manager.send(method, params)
47
+ async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
48
+ """
49
+ Make a request to the persistent connection and return the response. This method
50
+ does not process the response as it would when invoking a method via the
51
+ appropriate module on the `AsyncWeb3` instance,
52
+ e.g. `w3.eth.get_block("latest")`.
53
+
54
+ :param method: The RPC method, e.g. `eth_getBlockByNumber`.
55
+ :param params: The RPC method parameters, e.g. `["0x1337", False]`.
56
+
57
+ :return: The unprocessed response from the persistent connection.
58
+ :rtype: RPCResponse
59
+ """
60
+ return await self.provider.make_request(method, params)
61
+
62
+ async def send(self, method: RPCEndpoint, params: Any) -> None:
63
+ """
64
+ Send a raw, unprocessed message to the persistent connection.
37
65
 
38
- async def recv(self) -> Any:
39
- return await self._manager._get_next_message()
66
+ :param method: The RPC method, e.g. `eth_getBlockByNumber`.
67
+ :param params: The RPC method parameters, e.g. `["0x1337", False]`.
68
+
69
+ :return: None
70
+ """
71
+ await self._manager.send(method, params)
72
+
73
+ async def recv(self) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
74
+ """
75
+ Receive the next unprocessed response for a request from the persistent
76
+ connection.
77
+
78
+ :return: The next unprocessed response for a request from the persistent
79
+ connection.
80
+ :rtype: Union[RPCResponse, FormattedEthSubscriptionResponse]
81
+ """
82
+ return await self._manager.recv()
40
83
 
41
84
  def process_subscriptions(self) -> "_AsyncPersistentMessageStream":
85
+ """
86
+ Asynchronous iterator that yields messages from the subscription message stream.
87
+
88
+ :return: The subscription message stream.
89
+ :rtype: _AsyncPersistentMessageStream
90
+ """
42
91
  return self._manager._persistent_message_stream()