web3 7.6.1__py3-none-any.whl → 7.8.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 (46) hide show
  1. ens/async_ens.py +1 -1
  2. ens/ens.py +1 -1
  3. web3/_utils/caching/caching_utils.py +64 -0
  4. web3/_utils/caching/request_caching_validation.py +1 -0
  5. web3/_utils/events.py +1 -1
  6. web3/_utils/http_session_manager.py +32 -3
  7. web3/_utils/module_testing/eth_module.py +5 -18
  8. web3/_utils/module_testing/module_testing_utils.py +1 -43
  9. web3/_utils/module_testing/persistent_connection_provider.py +696 -207
  10. web3/_utils/module_testing/utils.py +99 -33
  11. web3/beacon/api_endpoints.py +10 -0
  12. web3/beacon/async_beacon.py +47 -0
  13. web3/beacon/beacon.py +45 -0
  14. web3/contract/async_contract.py +2 -206
  15. web3/contract/base_contract.py +217 -13
  16. web3/contract/contract.py +2 -205
  17. web3/datastructures.py +15 -16
  18. web3/eth/async_eth.py +23 -5
  19. web3/exceptions.py +7 -0
  20. web3/main.py +24 -3
  21. web3/manager.py +140 -48
  22. web3/method.py +1 -1
  23. web3/middleware/attrdict.py +12 -22
  24. web3/middleware/base.py +14 -6
  25. web3/module.py +17 -21
  26. web3/providers/async_base.py +23 -14
  27. web3/providers/base.py +6 -8
  28. web3/providers/ipc.py +7 -6
  29. web3/providers/legacy_websocket.py +1 -1
  30. web3/providers/persistent/async_ipc.py +5 -3
  31. web3/providers/persistent/persistent.py +121 -17
  32. web3/providers/persistent/persistent_connection.py +11 -4
  33. web3/providers/persistent/request_processor.py +49 -41
  34. web3/providers/persistent/subscription_container.py +56 -0
  35. web3/providers/persistent/subscription_manager.py +298 -0
  36. web3/providers/persistent/websocket.py +4 -4
  37. web3/providers/rpc/async_rpc.py +16 -3
  38. web3/providers/rpc/rpc.py +9 -5
  39. web3/types.py +28 -14
  40. web3/utils/__init__.py +4 -0
  41. web3/utils/subscriptions.py +289 -0
  42. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/LICENSE +1 -1
  43. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/METADATA +68 -56
  44. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/RECORD +46 -43
  45. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/WHEEL +1 -1
  46. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/top_level.txt +0 -0
web3/eth/async_eth.py CHANGED
@@ -99,11 +99,16 @@ from web3.types import (
99
99
  _Hash32,
100
100
  )
101
101
  from web3.utils import (
102
+ EthSubscription,
102
103
  async_handle_offchain_lookup,
103
104
  )
105
+ from web3.utils.subscriptions import (
106
+ EthSubscriptionHandler,
107
+ )
104
108
 
105
109
  if TYPE_CHECKING:
106
110
  from web3 import AsyncWeb3 # noqa: F401
111
+ from web3.contract.async_contract import AsyncContractEvent # noqa: F401
107
112
 
108
113
 
109
114
  class AsyncEth(BaseEth):
@@ -714,6 +719,9 @@ class AsyncEth(BaseEth):
714
719
  bool, # newPendingTransactions, full_transactions
715
720
  ]
716
721
  ] = None,
722
+ handler: Optional[EthSubscriptionHandler] = None,
723
+ handler_context: Optional[Dict[str, Any]] = None,
724
+ label: Optional[str] = None,
717
725
  ) -> HexStr:
718
726
  if not isinstance(self.w3.provider, PersistentConnectionProvider):
719
727
  raise MethodNotSupported(
@@ -721,10 +729,13 @@ class AsyncEth(BaseEth):
721
729
  "persistent connections."
722
730
  )
723
731
 
