web3 7.0.0b5__py3-none-any.whl → 7.0.0b6__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.
@@ -5,6 +5,7 @@ from typing import (
5
5
  Any,
6
6
  Callable,
7
7
  Coroutine,
8
+ List,
8
9
  Optional,
9
10
  Set,
10
11
  Tuple,
@@ -18,6 +19,7 @@ from eth_utils import (
18
19
  )
19
20
 
20
21
  from web3._utils.caching import (
22
+ CACHEABLE_REQUESTS,
21
23
  async_handle_request_caching,
22
24
  )
23
25
  from web3._utils.encoding import (
@@ -56,41 +58,33 @@ if TYPE_CHECKING:
56
58
  )
57
59
 
58
60
 
59
- CACHEABLE_REQUESTS = cast(
60
- Set[RPCEndpoint],
61
- (
62
- "eth_chainId",
63
- "eth_getBlockByHash",
64
- "eth_getBlockTransactionCountByHash",
65
- "eth_getRawTransactionByHash",
66
- "eth_getTransactionByBlockHashAndIndex",
67
- "eth_getTransactionByHash",
68
- "eth_getUncleByBlockHashAndIndex",
69
- "eth_getUncleCountByBlockHash",
70
- "net_version",
71
- "web3_clientVersion",
72
- ),
73
- )
74
-
75
-
76
61
  class AsyncBaseProvider:
77
62
  _request_func_cache: Tuple[
78
63
  Tuple[Middleware, ...], Callable[..., Coroutine[Any, Any, RPCResponse]]
79
64
  ] = (None, None)
80
65
 
66
+ _is_batching: bool = False
67
+ _batch_request_func_cache: Tuple[
68
+ Tuple[Middleware, ...], Callable[..., Coroutine[Any, Any, List[RPCResponse]]]
69
+ ] = (None, None)
70
+
81
71
  is_async = True
82
72
  has_persistent_connection = False
83
73
  global_ccip_read_enabled: bool = True
84
74
  ccip_read_max_redirects: int = 4
85
75
 
86
76
  # request caching
87
- cache_allowed_requests: bool = False
88
- cacheable_requests: Set[RPCEndpoint] = CACHEABLE_REQUESTS
89
77
  _request_cache: SimpleCache
90
78
  _request_cache_lock: asyncio.Lock = asyncio.Lock()
91
79
 
92
- def __init__(self) -> None:
80
+ def __init__(
81
+ self,
82
+ cache_allowed_requests: bool = False,
83
+ cacheable_requests: Set[RPCEndpoint] = None,
84
+ ) -> None:
93
85
  self._request_cache = SimpleCache(1000)
86
+ self.cache_allowed_requests = cache_allowed_requests
87
+ self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
94
88
 
95
89
  async def request_func(
96
90
  self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
@@ -109,10 +103,34 @@ class AsyncBaseProvider:
109
103
  )
110
104
  return self._request_func_cache[-1]
111
105
 
106
+ async def batch_request_func(
107
+ self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
108
+ ) -> Callable[..., Coroutine[Any, Any, List[RPCResponse]]]:
109
+ middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
110
+
111
+ cache_key = self._batch_request_func_cache[0]
112
+ if cache_key != middleware:
113
+ accumulator_fn = self.make_batch_request
114
+ for mw in reversed(middleware):
115
+ initialized = mw(async_w3)
116
+ # type ignore bc in order to wrap the method, we have to call
117
+ # `async_wrap_make_batch_request` with the accumulator_fn as the
118
+ # argument which breaks the type hinting for this particular case.
119
+ accumulator_fn = await initialized.async_wrap_make_batch_request( # type: ignore # noqa: E501
120
+ accumulator_fn
121
+ )
122
+ self._batch_request_func_cache = (middleware, accumulator_fn)
123
+ return self._batch_request_func_cache[-1]
124
+
112
125
  @async_handle_request_caching
113
126
  async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
114
127
  raise NotImplementedError("Providers must implement this method")
115
128
 
129
+ async def make_batch_request(
130
+ self, requests: List[Tuple[RPCEndpoint, Any]]
131
+ ) -> List[RPCResponse]:
132
+ raise NotImplementedError("Only AsyncHTTPProvider supports this method")
133
+
116
134
  async def is_connected(self, show_traceback: bool = False) -> bool:
117
135
  raise NotImplementedError("Providers must implement this method")
118
136
 
@@ -141,9 +159,9 @@ class AsyncBaseProvider:
141
159
 
142
160
 
143
161
  class AsyncJSONBaseProvider(AsyncBaseProvider):
144
- def __init__(self) -> None:
145
- super().__init__()
162
+ def __init__(self, **kwargs: Any) -> None:
146
163
  self.request_counter = itertools.count()
164
+ super().__init__(**kwargs)
147
165
 
148
166
  def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes:
149
167
  request_id = next(self.request_counter)
@@ -156,7 +174,8 @@ class AsyncJSONBaseProvider(AsyncBaseProvider):
156
174
  encoded = FriendlyJsonSerde().json_encode(rpc_dict, cls=Web3JsonEncoder)
157
175
  return to_bytes(text=encoded)
158
176
 
159
- def decode_rpc_response(self, raw_response: bytes) -> RPCResponse:
177
+ @staticmethod
178
+ def decode_rpc_response(raw_response: bytes) -> RPCResponse:
160
179
  text_response = str(
161
180
  to_text(raw_response) if not is_text(raw_response) else raw_response
162
181
  )
@@ -185,3 +204,16 @@ class AsyncJSONBaseProvider(AsyncBaseProvider):
185
204
  if show_traceback:
186
205
  raise ProviderConnectionError(f"Bad jsonrpc version: {response}")
187
206
  return False
207
+
208
+ # -- batch requests -- #
209
+
210
+ def encode_batch_rpc_request(
211
+ self, requests: List[Tuple[RPCEndpoint, Any]]
212
+ ) -> bytes:
213
+ return (
214
+ b"["
215
+ + b", ".join(
216
+ self.encode_rpc_request(method, params) for method, params in requests
217
+ )
218
+ + b"]"
219
+ )
web3/providers/base.py CHANGED
@@ -4,6 +4,7 @@ from typing import (
4
4
  TYPE_CHECKING,
5
5
  Any,
6
6
  Callable,
7
+ List,
7
8
  Set,
8
9
  Tuple,
9
10
  cast,
@@ -15,6 +16,7 @@ from eth_utils import (
15
16
  )
16
17
 
17
18
  from web3._utils.caching import (
19
+ CACHEABLE_REQUESTS,
18
20
  handle_request_caching,
19
21
  )
20
22
  from web3._utils.encoding import (
@@ -43,23 +45,6 @@ if TYPE_CHECKING:
43
45
  from web3 import Web3 # noqa: F401
44
46
 
45
47
 
46
- CACHEABLE_REQUESTS = cast(
47
- Set[RPCEndpoint],
48
- (
49
- "eth_chainId",
50
- "eth_getBlockByHash",
51
- "eth_getBlockTransactionCountByHash",
52
- "eth_getRawTransactionByHash",
53
- "eth_getTransactionByBlockHashAndIndex",
54
- "eth_getTransactionByHash",
55
- "eth_getUncleByBlockHashAndIndex",
56
- "eth_getUncleCountByBlockHash",
57
- "net_version",
58
- "web3_clientVersion",
59
- ),
60
- )
61
-
62
-
63
48
  class BaseProvider:
64
49
  # a tuple of (middleware, request_func)
65
50
  _request_func_cache: Tuple[Tuple[Middleware, ...], Callable[..., RPCResponse]] = (
@@ -73,13 +58,17 @@ class BaseProvider:
73
58
  ccip_read_max_redirects: int = 4
74
59
 
75
60
  # request caching
76
- cache_allowed_requests: bool = False
77
- cacheable_requests: Set[RPCEndpoint] = CACHEABLE_REQUESTS
78
61
  _request_cache: SimpleCache
79
62
  _request_cache_lock: threading.Lock = threading.Lock()
80
63
 
81
- def __init__(self) -> None:
64
+ def __init__(
65
+ self,
66
+ cache_allowed_requests: bool = False,
67
+ cacheable_requests: Set[RPCEndpoint] = None,
68
+ ) -> None:
82
69
  self._request_cache = SimpleCache(1000)
70
+ self.cache_allowed_requests = cache_allowed_requests
71
+ self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
83
72
 
84
73
  def request_func(
85
74
  self, w3: "Web3", middleware_onion: MiddlewareOnion
@@ -115,13 +104,14 @@ class BaseProvider:
115
104
 
116
105
 
117
106
  class JSONBaseProvider(BaseProvider):
118
- def __init__(self) -> None:
119
- self.request_counter = itertools.count()
120
- super().__init__()
107
+ _is_batching: bool = False
108
+ _batch_request_func_cache: Tuple[
109
+ Tuple[Middleware, ...], Callable[..., List[RPCResponse]]
110
+ ] = (None, None)
121
111
 
122
- def decode_rpc_response(self, raw_response: bytes) -> RPCResponse:
123
- text_response = to_text(raw_response)
124
- return cast(RPCResponse, FriendlyJsonSerde().json_decode(text_response))
112
+ def __init__(self, **kwargs: Any) -> None:
113
+ self.request_counter = itertools.count()
114
+ super().__init__(**kwargs)
125
115
 
126
116
  def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes:
127
117
  rpc_dict = {
@@ -133,6 +123,11 @@ class JSONBaseProvider(BaseProvider):
133
123
  encoded = FriendlyJsonSerde().json_encode(rpc_dict, Web3JsonEncoder)
134
124
  return to_bytes(text=encoded)
135
125
 
126
+ @staticmethod
127
+ def decode_rpc_response(raw_response: bytes) -> RPCResponse:
128
+ text_response = to_text(raw_response)
129
+ return cast(RPCResponse, FriendlyJsonSerde().json_decode(text_response))
130
+
136
131
  def is_connected(self, show_traceback: bool = False) -> bool:
137
132
  try:
138
133
  response = self.make_request(RPCEndpoint("web3_clientVersion"), [])
@@ -156,3 +151,41 @@ class JSONBaseProvider(BaseProvider):
156
151
  if show_traceback:
157
152
  raise ProviderConnectionError(f"Bad jsonrpc version: {response}")
158
153
  return False
154
+
155
+ # -- batch requests -- #
156
+
157
+ def batch_request_func(
158
+ self, w3: "Web3", middleware_onion: MiddlewareOnion
159
+ ) -> Callable[..., List[RPCResponse]]:
160
+ middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
161
+
162
+ cache_key = self._batch_request_func_cache[0]
163
+ if cache_key != middleware:
164
+ accumulator_fn = self.make_batch_request
165
+ for mw in reversed(middleware):
166
+ initialized = mw(w3)
167
+ # type ignore bc in order to wrap the method, we have to call
168
+ # `wrap_make_batch_request` with the accumulator_fn as the argument
169
+ # which breaks the type hinting for this particular case.
170
+ accumulator_fn = initialized.wrap_make_batch_request(
171
+ accumulator_fn
172
+ ) # type: ignore # noqa: E501
173
+ self._batch_request_func_cache = (middleware, accumulator_fn)
174
+
175
+ return self._batch_request_func_cache[-1]
176
+
177
+ def encode_batch_rpc_request(
178
+ self, requests: List[Tuple[RPCEndpoint, Any]]
179
+ ) -> bytes:
180
+ return (
181
+ b"["
182
+ + b", ".join(
183
+ self.encode_rpc_request(method, params) for method, params in requests
184
+ )
185
+ + b"]"
186
+ )
187
+
188
+ def make_batch_request(
189
+ self, requests: List[Tuple[RPCEndpoint, Any]]
190
+ ) -> List[RPCResponse]:
191
+ raise NotImplementedError("Providers must implement this method")
web3/providers/ipc.py CHANGED
@@ -14,8 +14,11 @@ from types import (
14
14
  )
15
15
  from typing import (
16
16
  Any,
17
+ List,
18
+ Tuple,
17
19
  Type,
18
20
  Union,
21
+ cast,
19
22
  )
20
23
 
21
24
  from web3._utils.threads import (
@@ -26,6 +29,9 @@ from web3.types import (
26
29
  RPCResponse,
27
30
  )
28
31
 
32
+ from .._utils.batching import (
33
+ sort_batch_response_by_response_ids,
34
+ )
29
35
  from ..exceptions import (
30
36
  Web3TypeError,
31
37
  Web3ValueError,
@@ -135,7 +141,6 @@ class IPCProvider(JSONBaseProvider):
135
141
  self,
136
142
  ipc_path: Union[str, Path] = None,
137
143
  timeout: int = 30,
138
- *args: Any,
139
144
  **kwargs: Any,
140
145
  ) -> None:
141
146
  if ipc_path is None:
@@ -148,17 +153,12 @@ class IPCProvider(JSONBaseProvider):
148
153
  self.timeout = timeout
149
154
  self._lock = threading.Lock()
150
155
  self._socket = PersistantSocket(self.ipc_path)
151
- super().__init__()
156
+ super().__init__(**kwargs)
152
157
 
153
158
  def __str__(self) -> str:
154
159
  return f"<{self.__class__.__name__} {self.ipc_path}>"
155
160
 
156
- def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
157
- self.logger.debug(
158
- f"Making request IPC. Path: {self.ipc_path}, Method: {method}"
159
- )
160
- request = self.encode_rpc_request(method, params)
161
-
161
+ def _make_request(self, request: bytes) -> RPCResponse:
162
162
  with self._lock, self._socket as sock:
163
163
  try:
164
164
  sock.sendall(request)
@@ -189,6 +189,21 @@ class IPCProvider(JSONBaseProvider):
189
189
  timeout.sleep(0)
190
190
  continue
191
191
 
192
+ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
193
+ self.logger.debug(
194
+ f"Making request IPC. Path: {self.ipc_path}, Method: {method}"
195
+ )
196
+ request = self.encode_rpc_request(method, params)
197
+ return self._make_request(request)
198
+
199
+ def make_batch_request(
200
+ self, requests: List[Tuple[RPCEndpoint, Any]]
201
+ ) -> List[RPCResponse]:
202
+ self.logger.debug(f"Making batch request IPC. Path: {self.ipc_path}")
203
+ request_data = self.encode_batch_rpc_request(requests)
204
+ response = cast(List[RPCResponse], self._make_request(request_data))
205
+ return sort_batch_response_by_response_ids(response)
206
+
192
207
 
193
208
  # A valid JSON RPC response can only end in } or ] http://www.jsonrpc.org/specification
194
209
  def has_valid_json_rpc_ending(raw_response: bytes) -> bool:
@@ -10,9 +10,12 @@ from types import (
10
10
  )
11
11
  from typing import (
12
12
  Any,
13
+ List,
13
14
  Optional,
15
+ Tuple,
14
16
  Type,
15
17
  Union,
18
+ cast,
16
19
  )
17
20
 
18
21
  from eth_typing import (
@@ -25,6 +28,12 @@ from websockets.legacy.client import (
25
28
  WebSocketClientProtocol,
26
29
  )
27
30
 
31
+ from web3._utils.batching import (
32
+ sort_batch_response_by_response_ids,
33
+ )
34
+ from web3._utils.caching import (
35
+ handle_request_caching,
36
+ )
28
37
  from web3.exceptions import (
29
38
  Web3ValidationError,
30
39
  )
@@ -91,6 +100,7 @@ class LegacyWebSocketProvider(JSONBaseProvider):
91
100
  endpoint_uri: Optional[Union[URI, str]] = None,
92
101
  websocket_kwargs: Optional[Any] = None,
93
102
  websocket_timeout: int = DEFAULT_WEBSOCKET_TIMEOUT,
103
+ **kwargs: Any,
94
104
  ) -> None:
95
105
  self.endpoint_uri = URI(endpoint_uri)
96
106
  self.websocket_timeout = websocket_timeout
@@ -110,7 +120,7 @@ class LegacyWebSocketProvider(JSONBaseProvider):
110
120
  f"in websocket_kwargs, found: {found_restricted_keys}"
111
121
  )
112
122
  self.conn = PersistentWebSocket(self.endpoint_uri, websocket_kwargs)
113
- super().__init__()
123
+ super().__init__(**kwargs)
114
124
 
115
125
  def __str__(self) -> str:
116
126
  return f"WS connection {self.endpoint_uri}"
@@ -124,6 +134,7 @@ class LegacyWebSocketProvider(JSONBaseProvider):
124
134
  await asyncio.wait_for(conn.recv(), timeout=self.websocket_timeout)
125
135
  )
126
136
 
137
+ @handle_request_caching
127
138
  def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
128
139
  self.logger.debug(
129
140
  f"Making request WebSocket. URI: {self.endpoint_uri}, " f"Method: {method}"
@@ -133,3 +144,17 @@ class LegacyWebSocketProvider(JSONBaseProvider):
133
144
  self.coro_make_request(request_data), LegacyWebSocketProvider._loop
134
145
  )
135
146
  return future.result()
147
+
148
+ def make_batch_request(
149
+ self, requests: List[Tuple[RPCEndpoint, Any]]
150
+ ) -> List[RPCResponse]:
151
+ self.logger.debug(
152
+ f"Making batch request WebSocket. URI: {self.endpoint_uri}, "
153
+ f"Methods: {requests}"
154
+ )
155
+ request_data = self.encode_batch_rpc_request(requests)
156
+ future = asyncio.run_coroutine_threadsafe(
157
+ self.coro_make_request(request_data), LegacyWebSocketProvider._loop
158
+ )
159
+ response = cast(List[RPCResponse], future.result())
160
+ return sort_batch_response_by_response_ids(response)
@@ -11,9 +11,11 @@ from pathlib import (
11
11
  import sys
12
12
  from typing import (
13
13
  Any,
14
+ List,
14
15
  Optional,
15
16
  Tuple,
16
17
  Union,
18
+ cast,
17
19
  )
18
20
 
19
21
  from eth_utils import (
@@ -28,6 +30,10 @@ from web3.types import (
28
30
  from . import (
29
31
  PersistentConnectionProvider,
30
32
  )
33
+ from ..._utils.batching import (
34
+ BATCH_REQUEST_ID,
35
+ sort_batch_response_by_response_ids,
36
+ )
31
37
  from ..._utils.caching import (
32
38
  async_handle_request_caching,
33
39
  )
@@ -59,11 +65,12 @@ class AsyncIPCProvider(PersistentConnectionProvider):
59
65
 
60
66
  _reader: Optional[asyncio.StreamReader] = None
61
67
  _writer: Optional[asyncio.StreamWriter] = None
68
+ _decoder: json.JSONDecoder = json.JSONDecoder()
69
+ _raw_message: str = ""
62
70
 
63
71
  def __init__(
64
72
  self,
65
73
  ipc_path: Optional[Union[str, Path]] = None,
66
- max_connection_retries: int = 5,
67
74
  # `PersistentConnectionProvider` kwargs can be passed through
68
75
  **kwargs: Any,
69
76
  ) -> None:
@@ -74,7 +81,6 @@ class AsyncIPCProvider(PersistentConnectionProvider):
74
81
  else:
75
82
  raise Web3TypeError("ipc_path must be of type string or pathlib.Path")
76
83
 
77
- self._max_connection_retries = max_connection_retries
78
84
  super().__init__(**kwargs)
79
85
 
80
86
  def __str__(self) -> str:
@@ -99,48 +105,16 @@ class AsyncIPCProvider(PersistentConnectionProvider):
99
105
  )
100
106
  return False
101
107
 
102
- async def connect(self) -> None:
103
- _connection_attempts = 0
104
- _backoff_rate_change = 1.75
105
- _backoff_time = 1.75
106
-
107
- while _connection_attempts != self._max_connection_retries:
108
- try:
109
- _connection_attempts += 1
110
- self._reader, self._writer = await async_get_ipc_socket(self.ipc_path)
111
- self._message_listener_task = asyncio.create_task(
112
- self._message_listener()
113
- )
114
- break
115
- except OSError as e:
116
- if _connection_attempts == self._max_connection_retries:
117
- raise ProviderConnectionError(
118
- f"Could not connect to: {self.ipc_path}. "
119
- f"Retries exceeded max of {self._max_connection_retries}."
120
- ) from e
121
- self.logger.info(
122
- f"Could not connect to: {self.ipc_path}. Retrying in "
123
- f"{round(_backoff_time, 1)} seconds.",
124
- exc_info=True,
125
- )
126
- await asyncio.sleep(_backoff_time)
127
- _backoff_time *= _backoff_rate_change
108
+ async def _provider_specific_connect(self) -> None:
109
+ self._reader, self._writer = await async_get_ipc_socket(self.ipc_path)
128
110
 
129
- async def disconnect(self) -> None:
111
+ async def _provider_specific_disconnect(self) -> None:
130
112
  if self._writer and not self._writer.is_closing():
131
113
  self._writer.close()
132
114
  await self._writer.wait_closed()
133
115
  self._writer = None
134
- self.logger.debug(f'Successfully disconnected from : "{self.ipc_path}')
135
-
136
- try:
137
- self._message_listener_task.cancel()
138
- await self._message_listener_task
116
+ if self._reader:
139
117
  self._reader = None
140
- except (asyncio.CancelledError, StopAsyncIteration):
141
- pass
142
-
143
- self._request_processor.clear_caches()
144
118
 
145
119
  async def _reset_socket(self) -> None:
146
120
  self._writer.close()
@@ -149,13 +123,12 @@ class AsyncIPCProvider(PersistentConnectionProvider):
149
123
 
150
124
  @async_handle_request_caching
151
125
  async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
152
- request_data = self.encode_rpc_request(method, params)
153
-
154
126
  if self._writer is None:
155
127
  raise ProviderConnectionError(
156
128
  "Connection to ipc socket has not been initiated for the provider."
157
129
  )
158
130
 
131
+ request_data = self.encode_rpc_request(method, params)
159
132
  try:
160
133
  self._writer.write(request_data)
161
134
  await self._writer.drain()
@@ -172,43 +145,54 @@ class AsyncIPCProvider(PersistentConnectionProvider):
172
145
 
173
146
  return response
174
147
 
175
- async def _message_listener(self) -> None:
176
- self.logger.info(
177
- "IPC socket listener background task started. Storing all messages in "
178
- "appropriate request processor queues / caches to be processed."
148
+ async def make_batch_request(
149
+ self, requests: List[Tuple[RPCEndpoint, Any]]
150
+ ) -> List[RPCResponse]:
151
+ if self._writer is None:
152
+ raise ProviderConnectionError(
153
+ "Connection to ipc socket has not been initiated for the provider."
154
+ )
155
+
156
+ request_data = self.encode_batch_rpc_request(requests)
157
+ try:
158
+ self._writer.write(request_data)
159
+ await self._writer.drain()
160
+ except OSError as e:
161
+ # Broken pipe
162
+ if e.errno == errno.EPIPE:
163
+ # one extra attempt, then give up
164
+ await self._reset_socket()
165
+ self._writer.write(request_data)
166
+ await self._writer.drain()
167
+
168
+ response = cast(
169
+ List[RPCResponse], await self._get_response_for_request_id(BATCH_REQUEST_ID)
179
170
  )
180
- raw_message = ""
181
- decoder = json.JSONDecoder()
171
+ return response
182
172
 
183
- while True:
184
- # the use of sleep(0) seems to be the most efficient way to yield control
185
- # back to the event loop to share the loop with other tasks.
186
- await asyncio.sleep(0)
173
+ async def _provider_specific_message_listener(self) -> None:
174
+ self._raw_message += to_text(await self._reader.read(4096)).lstrip()
187
175
 
176
+ while self._raw_message:
188
177
  try:
189
- raw_message += to_text(await self._reader.read(4096)).lstrip()
190
-
191
- while raw_message:
192
- try:
193
- response, pos = decoder.raw_decode(raw_message)
194
- except JSONDecodeError:
195
- break
196
-
197
- is_subscription = response.get("method") == "eth_subscription"
198
- await self._request_processor.cache_raw_response(
199
- response, subscription=is_subscription
200
- )
201
- raw_message = raw_message[pos:].lstrip()
202
- except Exception as e:
203
- if not self.silence_listener_task_exceptions:
204
- loop = asyncio.get_event_loop()
205
- for task in asyncio.all_tasks(loop=loop):
206
- task.cancel()
207
- raise e
208
-
209
- self.logger.error(
210
- "Exception caught in listener, error logging and keeping listener "
211
- f"background task alive.\n error={e}"
212
- )
213
- # if only error logging, reset the ``raw_message`` buffer and continue
214
- raw_message = ""
178
+ response, pos = self._decoder.raw_decode(self._raw_message)
179
+ except JSONDecodeError:
180
+ break
181
+
182
+ if isinstance(response, list):
183
+ response = sort_batch_response_by_response_ids(response)
184
+
185
+ is_subscription = (
186
+ response.get("method") == "eth_subscription"
187
+ if not isinstance(response, list)
188
+ else False
189
+ )
190
+ await self._request_processor.cache_raw_response(
191
+ response, subscription=is_subscription
192
+ )
193
+ self._raw_message = self._raw_message[pos:].lstrip()
194
+
195
+ def _error_log_listener_task_exception(self, e: Exception) -> None:
196
+ super()._error_log_listener_task_exception(e)
197
+ # reset the raw message buffer on exception when error logging
198
+ self._raw_message = ""