web3 7.11.1__py3-none-any.whl → 7.12.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.
@@ -37,6 +37,7 @@ from eth_utils import (
37
37
  )
38
38
  from eth_utils.toolz import (
39
39
  assoc,
40
+ merge,
40
41
  )
41
42
  from hexbytes import (
42
43
  HexBytes,
@@ -717,7 +718,7 @@ class AsyncEthModuleTest:
717
718
  async_w3.middleware_onion.remove("signing")
718
719
 
719
720
  @pytest.mark.asyncio
720
- async def test_async_sign_authorization_and_send_raw_set_code_transaction(
721
+ async def test_async_sign_authorization_send_raw_and_send_set_code_transactions(
721
722
  self,
722
723
  async_w3: "AsyncWeb3",
723
724
  keyfile_account_pkey: HexStr,
@@ -752,6 +753,7 @@ class AsyncEthModuleTest:
752
753
  "authorizationList": [signed_auth],
753
754
  }
754
755
 
756
+ # test eth_sendRawTransaction
755
757
  signed = keyfile_account.sign_transaction(txn)
756
758
  tx_hash = await async_w3.eth.send_raw_transaction(signed.raw_transaction)
757
759
  get_tx = await async_w3.eth.get_transaction(tx_hash)
@@ -790,14 +792,17 @@ class AsyncEthModuleTest:
790
792
  "nonce": nonce + 3,
791
793
  }
792
794
  signed_reset_auth = keyfile_account.sign_authorization(reset_auth)
793
- new_txn = dict(txn)
794
- new_txn["authorizationList"] = [signed_reset_auth]
795
- new_txn["nonce"] = nonce + 2
796
-
797
- signed_reset = keyfile_account.sign_transaction(new_txn)
798
- reset_tx_hash = await async_w3.eth.send_raw_transaction(
799
- signed_reset.raw_transaction
795
+ reset_code_txn = merge(
796
+ txn,
797
+ {
798
+ "from": keyfile_account.address,
799
+ "authorizationList": [signed_reset_auth],
800
+ "nonce": nonce + 2,
801
+ },
800
802
  )
803
+
804
+ # test eth_sendTransaction
805
+ reset_tx_hash = await async_w3.eth.send_transaction(reset_code_txn)
801
806
  reset_tx_receipt = await async_w3.eth.wait_for_transaction_receipt(
802
807
  reset_tx_hash, timeout=10
803
808
  )
@@ -3867,7 +3872,7 @@ class EthModuleTest:
3867
3872
  # cleanup
3868
3873
  w3.middleware_onion.remove("signing")
3869
3874
 
3870
- def test_sign_authorization_and_send_raw_set_code_transaction(
3875
+ def test_sign_authorization_send_raw_and_send_set_code_transactions(
3871
3876
  self, w3: "Web3", keyfile_account_pkey: HexStr, math_contract: "Contract"
3872
3877
  ) -> None:
3873
3878
  # TODO: remove blockNumber block_id from eth_call and eth_getCode calls once
@@ -3899,6 +3904,7 @@ class EthModuleTest:
3899
3904
  "authorizationList": [signed_auth],
3900
3905
  }
3901
3906
 
3907
+ # test eth_sendRawTransaction
3902
3908
  signed = keyfile_account.sign_transaction(txn)
3903
3909
  tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
3904
3910
  get_tx = w3.eth.get_transaction(tx_hash)
@@ -3939,12 +3945,17 @@ class EthModuleTest:
3939
3945
  "nonce": nonce + 3,
3940
3946
  }
3941
3947
  signed_reset_auth = keyfile_account.sign_authorization(reset_auth)
3942
- new_txn = dict(txn)
3943
- new_txn["authorizationList"] = [signed_reset_auth]
3944
- new_txn["nonce"] = nonce + 2
3948
+ reset_code_txn = merge(
3949
+ txn,
3950
+ {
3951
+ "from": keyfile_account.address,
3952
+ "authorizationList": [signed_reset_auth],
3953
+ "nonce": nonce + 2,
3954
+ },
3955
+ )
3945
3956
 
3946
- signed_reset = keyfile_account.sign_transaction(new_txn)
3947
- reset_tx_hash = w3.eth.send_raw_transaction(signed_reset.raw_transaction)
3957
+ # test eth_sendTransaction
3958
+ reset_tx_hash = w3.eth.send_transaction(reset_code_txn)
3948
3959
  reset_tx_receipt = w3.eth.wait_for_transaction_receipt(
3949
3960
  reset_tx_hash, timeout=10
3950
3961
  )
@@ -1,4 +1,7 @@
1
1
  import pytest
2
+ import asyncio
3
+ import threading
4
+ import time
2
5
  from typing import (
3
6
  TYPE_CHECKING,
4
7
  Any,
@@ -43,6 +46,9 @@ if TYPE_CHECKING:
43
46
  )
44
47
 
45
48
 
49
+ SOME_BLOCK_KEYS = {"number", "hash", "parentHash", "stateRoot", "transactions"}
50
+
51
+
46
52
  class Web3ModuleTest:
47
53
  def test_web3_client_version(self, w3: Web3) -> None:
48
54
  client_version = w3.client_version
@@ -336,7 +342,7 @@ class Web3ModuleTest:
336
342
 
337
343
  # assert proper batch cleanup after execution
338
344
  assert batch._requests_info == []
339
- assert not batch._provider._is_batching
345
+ assert not w3.provider._is_batching
340
346
 
341
347
  # assert batch cannot be added to after execution
342
348
  with pytest.raises(
@@ -395,7 +401,7 @@ class Web3ModuleTest:
395
401
 
396
402
  # assert proper batch cleanup after execution
397
403
  assert batch._requests_info == []
398
- assert not batch._provider._is_batching
404
+ assert not w3.provider._is_batching
399
405
 
400
406
  # assert batch cannot be added to after execution
401
407
  with pytest.raises(
@@ -513,6 +519,43 @@ class Web3ModuleTest:
513
519
  batch.add(w3.eth.sign(Address(b"\x00" * 20)))
514
520
  batch.execute()
515
521
 
522
+ def test_batch_requests_concurrently_with_regular_requests(
523
+ self, w3: "Web3"
524
+ ) -> None:
525
+ num_requests = 40
526
+ responses = []
527
+ batch_response = []
528
+
529
+ def make_regular_requests() -> None:
530
+ for _ in range(num_requests):
531
+ responses.append(w3.eth.get_block(0))
532
+ time.sleep(0.01)
533
+
534
+ def make_batch_request() -> None:
535
+ with w3.batch_requests() as batch:
536
+ for _ in range(num_requests):
537
+ batch.add(w3.eth.get_block(0))
538
+ time.sleep(0.01)
539
+ batch_response.extend(batch.execute())
540
+
541
+ # split into threads
542
+ regular_thread = threading.Thread(target=make_regular_requests)
543
+ batch_thread = threading.Thread(target=make_batch_request)
544
+
545
+ regular_thread.start()
546
+ batch_thread.start()
547
+
548
+ # wait for threads to finish
549
+ regular_thread.join()
550
+ batch_thread.join()
551
+ assert not regular_thread.is_alive()
552
+ assert not batch_thread.is_alive()
553
+
554
+ assert len(responses) == num_requests
555
+ assert len(batch_response) == num_requests
556
+ assert all(SOME_BLOCK_KEYS.issubset(response.keys()) for response in responses)
557
+ assert set(responses) == set(batch_response)
558
+
516
559
 
517
560
  # -- async -- #
518
561
 
@@ -551,7 +594,7 @@ class AsyncWeb3ModuleTest(Web3ModuleTest):
551
594
 
552
595
  # assert proper batch cleanup after execution
553
596
  assert batch._async_requests_info == []
554
- assert not batch._provider._is_batching
597
+ assert not async_w3.provider._is_batching
555
598
 
556
599
  # assert batch cannot be added to after execution
557
600
  with pytest.raises(
@@ -614,7 +657,7 @@ class AsyncWeb3ModuleTest(Web3ModuleTest):
614
657
 
615
658
  # assert proper batch cleanup after execution
616
659
  assert batch._async_requests_info == []
617
- assert not batch._provider._is_batching
660
+ assert not async_w3.provider._is_batching
618
661
 
619
662
  # assert batch cannot be added to after execution
620
663
  with pytest.raises(
@@ -734,3 +777,34 @@ class AsyncWeb3ModuleTest(Web3ModuleTest):
734
777
  with pytest.raises(MethodNotSupported, match="eth_sign"):
735
778
  batch.add(async_w3.eth.sign(Address(b"\x00" * 20)))
736
779
  await batch.async_execute()
780
+
781
+ @pytest.mark.asyncio
782
+ async def test_batch_requests_concurrently_with_regular_requests( # type: ignore[override] # noqa: E501
783
+ self, async_w3: AsyncWeb3 # type: ignore[override]
784
+ ) -> None:
785
+ responses = []
786
+ batch_response = []
787
+
788
+ num_blocks = await async_w3.eth.block_number
789
+
790
+ async def make_regular_requests() -> None:
791
+ for i in range(num_blocks):
792
+ responses.append(await async_w3.eth.get_block(i))
793
+ await asyncio.sleep(0.01)
794
+
795
+ async def make_batch_request() -> None:
796
+ async with async_w3.batch_requests() as batch:
797
+ for i in range(num_blocks):
798
+ batch.add(async_w3.eth.get_block(i))
799
+ await asyncio.sleep(0.01)
800
+ batch_response.extend(await batch.async_execute())
801
+
802
+ await asyncio.gather(
803
+ make_regular_requests(),
804
+ make_batch_request(),
805
+ )
806
+
807
+ assert len(responses) == num_blocks
808
+ assert len(batch_response) == num_blocks
809
+ assert all(SOME_BLOCK_KEYS.issubset(response.keys()) for response in responses)
810
+ assert set(responses) == set(batch_response)
web3/_utils/validation.py CHANGED
@@ -396,7 +396,7 @@ def validate_rpc_response_and_raise_if_error(
396
396
 
397
397
  response = apply_error_formatters(error_formatters, response)
398
398
  if logger is not None:
399
- logger.debug(f"RPC error response: {response}")
399
+ logger.debug("RPC error response: %s", response)
400
400
 
401
401
  raise web3_rpc_error
402
402
 
web3/contract/utils.py CHANGED
@@ -43,8 +43,8 @@ from web3._utils.abi import (
43
43
  from web3._utils.async_transactions import (
44
44
  async_fill_transaction_defaults,
45
45
  )
46
- from web3._utils.compat import (
47
- TypeAlias,
46
+ from web3._utils.batching import (
47
+ BatchRequestInformation,
48
48
  )
49
49
  from web3._utils.contracts import (
50
50
  prepare_transaction,
@@ -62,7 +62,6 @@ from web3.exceptions import (
62
62
  from web3.types import (
63
63
  ABIElementIdentifier,
64
64
  BlockIdentifier,
65
- RPCEndpoint,
66
65
  StateOverride,
67
66
  TContractEvent,
68
67
  TContractFn,
@@ -175,10 +174,8 @@ def call_contract_function(
175
174
  if abi_callable["type"] == "function":
176
175
  output_types = get_abi_output_types(abi_callable)
177
176
 
178
- provider = w3.provider
179
- if hasattr(provider, "_is_batching") and provider._is_batching:
180
- BatchingReturnData: TypeAlias = Tuple[Tuple[RPCEndpoint, Any], Tuple[Any, ...]]
181
- request_information = tuple(cast(BatchingReturnData, return_data))
177
+ if w3.provider._is_batching:
178
+ request_information = tuple(cast(BatchRequestInformation, return_data))
182
179
  method_and_params = request_information[0]
183
180
 
184
181
  # append return data formatting to result formatters
@@ -483,35 +480,23 @@ async def async_call_contract_function(
483
480
  normalizers,
484
481
  output_types,
485
482
  )
486
- if async_w3.provider.has_persistent_connection:
487
- # get the current request id
488
- provider = cast("PersistentConnectionProvider", async_w3.provider)
489
- current_request_id = provider._batch_request_counter - 1
490
- provider._request_processor.append_result_formatter_for_request(
491
- current_request_id, contract_call_return_data_formatter
492
- )
493
- else:
494
- BatchingReturnData: TypeAlias = Tuple[
495
- Tuple[RPCEndpoint, Any], Tuple[Any, ...]
496
- ]
497
- request_information = tuple(cast(BatchingReturnData, return_data))
498
- method_and_params = request_information[0]
499
-
500
- # append return data formatter to result formatters
501
- current_response_formatters = request_information[1]
502
- current_result_formatters = current_response_formatters[0]
503
- updated_result_formatters = compose(
504
- contract_call_return_data_formatter,
505
- current_result_formatters,
506
- )
507
- response_formatters = (
508
- updated_result_formatters, # result formatters
509
- current_response_formatters[1], # error formatters
510
- current_response_formatters[2], # null result formatters
511
- )
512
- return (method_and_params, response_formatters)
513
483
 
514
- return return_data
484
+ request_information = tuple(cast(BatchRequestInformation, return_data))
485
+ method_and_params = request_information[0]
486
+
487
+ # append return data formatter to result formatters
488
+ current_response_formatters = request_information[1]
489
+ current_result_formatters = current_response_formatters[0]
490
+ updated_result_formatters = compose(
491
+ contract_call_return_data_formatter,
492
+ current_result_formatters,
493
+ )
494
+ response_formatters = (
495
+ updated_result_formatters, # result formatters
496
+ current_response_formatters[1], # error formatters
497
+ current_response_formatters[2], # null result formatters
498
+ )
499
+ return (method_and_params, response_formatters)
515
500
 
516
501
  try:
517
502
  output_data = async_w3.codec.decode(output_types, return_data)
web3/manager.py CHANGED
@@ -159,7 +159,7 @@ class RequestManager:
159
159
  request_func = provider.request_func(
160
160
  cast("Web3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
161
161
  )
162
- self.logger.debug(f"Making request. Method: {method}")
162
+ self.logger.debug("Making request. Method: %s", method)
163
163
  return request_func(method, params)
164
164
 
165
165
  async def _coro_make_request(
@@ -169,7 +169,7 @@ class RequestManager:
169
169
  request_func = await provider.request_func(
170
170
  cast("AsyncWeb3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
171
171
  )
172
- self.logger.debug(f"Making request. Method: {method}")
172
+ self.logger.debug("Making request. Method: %s", method)
173
173
  return await request_func(method, params)
174
174
 
175
175
  #
@@ -261,7 +261,7 @@ class RequestManager:
261
261
  return RequestBatcher(self.w3)
262
262
 
263
263
  def _make_batch_request(
264
- self, requests_info: List[Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
264
+ self, requests_info: List[Tuple[Tuple["RPCEndpoint", Any], Tuple[Any, ...]]]
265
265
  ) -> List[RPCResponse]:
266
266
  """
267
267
  Make a batch request using the provider
@@ -291,7 +291,7 @@ class RequestManager:
291
291
  async def _async_make_batch_request(
292
292
  self,
293
293
  requests_info: List[
294
- Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
294
+ Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any], Tuple[Any]]]
295
295
  ],
296
296
  ) -> List[RPCResponse]:
297
297
  """
@@ -315,13 +315,6 @@ class RequestManager:
315
315
  if isinstance(response, list):
316
316
  # expected format
317
317
  response = cast(List[RPCResponse], response)
318
- if isinstance(self.provider, PersistentConnectionProvider):
319
- # call _process_response for each response in the batch
320
- return [
321
- cast(RPCResponse, await self._process_response(resp))
322
- for resp in response
323
- ]
324
-
325
318
  formatted_responses = [
326
319
  self._format_batched_response(info, resp)
327
320
  for info, resp in zip(unpacked_requests_info, response)
@@ -331,6 +324,86 @@ class RequestManager:
331
324
  # expect a single response with an error
332
325
  raise_error_for_batch_response(response, self.logger)
333
326
 
327
+ async def _async_send_batch(
328
+ self, requests: List[Tuple["RPCEndpoint", Any]]
329
+ ) -> List[RPCRequest]:
330
+ """
331
+ Send a batch request via socket.
332
+ """
333
+ if not isinstance(self._provider, PersistentConnectionProvider):
334
+ raise Web3TypeError(
335
+ "Only providers that maintain an open, persistent connection "
336
+ "can send batch requests."
337
+ )
338
+ send_func = await self._provider.send_batch_func(
339
+ cast("AsyncWeb3", self.w3),
340
+ cast("MiddlewareOnion", self.middleware_onion),
341
+ )
342
+ self.logger.debug(
343
+ "Sending batch request to open socket connection: %s",
344
+ self._provider.get_endpoint_uri_or_ipc_path(),
345
+ )
346
+ return await send_func(requests)
347
+
348
+ async def _async_recv_batch(self, requests: List[RPCRequest]) -> List[RPCResponse]:
349
+ """
350
+ Receive a batch request via socket.
351
+ """
352
+ if not isinstance(self._provider, PersistentConnectionProvider):
353
+ raise Web3TypeError(
354
+ "Only providers that maintain an open, persistent connection "
355
+ "can receive batch requests."
356
+ )
357
+ recv_func = await self._provider.recv_batch_func(
358
+ cast("AsyncWeb3", self.w3),
359
+ cast("MiddlewareOnion", self.middleware_onion),
360
+ )
361
+ self.logger.debug(
362
+ "Receiving batch request from open socket connection: %s",
363
+ self._provider.get_endpoint_uri_or_ipc_path(),
364
+ )
365
+ return await recv_func(requests)
366
+
367
+ async def _async_make_socket_batch_request(
368
+ self,
369
+ requests_info: List[
370
+ Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any], Tuple[Any, ...]]]
371
+ ],
372
+ ) -> List[RPCResponse]:
373
+ """
374
+ Send and receive a batch request via a socket.
375
+ """
376
+ if not isinstance(self._provider, PersistentConnectionProvider):
377
+ raise Web3TypeError(
378
+ "Only providers that maintain an open, persistent connection "
379
+ "can send and receive batch requests."
380
+ )
381
+
382
+ unpacked_requests_info = await asyncio.gather(*requests_info)
383
+ reqs = [req for req, _ in unpacked_requests_info]
384
+ response_formatters = [resp_f for _, resp_f in unpacked_requests_info]
385
+
386
+ requests = await self._async_send_batch(reqs)
387
+
388
+ for i, request in enumerate(requests):
389
+ self._provider._request_processor.cache_request_information(
390
+ request["id"],
391
+ request["method"],
392
+ request["params"],
393
+ response_formatters=response_formatters[i],
394
+ )
395
+
396
+ responses = await self._async_recv_batch(requests)
397
+ if isinstance(responses, list):
398
+ # expected format
399
+ return [
400
+ cast(RPCResponse, await self._process_response(resp))
401
+ for resp in responses
402
+ ]
403
+ else:
404
+ # expect a single response with an error
405
+ raise_error_for_batch_response(responses, self.logger)
406
+
334
407
  def _format_batched_response(
335
408
  self,
336
409
  requests_info: Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]],
@@ -366,9 +439,12 @@ class RequestManager:
366
439
  ) -> RPCResponse:
367
440
  provider = cast(PersistentConnectionProvider, self._provider)
368
441
  self.logger.debug(
369
- "Making request to open socket connection and waiting for response: "
370
- f"{provider.get_endpoint_uri_or_ipc_path()},\n method: {method},\n"
371
- f" params: {params}"
442
+ "Making request to open socket connection and waiting for response: %s,\n"
443
+ " method: %s,\n"
444
+ " params: %s",
445
+ provider.get_endpoint_uri_or_ipc_path(),
446
+ method,
447
+ params,
372
448
  )
373
449
  rpc_request = await self.send(method, params)
374
450
  provider._request_processor.cache_request_information(
@@ -388,9 +464,12 @@ class RequestManager:
388
464
  middleware_onion,
389
465
  )
390
466
  self.logger.debug(
391
- "Sending request to open socket connection: "
392
- f"{provider.get_endpoint_uri_or_ipc_path()},\n method: {method},\n"
393
- f" params: {params}"
467
+ "Sending request to open socket connection: %s,\n"
468
+ " method: %s,\n"
469
+ " params: %s",
470
+ provider.get_endpoint_uri_or_ipc_path(),
471
+ method,
472
+ params,
394
473
  )
395
474
  return await send_func(method, params)
396
475
 
@@ -404,7 +483,8 @@ class RequestManager:
404
483
  )
405
484
  self.logger.debug(
406
485
  "Getting response for request from open socket connection:\n"
407
- f" request: {rpc_request}"
486
+ " request: %s",
487
+ rpc_request,
408
488
  )
409
489
  response = await recv_func(rpc_request)
410
490
  try:
@@ -417,8 +497,8 @@ class RequestManager:
417
497
  async def recv(self) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
418
498
  provider = cast(PersistentConnectionProvider, self._provider)
419
499
  self.logger.debug(
420
- "Getting next response from open socket connection: "
421
- f"{provider.get_endpoint_uri_or_ipc_path()}"
500
+ "Getting next response from open socket connection: %s",
501
+ provider.get_endpoint_uri_or_ipc_path(),
422
502
  )
423
503
  # pop from the queue since the listener task is responsible for reading
424
504
  # directly from the socket
@@ -501,9 +581,11 @@ class RequestManager:
501
581
  # subscription as it comes in
502
582
  request_info.subscription_id = subscription_id
503
583
  provider.logger.debug(
504
- "Caching eth_subscription info:\n "
505
- f"cache_key={cache_key},\n "
506
- f"request_info={request_info.__dict__}"
584
+ "Caching eth_subscription info:\n"
585
+ " cache_key=%s,\n"
586
+ " request_info=%s",
587
+ cache_key,
588
+ request_info.__dict__,
507
589
  )
508
590
  self._request_processor._request_information_cache.cache(
509
591
  cache_key, request_info
web3/method.py CHANGED
@@ -165,8 +165,7 @@ class Method(Generic[TFunc]):
165
165
  "usually attached to a web3 instance."
166
166
  )
167
167
 
168
- provider = module.w3.provider
169
- if hasattr(provider, "_is_batching") and provider._is_batching:
168
+ if module.w3.provider._is_batching:
170
169
  if self.json_rpc_method in RPC_METHODS_UNSUPPORTED_DURING_BATCH:
171
170
  raise MethodNotSupported(
172
171
  f"Method `{self.json_rpc_method}` is not supported within a batch "
@@ -182,12 +181,13 @@ class Method(Generic[TFunc]):
182
181
  @property
183
182
  def method_selector_fn(
184
183
  self,
185
- ) -> Callable[..., Union[RPCEndpoint, Callable[..., RPCEndpoint]]]:
184
+ ) -> Callable[[], RPCEndpoint]:
186
185
  """Gets the method selector from the config."""
187
- if callable(self.json_rpc_method):
188
- return self.json_rpc_method
189
- elif isinstance(self.json_rpc_method, (str,)):
190
- return lambda *_: self.json_rpc_method
186
+ method = self.json_rpc_method
187
+ if callable(method):
188
+ return method
189
+ elif isinstance(method, str):
190
+ return lambda: method
191
191
  raise Web3ValueError(
192
192
  "``json_rpc_method`` config invalid. May be a string or function"
193
193
  )
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import contextvars
2
3
  import itertools
3
4
  import logging
4
5
  from typing import (
@@ -61,6 +62,9 @@ if TYPE_CHECKING:
61
62
  AsyncWeb3,
62
63
  WebSocketProvider,
63
64
  )
65
+ from web3._utils.batching import ( # noqa: F401
66
+ RequestBatcher,
67
+ )
64
68
  from web3.providers.persistent import ( # noqa: F401
65
69
  RequestProcessor,
66
70
  )
@@ -94,12 +98,19 @@ class AsyncBaseProvider:
94
98
  self.cache_allowed_requests = cache_allowed_requests
95
99
  self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
96
100
  self.request_cache_validation_threshold = request_cache_validation_threshold
97
- self._is_batching: bool = False
101
+
102
+ self._batching_context: contextvars.ContextVar[
103
+ Optional["RequestBatcher[Any]"]
104
+ ] = contextvars.ContextVar("batching_context", default=None)
98
105
  self._batch_request_func_cache: Tuple[
99
106
  Tuple[Middleware, ...],
100
107
  Callable[..., Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]]],
101
108
  ] = (None, None)
102
109
 
110
+ @property
111
+ def _is_batching(self) -> bool:
112
+ return self._batching_context.get() is not None
113
+
103
114
  async def request_func(
104
115
  self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
105
116
  ) -> Callable[..., Coroutine[Any, Any, RPCResponse]]:
@@ -240,3 +251,6 @@ class AsyncJSONBaseProvider(AsyncBaseProvider):
240
251
  )
241
252
  + b"]"
242
253
  )
254
+
255
+ def encode_batch_request_dicts(self, request_dicts: List[RPCRequest]) -> bytes:
256
+ return b"[" + b",".join(self.encode_rpc_dict(d) for d in request_dicts) + b"]"
web3/providers/base.py CHANGED
@@ -1,3 +1,4 @@
1
+ import contextvars
1
2
  import itertools
2
3
  import logging
3
4
  import threading
@@ -50,6 +51,9 @@ from web3.utils import (
50
51
 
51
52
  if TYPE_CHECKING:
52
53
  from web3 import Web3 # noqa: F401
54
+ from web3._utils.batching import (
55
+ RequestBatcher,
56
+ )
53
57
 
54
58
 
55
59
  class BaseProvider:
@@ -81,6 +85,20 @@ class BaseProvider:
81
85
  self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
82
86
  self.request_cache_validation_threshold = request_cache_validation_threshold
83
87
 
88
+ self._batching_context: contextvars.ContextVar[
89
+ Optional["RequestBatcher[Any]"]
90
+ ] = contextvars.ContextVar("batching_context", default=None)
91
+ self._batch_request_func_cache: Tuple[
92
+ Tuple[Middleware, ...], Callable[..., Union[List[RPCResponse], RPCResponse]]
93
+ ] = (None, None)
94
+
95
+ @property
96
+ def _is_batching(self) -> bool:
97
+ """
98
+ Check if the provider is currently batching requests.
99
+ """
100
+ return self._batching_context.get() is not None
101
+
84
102
  def request_func(
85
103
  self, w3: "Web3", middleware_onion: MiddlewareOnion
86
104
  ) -> Callable[..., RPCResponse]:
@@ -120,11 +138,6 @@ class JSONBaseProvider(BaseProvider):
120
138
  super().__init__(**kwargs)
121
139
  self.request_counter = itertools.count()
122
140
 
123
- self._is_batching: bool = False
124
- self._batch_request_func_cache: Tuple[
125
- Tuple[Middleware, ...], Callable[..., Union[List[RPCResponse], RPCResponse]]
126
- ] = (None, None)
127
-
128
141
  def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes:
129
142
  rpc_dict = {
130
143
  "jsonrpc": "2.0",
web3/providers/ipc.py CHANGED
@@ -30,7 +30,6 @@ from web3.types import (
30
30
  )
31
31
 
32
32
  from .._utils.batching import (
33
- batching_context,
34
33
  sort_batch_response_by_response_ids,
35
34
  )
36
35
  from .._utils.caching import (
@@ -197,16 +196,15 @@ class IPCProvider(JSONBaseProvider):
197
196
  @handle_request_caching
198
197
  def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
199
198
  self.logger.debug(
200
- f"Making request IPC. Path: {self.ipc_path}, Method: {method}"
199
+ "Making request IPC. Path: %s, Method: %s", self.ipc_path, method
201
200
  )
202
201
  request = self.encode_rpc_request(method, params)
203
202
  return self._make_request(request)
204
203
 
205
- @batching_context
206
204
  def make_batch_request(
207
205
  self, requests: List[Tuple[RPCEndpoint, Any]]
208
206
  ) -> List[RPCResponse]:
209
- self.logger.debug(f"Making batch request IPC. Path: {self.ipc_path}")
207
+ self.logger.debug("Making batch request IPC. Path: %s", self.ipc_path)
210
208
  request_data = self.encode_batch_rpc_request(requests)
211
209
  response = cast(List[RPCResponse], self._make_request(request_data))
212
210
  return sort_batch_response_by_response_ids(response)