724
- if subscription_arg is None:
725
- return await self._subscribe(subscription_type)
726
-
727
- return await self._subscribe_with_args(subscription_type, subscription_arg)
732
+ sub = EthSubscription._create_type_aware_subscription(
733
+ subscription_params=(subscription_type, subscription_arg),
734
+ handler=handler,
735
+ handler_context=handler_context or {},
736
+ label=label,
737
+ )
738
+ return await self.w3.subscription_manager.subscribe(sub)
728
739
 
729
740
  _unsubscribe: Method[Callable[[HexStr], Awaitable[bool]]] = Method(
730
741
  RPC.eth_unsubscribe,
@@ -738,7 +749,14 @@ class AsyncEth(BaseEth):
738
749
  "persistent connections."
739
750
  )
740
751
 
741
- return await self._unsubscribe(subscription_id)
752
+ for sub in self.w3.subscription_manager.subscriptions:
753
+ if sub._id == subscription_id:
754
+ return await sub.unsubscribe()
755
+
756
+ raise Web3ValueError(
757
+ f"Cannot unsubscribe subscription with id `{subscription_id}`. "
758
+ "Subscription not found."
759
+ )
742
760
 
743
761
  # -- contract methods -- #
744
762
 
web3/exceptions.py CHANGED
@@ -353,6 +353,13 @@ class PersistentConnectionClosedOK(PersistentConnectionError):
353
353
  """
354
354
 
355
355
 
356
+ class SubscriptionProcessingFinished(Web3Exception):
357
+ """
358
+ Raised to alert the subscription manager that the processing of subscriptions
359
+ has finished.
360
+ """
361
+
362
+
356
363
  class Web3RPCError(Web3Exception):
357
364
  """
358
365
  Raised when a JSON-RPC response contains an error field.
web3/main.py CHANGED
@@ -148,6 +148,9 @@ from web3.tracing import (
148
148
  from web3.types import (
149
149
  Wei,
150
150
  )
151
+ from web3.providers.persistent.subscription_manager import (
152
+ SubscriptionManager,
153
+ )
151
154
 
152
155
  if TYPE_CHECKING:
153
156
  from web3._utils.batching import RequestBatcher # noqa: F401
@@ -508,12 +511,27 @@ class AsyncWeb3(BaseWeb3):
508
511
  new_ens.w3 = self # set self object reference for ``AsyncENS.w3``
509
512
  self._ens = new_ens
510
513
 
511
- # -- persistent connection methods -- #
514
+ # -- persistent connection settings -- #
515
+
516
+ _subscription_manager: Optional[SubscriptionManager] = None
517
+ _persistent_connection: Optional["PersistentConnection"] = None
518
+
519
+ @property
520
+ @persistent_connection_provider_method()
521
+ def subscription_manager(self) -> SubscriptionManager:
522
+ """
523
+ Access the subscription manager for the current PersistentConnectionProvider.
524
+ """
525
+ if not self._subscription_manager:
526
+ self._subscription_manager = SubscriptionManager(self)
527
+ return self._subscription_manager
512
528
 
513
529
  @property
514
530
  @persistent_connection_provider_method()
515
531
  def socket(self) -> PersistentConnection:
516
- return PersistentConnection(self)
532
+ if self._persistent_connection is None:
533
+ self._persistent_connection = PersistentConnection(self)
534
+ return self._persistent_connection
517
535
 
518
536
  # w3 = await AsyncWeb3(PersistentConnectionProvider(...))
519
537
  @persistent_connection_provider_method(
@@ -522,7 +540,10 @@ class AsyncWeb3(BaseWeb3):
522
540
  )
523
541
  def __await__(self) -> Generator[Any, None, Self]:
524
542
  async def __async_init__() -> Self:
525
- await self.provider.connect()
543
+ provider = cast("PersistentConnectionProvider", self.provider)
544
+ await provider.connect()
545
+ # set signal handlers since not within a context manager
546
+ provider._set_signal_handlers()
526
547
  return self
527
548
 
528
549
  return __async_init__().__await__()
web3/manager.py CHANGED
@@ -6,7 +6,9 @@ from typing import (
6
6
  AsyncGenerator,
7
7
  Callable,
8
8
  Coroutine,
9
+ Dict,
9
10
  List,
11
+ NoReturn,
10
12
  Optional,
11
13
  Sequence,
12
14
  Tuple,
@@ -69,7 +71,9 @@ from web3.providers.async_base import (
69
71
  AsyncJSONBaseProvider,
70
72
  )
71
73
  from web3.types import (
74
+ FormattedEthSubscriptionResponse,
72
75
  RPCEndpoint,
76
+ RPCRequest,
73
77
  RPCResponse,
74
78
  )
75
79
 
@@ -255,15 +259,38 @@ def _validate_response(
255
259
  web3_rpc_error = Web3RPCError(repr(error), rpc_response=response)
256
260
 
257
261
  response = apply_error_formatters(error_formatters, response)
258
-
259
- logger.error(web3_rpc_error.user_message)
260
262
  logger.debug(f"RPC error response: {response}")
263
+
261
264
  raise web3_rpc_error
262
265
 
263
266
  elif "result" not in response and not is_subscription_response:
264
267
  _raise_bad_response_format(response)
265
268
 
266
269
 
270
+ def _raise_error_for_batch_response(
271
+ response: RPCResponse,
272
+ logger: Optional[logging.Logger] = None,
273
+ ) -> NoReturn:
274
+ error = response.get("error")
275
+ if error is None:
276
+ _raise_bad_response_format(
277
+ response,
278
+ "Batch response must be formatted as a list of responses or "
279
+ "as a single JSON-RPC error response.",
280
+ )
281
+ _validate_response(
282
+ response,
283
+ None,
284
+ is_subscription_response=False,
285
+ logger=logger,
286
+ params=[],
287
+ )
288
+ # This should not be reached, but if it is, raise a generic `BadResponseFormat`
289
+ raise BadResponseFormat(
290
+ "Batch response was in an unexpected format and unable to be parsed."
291
+ )
292
+
293
+
267
294
  class RequestManager:
268
295
  logger = logging.getLogger("web3.manager.RequestManager")
269
296
 
@@ -293,9 +320,6 @@ class RequestManager:
293
320
  provider = cast(PersistentConnectionProvider, self.provider)
294
321
  self._request_processor: RequestProcessor = provider._request_processor
295
322
 
296
- w3: Union["AsyncWeb3", "Web3"] = None
297
- _provider = None
298
-
299
323
  @property
300
324
  def provider(self) -> Union["BaseProvider", "AsyncBaseProvider"]:
301
325
  return self._provider
@@ -437,17 +461,23 @@ class RequestManager:
437
461
  request_func = provider.batch_request_func(
438
462
  cast("Web3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
439
463
  )
440
- responses = request_func(
464
+ response = request_func(
441
465
  [
442
466
  (method, params)
443
467
  for (method, params), _response_formatters in requests_info
444
468
  ]
445
469
  )
446
- formatted_responses = [
447
- self._format_batched_response(info, resp)
448
- for info, resp in zip(requests_info, responses)
449
- ]
450
- return list(formatted_responses)
470
+
471
+ if isinstance(response, list):
472
+ # expected format
473
+ formatted_responses = [
474
+ self._format_batched_response(info, cast(RPCResponse, resp))
475
+ for info, resp in zip(requests_info, response)
476
+ ]
477
+ return list(formatted_responses)
478
+ else:
479
+ # expect a single response with an error
480
+ _raise_error_for_batch_response(response, self.logger)
451
481
 
452
482
  async def _async_make_batch_request(
453
483
  self,
@@ -466,22 +496,31 @@ class RequestManager:
466
496
  # since we add items to the batch without awaiting, we unpack the coroutines
467
497
  # and await them all here
468
498
  unpacked_requests_info = await asyncio.gather(*requests_info)
469
- responses = await request_func(
499
+ response = await request_func(
470
500
  [
471
501
  (method, params)
472
502
  for (method, params), _response_formatters in unpacked_requests_info
473
503
  ]
474
504
  )
475
505
 
476
- if isinstance(self.provider, PersistentConnectionProvider):
477
- # call _process_response for each response in the batch
478
- return [await self._process_response(resp) for resp in responses]
479
-
480
- formatted_responses = [
481
- self._format_batched_response(info, resp)
482
- for info, resp in zip(unpacked_requests_info, responses)
483
- ]
484
- return list(formatted_responses)
506
+ if isinstance(response, list):
507
+ # expected format
508
+ response = cast(List[RPCResponse], response)
509
+ if isinstance(self.provider, PersistentConnectionProvider):
510
+ # call _process_response for each response in the batch
511
+ return [
512
+ cast(RPCResponse, await self._process_response(resp))
513
+ for resp in response
514
+ ]
515
+
516
+ formatted_responses = [
517
+ self._format_batched_response(info, resp)
518
+ for info, resp in zip(unpacked_requests_info, response)
519
+ ]
520
+ return list(formatted_responses)
521
+ else:
522
+ # expect a single response with an error
523
+ _raise_error_for_batch_response(response, self.logger)
485
524
 
486
525
  def _format_batched_response(
487
526
  self,
@@ -489,6 +528,13 @@ class RequestManager:
489
528
  response: RPCResponse,
490
529
  ) -> RPCResponse:
491
530
  result_formatters, error_formatters, null_result_formatters = requests_info[1]
531
+ _validate_response(
532
+ response,
533
+ error_formatters,
534
+ is_subscription_response=False,
535
+ logger=self.logger,
536
+ params=requests_info[0][1],
537
+ )
492
538
  return apply_result_formatters(
493
539
  result_formatters,
494
540
  self.formatted_response(
@@ -501,32 +547,65 @@ class RequestManager:
501
547
 
502
548
  # -- persistent connection -- #
503
549
 
504
- async def socket_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
550
+ async def socket_request(
551
+ self,
552
+ method: RPCEndpoint,
553
+ params: Any,
554
+ response_formatters: Optional[
555
+ Tuple[Dict[str, Callable[..., Any]], Callable[..., Any], Callable[..., Any]]
556
+ ] = None,
557
+ ) -> RPCResponse:
505
558
  provider = cast(PersistentConnectionProvider, self._provider)
506
- request_func = await provider.request_func(
507
- cast("AsyncWeb3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
508
- )
509
559
  self.logger.debug(
510
560
  "Making request to open socket connection and waiting for response: "
511
- f"{provider.get_endpoint_uri_or_ipc_path()}, method: {method}"
561
+ f"{provider.get_endpoint_uri_or_ipc_path()},\n method: {method},\n"
562
+ f" params: {params}"
512
563
  )
513
- response = await request_func(method, params)
514
- return await self._process_response(response)
564
+ rpc_request = await self.send(method, params)
565
+ provider._request_processor.cache_request_information(
566
+ rpc_request["id"],
567
+ rpc_request["method"],
568
+ rpc_request["params"],
569
+ response_formatters=response_formatters or ((), (), ()),
570
+ )
571
+ return await self.recv_for_request(rpc_request)
515
572
 
516
- async def send(self, method: RPCEndpoint, params: Any) -> None:
573
+ async def send(self, method: RPCEndpoint, params: Any) -> RPCRequest:
517
574
  provider = cast(PersistentConnectionProvider, self._provider)
518
- # run through the request processors of the middleware
519
- for mw_class in self.middleware_onion.as_tuple_of_middleware():
520
- mw = mw_class(self.w3)
521
- method, params = mw.request_processor(method, params)
522
-
575
+ async_w3 = cast("AsyncWeb3", self.w3)
576
+ middleware_onion = cast("MiddlewareOnion", self.middleware_onion)
577
+ send_func = await provider.send_func(
578
+ async_w3,
579
+ middleware_onion,
580
+ )
523
581
  self.logger.debug(
524
582
  "Sending request to open socket connection: "
525
- f"{provider.get_endpoint_uri_or_ipc_path()}, method: {method}"
583
+ f"{provider.get_endpoint_uri_or_ipc_path()},\n method: {method},\n"
584
+ f" params: {params}"
526
585
  )
527
- await provider.socket_send(provider.encode_rpc_request(method, params))
586
+ return await send_func(method, params)
528
587
 
529
- async def recv(self) -> RPCResponse:
588
+ async def recv_for_request(self, rpc_request: RPCRequest) -> RPCResponse:
589
+ provider = cast(PersistentConnectionProvider, self._provider)
590
+ async_w3 = cast("AsyncWeb3", self.w3)
591
+ middleware_onion = cast("MiddlewareOnion", self.middleware_onion)
592
+ recv_func = await provider.recv_func(
593
+ async_w3,
594
+ middleware_onion,
595
+ )
596
+ self.logger.debug(
597
+ "Getting response for request from open socket connection:\n"
598
+ f" request: {rpc_request}"
599
+ )
600
+ response = await recv_func(rpc_request)
601
+ try:
602
+ return cast(RPCResponse, await self._process_response(response))
603
+ except Exception:
604
+ response_id_key = generate_cache_key(response["id"])
605
+ provider._request_processor._request_information_cache.pop(response_id_key)
606
+ raise
607
+
608
+ async def recv(self) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
530
609
  provider = cast(PersistentConnectionProvider, self._provider)
531
610
  self.logger.debug(
532
611
  "Getting next response from open socket connection: "
@@ -544,15 +623,18 @@ class RequestManager:
544
623
  def _persistent_message_stream(self) -> "_AsyncPersistentMessageStream":
545
624
  return _AsyncPersistentMessageStream(self)
546
625
 
547
- async def _get_next_message(self) -> RPCResponse:
626
+ async def _get_next_message(self) -> FormattedEthSubscriptionResponse:
548
627
  return await self._message_stream().__anext__()
549
628
 
550
- async def _message_stream(self) -> AsyncGenerator[RPCResponse, None]:
629
+ async def _message_stream(
630
+ self,
631
+ ) -> AsyncGenerator[FormattedEthSubscriptionResponse, None]:
551
632
  if not isinstance(self._provider, PersistentConnectionProvider):
552
633
  raise Web3TypeError(
553
634
  "Only providers that maintain an open, persistent connection "
554
635
  "can listen to streams."
555
636
  )
637
+ async_w3 = cast("AsyncWeb3", self.w3)
556
638
 
557
639
  if self._provider._message_listener_task is None:
558
640
  raise ProviderConnectionError(
@@ -564,13 +646,21 @@ class RequestManager:
564
646
  response = await self._request_processor.pop_raw_response(
565
647
  subscription=True
566
648
  )
567
- if (
568
- response is not None
569
- and response.get("params", {}).get("subscription")
570
- in self._request_processor.active_subscriptions
571
- ):
572
- # if response is an active subscription response, process it
573
- yield await self._process_response(response)
649
+ # if the subscription was unsubscribed from, we won't have a formatted
650
+ # response because we lost the request information.
651
+ sub_id = response.get(
652
+ "subscription", response.get("params", {}).get("subscription")
653
+ )
654
+ if async_w3.subscription_manager.get_by_id(sub_id):
655
+ # if active subscription, process and yield the formatted response
656
+ formatted_sub_response = cast(
657
+ FormattedEthSubscriptionResponse,
658
+ await self._process_response(response),
659
+ )
660
+ yield formatted_sub_response
661
+ else:
662
+ # if not an active sub, skip processing and continue
663
+ continue
574
664
  except TaskNotRunning:
575
665
  await asyncio.sleep(0)
576
666
  self._provider._handle_listener_task_exceptions()
@@ -580,7 +670,9 @@ class RequestManager:
580
670
  )
581
671
  return
582
672
 
583
- async def _process_response(self, response: RPCResponse) -> RPCResponse:
673
+ async def _process_response(
674
+ self, response: RPCResponse
675
+ ) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
584
676
  provider = cast(PersistentConnectionProvider, self._provider)
585
677
  request_info = self._request_processor.get_request_information_for_response(
586
678
  response
@@ -643,5 +735,5 @@ class _AsyncPersistentMessageStream:
643
735
  def __aiter__(self) -> Self:
644
736
  return self
645
737
 
646
- async def __anext__(self) -> RPCResponse:
738
+ async def __anext__(self) -> FormattedEthSubscriptionResponse:
647
739
  return await self.manager._get_next_message()
web3/method.py CHANGED
@@ -203,7 +203,7 @@ class Method(Generic[TFunc]):
203
203
  def process_params(
204
204
  self, module: "Module", *args: Any, **kwargs: Any
205
205
  ) -> Tuple[
206
- Tuple[Union[RPCEndpoint, Callable[..., RPCEndpoint]], Tuple[Any, ...]],
206
+ Tuple[Union[RPCEndpoint, Callable[..., RPCEndpoint]], Tuple[RPCEndpoint, ...]],
207
207
  Tuple[
208
208
  Union[TReturn, Dict[str, Callable[..., Any]]],
209
209
  Callable[..., Any],
@@ -7,10 +7,6 @@ from typing import (
7
7
  cast,
8
8
  )
9
9
 
10
- from eth_utils.toolz import (
11
- assoc,
12
- )
13
-
14
10
  from web3.datastructures import (
15
11
  AttributeDict,
16
12
  )
@@ -33,21 +29,18 @@ if TYPE_CHECKING:
33
29
 
34
30
 
35
31
  def _handle_async_response(response: "RPCResponse") -> "RPCResponse":
32
+ """
33
+ Process the RPC response by converting nested dictionaries into AttributeDict.
34
+ """
36
35
  if "result" in response:
37
- return assoc(response, "result", AttributeDict.recursive(response["result"]))
36
+ response["result"] = AttributeDict.recursive(response["result"])
38
37
  elif "params" in response and "result" in response["params"]:
39
- # this is a subscription response
40
- return assoc(
41
- response,
42
- "params",
43
- assoc(
44
- response["params"],
45
- "result",
46
- AttributeDict.recursive(response["params"]["result"]),
47
- ),
38
+ # subscription response
39
+ response["params"]["result"] = AttributeDict.recursive(
40
+ response["params"]["result"]
48
41
  )
49
- else:
50
- return response
42
+
43
+ return response
51
44
 
52
45
 
53
46
  class AttributeDictMiddleware(Web3Middleware, ABC):
@@ -60,11 +53,9 @@ class AttributeDictMiddleware(Web3Middleware, ABC):
60
53
 
61
54
  def response_processor(self, method: "RPCEndpoint", response: "RPCResponse") -> Any:
62
55
  if "result" in response:
63
- return assoc(
64
- response, "result", AttributeDict.recursive(response["result"])
65
- )
66
- else:
67
- return response
56
+ new_result = AttributeDict.recursive(response["result"])
57
+ response = {**response, "result": new_result}
58
+ return response
68
59
 
69
60
  # -- async -- #
70
61
 
@@ -72,7 +63,6 @@ class AttributeDictMiddleware(Web3Middleware, ABC):
72
63
  self, method: "RPCEndpoint", response: "RPCResponse"
73
64
  ) -> Any:
74
65
  if self._w3.provider.has_persistent_connection:
75
- # asynchronous response processing
76
66
  provider = cast("PersistentConnectionProvider", self._w3.provider)
77
67
  provider._request_processor.append_middleware_response_processor(
78
68
  response, _handle_async_response
web3/middleware/base.py CHANGED
@@ -62,15 +62,19 @@ class Web3Middleware:
62
62
  ) -> "MakeBatchRequestFn":
63
63
  def middleware(
64
64
  requests_info: List[Tuple["RPCEndpoint", Any]]
65
- ) -> List["RPCResponse"]:
65
+ ) -> Union[List["RPCResponse"], "RPCResponse"]:
66
66
  req_processed = [
67
67
  self.request_processor(method, params)
68
68
  for (method, params) in requests_info
69
69
  ]
70
- responses = make_batch_request(req_processed)
70
+ response = make_batch_request(req_processed)
71
+ if not isinstance(response, list):
72
+ # RPC errors return only one response with the error object
73
+ return response
74
+
71
75
  methods, _params = zip(*req_processed)
72
76
  formatted_responses = [
73
- self.response_processor(m, r) for m, r in zip(methods, responses)
77
+ self.response_processor(m, r) for m, r in zip(methods, response)
74
78
  ]
75
79
  return formatted_responses
76
80
 
@@ -103,16 +107,20 @@ class Web3Middleware:
103
107
  ) -> "AsyncMakeBatchRequestFn":
104
108
  async def middleware(
105
109
  requests_info: List[Tuple["RPCEndpoint", Any]]
106
- ) -> List["RPCResponse"]:
110
+ ) -> Union[List["RPCResponse"], "RPCResponse"]:
107
111
  req_processed = [
108
112
  await self.async_request_processor(method, params)
109
113
  for (method, params) in requests_info
110
114
  ]
111
- responses = await make_batch_request(req_processed)
115
+ response = await make_batch_request(req_processed)
116
+ if not isinstance(response, list):
117
+ # RPC errors return only one response with the error object
118
+ return response
119
+
112
120
  methods, _params = zip(*req_processed)
113
121
  formatted_responses = [
114
122
  await self.async_response_processor(m, r)
115
- for m, r in zip(methods, responses)
123
+ for m, r in zip(methods, response)
116
124
  ]
117
125
  return formatted_responses
118
126
 
web3/module.py CHANGED
@@ -32,6 +32,7 @@ from web3.providers.persistent import (
32
32
  PersistentConnectionProvider,
33
33
  )
34
34
  from web3.types import (
35
+ FormattedEthSubscriptionResponse,
35
36
  RPCEndpoint,
36
37
  RPCResponse,
37
38
  )
@@ -74,7 +75,7 @@ def retrieve_request_information_for_batching(
74
75
  )
75
76
  if isinstance(w3.provider, PersistentConnectionProvider):
76
77
  w3.provider._request_processor.cache_request_information(
77
- cast(RPCEndpoint, method_str), params, response_formatters
78
+ None, cast(RPCEndpoint, method_str), params, response_formatters
78
79
  )
79
80
  return (cast(RPCEndpoint, method_str), params), response_formatters
80
81
 
@@ -121,8 +122,17 @@ def retrieve_async_method_call_fn(
121
122
  async_w3: "AsyncWeb3",
122
123
  module: "Module",
123
124
  method: Method[Callable[..., Any]],
124
- ) -> Callable[..., Coroutine[Any, Any, Optional[Union[RPCResponse, AsyncLogFilter]]]]:
125
- async def caller(*args: Any, **kwargs: Any) -> Union[RPCResponse, AsyncLogFilter]:
125
+ ) -> Callable[
126
+ ...,
127
+ Coroutine[
128
+ Any,
129
+ Any,
130
+ Optional[Union[RPCResponse, FormattedEthSubscriptionResponse, AsyncLogFilter]],
131
+ ],
132
+ ]:
133
+ async def caller(
134
+ *args: Any, **kwargs: Any
135
+ ) -> Union[RPCResponse, FormattedEthSubscriptionResponse, AsyncLogFilter]:
126
136
  try:
127
137
  (method_str, params), response_formatters = method.process_params(
128
138
  module, *args, **kwargs
@@ -131,31 +141,17 @@ def retrieve_async_method_call_fn(
131
141
  return AsyncLogFilter(eth_module=module, filter_id=err.filter_id)
132
142
 
133
143
  if isinstance(async_w3.provider, PersistentConnectionProvider):
134
- provider = async_w3.provider
135
- cache_key = provider._request_processor.cache_request_information(
136
- cast(RPCEndpoint, method_str), params, response_formatters
144
+ return await async_w3.manager.socket_request(
145
+ cast(RPCEndpoint, method_str),
146
+ params,
147
+ response_formatters=response_formatters,
137
148
  )
138
-
139
- try:
140
- method_str = cast(RPCEndpoint, method_str)
141
- return await async_w3.manager.socket_request(method_str, params)
142
- except Exception as e:
143
- if (
144
- cache_key is not None
145
- and cache_key
146
- in provider._request_processor._request_information_cache
147
- ):
148
- provider._request_processor.pop_cached_request_information(
149
- cache_key
150
- )
151
- raise e
152
149
  else:
153
150
  (
154
151
  result_formatters,
155
152
  error_formatters,
156
153
  null_result_formatters,
157
154
  ) = response_formatters
158
-
159
155
  result = await async_w3.manager.coro_request(
160
156
  method_str, params, error_formatters, null_result_formatters
161
157
  )