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.
web3/eth/eth.py CHANGED
@@ -30,6 +30,9 @@ from hexbytes import (
30
30
  from web3._utils.blocks import (
31
31
  select_method_for_block_identifier,
32
32
  )
33
+ from web3._utils.compat import (
34
+ Unpack,
35
+ )
33
36
  from web3._utils.fee_utils import (
34
37
  fee_history_priority_fee,
35
38
  )
@@ -596,10 +599,8 @@ class Eth(BaseEth):
596
599
  current_transaction = get_required_transaction(self.w3, transaction_hash)
597
600
  return replace_transaction(self.w3, current_transaction, new_transaction)
598
601
 
599
- # todo: Update Any to stricter kwarg checking with TxParams
600
- # https://github.com/python/mypy/issues/4441
601
602
  def modify_transaction(
602
- self, transaction_hash: _Hash32, **transaction_params: Any
603
+ self, transaction_hash: _Hash32, **transaction_params: Unpack[TxParams]
603
604
  ) -> HexBytes:
604
605
  assert_valid_transaction_params(cast(TxParams, transaction_params))
605
606
  current_transaction = get_required_transaction(self.w3, transaction_hash)
@@ -680,7 +681,7 @@ class Eth(BaseEth):
680
681
 
681
682
  @overload
682
683
  # type error: Overloaded function signatures 1 and 2 overlap with incompatible return types # noqa: E501
683
- def contract(self, address: None = None, **kwargs: Any) -> Type[Contract]: # type: ignore[misc] # noqa: E501
684
+ def contract(self, address: None = None, **kwargs: Any) -> Type[Contract]: # type: ignore[overload-overlap] # noqa: E501
684
685
  ...
685
686
 
686
687
  @overload
web3/exceptions.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import datetime
2
2
  import time
3
3
  from typing import (
4
+ TYPE_CHECKING,
4
5
  Any,
5
6
  Dict,
6
7
  Optional,
@@ -16,6 +17,9 @@ from web3.types import (
16
17
  RPCResponse,
17
18
  )
18
19
 
20
+ if TYPE_CHECKING:
21
+ import asyncio
22
+
19
23
 
20
24
  class Web3Exception(Exception):
21
25
  """
@@ -307,6 +311,22 @@ class BadResponseFormat(Web3Exception):
307
311
  """
308
312
 
309
313
 
314
+ class TaskNotRunning(Web3Exception):
315
+ """
316
+ Used to signal between asyncio contexts that a task that is being awaited
317
+ is not currently running.
318
+ """
319
+
320
+ def __init__(
321
+ self, task: "asyncio.Task[Any]", message: Optional[str] = None
322
+ ) -> None:
323
+ self.task = task
324
+ if message is None:
325
+ message = f"Task {task} is not running."
326
+ self.message = message
327
+ super().__init__(message)
328
+
329
+
310
330
  class Web3RPCError(Web3Exception):
311
331
  """
312
332
  Raised when a JSON-RPC response contains an error field.
@@ -110,11 +110,11 @@ def _aggregate_miner_data(
110
110
  # types ignored b/c mypy has trouble inferring gas_prices: Sequence[Wei]
111
111
  price_percentile = percentile(gas_prices, percentile=20) # type: ignore
112
112
  except InsufficientData:
113
- price_percentile = min(gas_prices) # type: ignore
113
+ price_percentile = min(gas_prices)
114
114
  yield MinerData(
115
115
  miner,
116
116
  len(set(block_hashes)),
117
- min(gas_prices), # type: ignore
117
+ min(gas_prices),
118
118
  price_percentile,
119
119
  )
120
120
 
web3/main.py CHANGED
@@ -34,6 +34,7 @@ from typing import (
34
34
  TYPE_CHECKING,
35
35
  Any,
36
36
  AsyncIterator,
37
+ Callable,
37
38
  Dict,
38
39
  Generator,
39
40
  List,
@@ -100,6 +101,9 @@ from web3.manager import (
100
101
  RequestManager as DefaultRequestManager,
101
102
  )
102
103
  from web3.middleware.base import MiddlewareOnion
104
+ from web3.method import (
105
+ Method,
106
+ )
103
107
  from web3.module import (
104
108
  Module,
105
109
  )
@@ -143,7 +147,9 @@ from web3.types import (
143
147
  )
144
148
 
145
149
  if TYPE_CHECKING:
150
+ from web3._utils.batching import RequestBatcher # noqa: F401
146
151
  from web3._utils.empty import Empty # noqa: F401
152
+ from web3.providers.persistent import PersistentConnectionProvider # noqa: F401
147
153
 
148
154
 
149
155
  def get_async_default_modules() -> Dict[str, Union[Type[Module], Sequence[Any]]]:
@@ -336,6 +342,13 @@ class BaseWeb3:
336
342
  def is_encodable(self, _type: TypeStr, value: Any) -> bool:
337
343
  return self.codec.is_encodable(_type, value)
338
344
 
345
+ # -- APIs for high-level requests -- #
346
+
347
+ def batch_requests(
348
+ self,
349
+ ) -> "RequestBatcher[Method[Callable[..., Any]]]":
350
+ return self.manager._batch_requests()
351
+
339
352
 
340
353
  class Web3(BaseWeb3):
341
354
  # mypy types
@@ -468,7 +481,7 @@ class AsyncWeb3(BaseWeb3):
468
481
  new_ens.w3 = self # set self object reference for ``AsyncENS.w3``
469
482
  self._ens = new_ens
470
483
 
471
- # -- persistent connection methods -- #
484
+ # -- persistent connection methods -- #
472
485
 
473
486
  @property
474
487
  @persistent_connection_provider_method()
@@ -511,12 +524,11 @@ class AsyncWeb3(BaseWeb3):
511
524
  "when instantiating via ``async for``."
512
525
  )
513
526
  async def __aiter__(self) -> AsyncIterator[Self]:
514
- if not await self.provider.is_connected():
515
- await self.provider.connect()
516
-
527
+ provider = self.provider
517
528
  while True:
518
- try:
519
- yield self
520
- except Exception:
521
- # provider should handle connection / reconnection
522
- continue
529
+ await provider.connect()
530
+ yield self
531
+ cast("PersistentConnectionProvider", provider).logger.error(
532
+ "Connection interrupted, attempting to reconnect..."
533
+ )
534
+ await provider.disconnect()
web3/manager.py CHANGED
@@ -1,9 +1,11 @@
1
+ import asyncio
1
2
  import logging
2
3
  from typing import (
3
4
  TYPE_CHECKING,
4
5
  Any,
5
6
  AsyncGenerator,
6
7
  Callable,
8
+ Coroutine,
7
9
  List,
8
10
  Optional,
9
11
  Sequence,
@@ -22,6 +24,9 @@ from websockets.exceptions import (
22
24
  ConnectionClosedOK,
23
25
  )
24
26
 
27
+ from web3._utils.batching import (
28
+ RequestBatcher,
29
+ )
25
30
  from web3._utils.caching import (
26
31
  generate_cache_key,
27
32
  )
@@ -35,9 +40,13 @@ from web3.exceptions import (
35
40
  BadResponseFormat,
36
41
  MethodUnavailable,
37
42
  ProviderConnectionError,
43
+ TaskNotRunning,
38
44
  Web3RPCError,
39
45
  Web3TypeError,
40
46
  )
47
+ from web3.method import (
48
+ Method,
49
+ )
41
50
  from web3.middleware import (
42
51
  AttributeDictMiddleware,
43
52
  BufferedGasEstimateMiddleware,
@@ -54,8 +63,12 @@ from web3.module import (
54
63
  )
55
64
  from web3.providers import (
56
65
  AutoProvider,
66
+ JSONBaseProvider,
57
67
  PersistentConnectionProvider,
58
68
  )
69
+ from web3.providers.async_base import (
70
+ AsyncJSONBaseProvider,
71
+ )
59
72
  from web3.types import (
60
73
  RPCEndpoint,
61
74
  RPCResponse,
@@ -375,6 +388,88 @@ class RequestManager:
375
388
  response, params, error_formatters, null_result_formatters
376
389
  )
377
390
 
391
+ # -- batch requests management -- #
392
+
393
+ def _batch_requests(self) -> RequestBatcher[Method[Callable[..., Any]]]:
394
+ """
395
+ Context manager for making batch requests
396
+ """
397
+ if not isinstance(self.provider, (AsyncJSONBaseProvider, JSONBaseProvider)):
398
+ raise Web3TypeError("Batch requests are not supported by this provider.")
399
+ return RequestBatcher(self.w3)
400
+
401
+ def _make_batch_request(
402
+ self, requests_info: List[Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
403
+ ) -> List[RPCResponse]:
404
+ """
405
+ Make a batch request using the provider
406
+ """
407
+ provider = cast(JSONBaseProvider, self.provider)
408
+ request_func = provider.batch_request_func(
409
+ cast("Web3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
410
+ )
411
+ responses = request_func(
412
+ [
413
+ (method, params)
414
+ for (method, params), _response_formatters in requests_info
415
+ ]
416
+ )
417
+ formatted_responses = [
418
+ self._format_batched_response(info, resp)
419
+ for info, resp in zip(requests_info, responses)
420
+ ]
421
+ return list(formatted_responses)
422
+
423
+ async def _async_make_batch_request(
424
+ self,
425
+ requests_info: List[
426
+ Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
427
+ ],
428
+ ) -> List[RPCResponse]:
429
+ """
430
+ Make an asynchronous batch request using the provider
431
+ """
432
+ provider = cast(AsyncJSONBaseProvider, self.provider)
433
+ request_func = await provider.batch_request_func(
434
+ cast("AsyncWeb3", self.w3),
435
+ cast("MiddlewareOnion", self.middleware_onion),
436
+ )
437
+ # since we add items to the batch without awaiting, we unpack the coroutines
438
+ # and await them all here
439
+ unpacked_requests_info = await asyncio.gather(*requests_info)
440
+ responses = await request_func(
441
+ [
442
+ (method, params)
443
+ for (method, params), _response_formatters in unpacked_requests_info
444
+ ]
445
+ )
446
+
447
+ if isinstance(self.provider, PersistentConnectionProvider):
448
+ # call _process_response for each response in the batch
449
+ return [await self._process_response(resp) for resp in responses]
450
+
451
+ formatted_responses = [
452
+ self._format_batched_response(info, resp)
453
+ for info, resp in zip(unpacked_requests_info, responses)
454
+ ]
455
+ return list(formatted_responses)
456
+
457
+ def _format_batched_response(
458
+ self,
459
+ requests_info: Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]],
460
+ response: RPCResponse,
461
+ ) -> RPCResponse:
462
+ result_formatters, error_formatters, null_result_formatters = requests_info[1]
463
+ return apply_result_formatters(
464
+ result_formatters,
465
+ self.formatted_response(
466
+ response,
467
+ requests_info[0][1],
468
+ error_formatters,
469
+ null_result_formatters,
470
+ ),
471
+ )
472
+
378
473
  # -- persistent connection -- #
379
474
 
380
475
  async def send(self, method: RPCEndpoint, params: Any) -> RPCResponse:
@@ -408,14 +503,24 @@ class RequestManager:
408
503
  )
409
504
 
410
505
  while True:
411
- response = await self._request_processor.pop_raw_response(subscription=True)
412
- if (
413
- response is not None
414
- and response.get("params", {}).get("subscription")
415
- in self._request_processor.active_subscriptions
416
- ):
417
- # if response is an active subscription response, process it
418
- yield await self._process_response(response)
506
+ try:
507
+ response = await self._request_processor.pop_raw_response(
508
+ subscription=True
509
+ )
510
+ if (
511
+ response is not None
512
+ and response.get("params", {}).get("subscription")
513
+ in self._request_processor.active_subscriptions
514
+ ):
515
+ # if response is an active subscription response, process it
516
+ yield await self._process_response(response)
517
+ except TaskNotRunning:
518
+ self._provider._handle_listener_task_exceptions()
519
+ self.logger.error(
520
+ "Message listener background task has stopped unexpectedly. "
521
+ "Stopping message stream."
522
+ )
523
+ raise StopAsyncIteration
419
524
 
420
525
  async def _process_response(self, response: RPCResponse) -> RPCResponse:
421
526
  provider = cast(PersistentConnectionProvider, self._provider)
web3/method.py CHANGED
@@ -10,7 +10,6 @@ from typing import (
10
10
  Sequence,
11
11
  Tuple,
12
12
  Type,
13
- TypeVar,
14
13
  Union,
15
14
  )
16
15
  import warnings
@@ -22,6 +21,9 @@ from eth_utils.toolz import (
22
21
  pipe,
23
22
  )
24
23
 
24
+ from web3._utils.batching import (
25
+ RPC_METHODS_UNSUPPORTED_DURING_BATCH,
26
+ )
25
27
  from web3._utils.method_formatters import (
26
28
  get_error_formatters,
27
29
  get_null_result_formatters,
@@ -32,17 +34,22 @@ from web3._utils.rpc_abi import (
32
34
  RPC,
33
35
  )
34
36
  from web3.exceptions import (
37
+ MethodNotSupported,
35
38
  Web3TypeError,
36
39
  Web3ValidationError,
37
40
  Web3ValueError,
38
41
  )
39
42
  from web3.types import (
40
43
  RPCEndpoint,
44
+ TFunc,
41
45
  TReturn,
42
46
  )
43
47
 
44
48
  if TYPE_CHECKING:
45
- from web3 import Web3 # noqa: F401
49
+ from web3 import ( # noqa: F401
50
+ PersistentConnectionProvider,
51
+ Web3,
52
+ )
46
53
  from web3.module import Module # noqa: F401
47
54
 
48
55
 
@@ -84,9 +91,6 @@ def default_root_munger(_module: "Module", *args: Any) -> List[Any]:
84
91
  return [*args]
85
92
 
86
93
 
87
- TFunc = TypeVar("TFunc", bound=Callable[..., Any])
88
-
89
-
90
94
  class Method(Generic[TFunc]):
91
95
  """
92
96
  Method object for web3 module methods
@@ -149,15 +153,31 @@ class Method(Generic[TFunc]):
149
153
  self.is_property = is_property
150
154
 
151
155
  def __get__(
152
- self, obj: Optional["Module"] = None, obj_type: Optional[Type["Module"]] = None
156
+ self,
157
+ module: Optional["Module"] = None,
158
+ _type: Optional[Type["Module"]] = None,
153
159
  ) -> TFunc:
154
- if obj is None:
160
+ self._module = module
161
+ if module is None:
155
162
  raise Web3TypeError(
156
163
  "Direct calls to methods are not supported. "
157
- "Methods must be called from an module instance, "
164
+ "Methods must be called from a module instance, "
158
165
  "usually attached to a web3 instance."
159
166
  )
160
- return obj.retrieve_caller_fn(self)
167
+
168
+ provider = module.w3.provider
169
+ if hasattr(provider, "_is_batching") and provider._is_batching:
170
+ if self.json_rpc_method in RPC_METHODS_UNSUPPORTED_DURING_BATCH:
171
+ raise MethodNotSupported(
172
+ f"Method `{self.json_rpc_method}` is not supported within a batch "
173
+ "request."
174
+ )
175
+ return module.retrieve_request_information(self)
176
+ else:
177
+ return module.retrieve_caller_fn(self)
178
+
179
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
180
+ return self.__get__(self._module)(*args, **kwargs)
161
181
 
162
182
  @property
163
183
  def method_selector_fn(
web3/middleware/base.py CHANGED
@@ -4,6 +4,8 @@ from abc import (
4
4
  from typing import (
5
5
  TYPE_CHECKING,
6
6
  Any,
7
+ List,
8
+ Tuple,
7
9
  Type,
8
10
  Union,
9
11
  )
@@ -18,7 +20,9 @@ if TYPE_CHECKING:
18
20
  Web3,
19
21
  )
20
22
  from web3.types import ( # noqa: F401
23
+ AsyncMakeBatchRequestFn,
21
24
  AsyncMakeRequestFn,
25
+ MakeBatchRequestFn,
22
26
  MakeRequestFn,
23
27
  RPCEndpoint,
24
28
  RPCResponse,
@@ -45,6 +49,25 @@ class Web3Middleware:
45
49
 
46
50
  return middleware
47
51
 
52
+ def wrap_make_batch_request(
53
+ self, make_batch_request: "MakeBatchRequestFn"
54
+ ) -> "MakeBatchRequestFn":
55
+ def middleware(
56
+ requests_info: List[Tuple["RPCEndpoint", Any]]
57
+ ) -> List["RPCResponse"]:
58
+ req_processed = [
59
+ self.request_processor(method, params)
60
+ for (method, params) in requests_info
61
+ ]
62
+ responses = make_batch_request(req_processed)
63
+ methods, _params = zip(*req_processed)
64
+ formatted_responses = [
65
+ self.response_processor(m, r) for m, r in zip(methods, responses)
66
+ ]
67
+ return formatted_responses
68
+
69
+ return middleware
70
+
48
71
  def request_processor(self, method: "RPCEndpoint", params: Any) -> Any:
49
72
  return method, params
50
73
 
@@ -67,6 +90,26 @@ class Web3Middleware:
67
90
 
68
91
  return middleware
69
92
 
93
+ async def async_wrap_make_batch_request(
94
+ self, make_batch_request: "AsyncMakeBatchRequestFn"
95
+ ) -> "AsyncMakeBatchRequestFn":
96
+ async def middleware(
97
+ requests_info: List[Tuple["RPCEndpoint", Any]]
98
+ ) -> List["RPCResponse"]:
99
+ req_processed = [
100
+ await self.async_request_processor(method, params)
101
+ for (method, params) in requests_info
102
+ ]
103
+ responses = await make_batch_request(req_processed)
104
+ methods, _params = zip(*req_processed)
105
+ formatted_responses = [
106
+ await self.async_response_processor(m, r)
107
+ for m, r in zip(methods, responses)
108
+ ]
109
+ return formatted_responses
110
+
111
+ return middleware
112
+
70
113
  async def async_request_processor(
71
114
  self,
72
115
  method: "RPCEndpoint",
web3/module.py CHANGED
@@ -5,6 +5,8 @@ from typing import (
5
5
  Coroutine,
6
6
  Dict,
7
7
  Optional,
8
+ Sequence,
9
+ Tuple,
8
10
  TypeVar,
9
11
  Union,
10
12
  cast,
@@ -55,9 +57,43 @@ def apply_result_formatters(
55
57
  TReturn = TypeVar("TReturn")
56
58
 
57
59
 
60
+ @curry
61
+ def retrieve_request_information_for_batching(
62
+ w3: Union["AsyncWeb3", "Web3"],
63
+ module: "Module",
64
+ method: Method[Callable[..., Any]],
65
+ ) -> Union[
66
+ Callable[..., Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]],
67
+ Callable[..., Coroutine[Any, Any, Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]]],
68
+ ]:
69
+ async def async_inner(
70
+ *args: Any, **kwargs: Any
71
+ ) -> Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]:
72
+ (method_str, params), response_formatters = method.process_params(
73
+ module, *args, **kwargs
74
+ )
75
+ if isinstance(w3.provider, PersistentConnectionProvider):
76
+ w3.provider._request_processor.cache_request_information(
77
+ cast(RPCEndpoint, method_str), params, response_formatters
78
+ )
79
+ return (cast(RPCEndpoint, method_str), params), response_formatters
80
+
81
+ def inner(
82
+ *args: Any, **kwargs: Any
83
+ ) -> Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]:
84
+ (method_str, params), response_formatters = method.process_params(
85
+ module, *args, **kwargs
86
+ )
87
+ return (cast(RPCEndpoint, method_str), params), response_formatters
88
+
89
+ return async_inner if module.is_async else inner
90
+
91
+
58
92
  @curry
59
93
  def retrieve_blocking_method_call_fn(
60
- w3: "Web3", module: "Module", method: Method[Callable[..., TReturn]]
94
+ w3: "Web3",
95
+ module: "Module",
96
+ method: Method[Callable[..., TReturn]],
61
97
  ) -> Callable[..., Union[TReturn, LogFilter]]:
62
98
  def caller(*args: Any, **kwargs: Any) -> Union[TReturn, LogFilter]:
63
99
  try:
@@ -82,7 +118,9 @@ def retrieve_blocking_method_call_fn(
82
118
 
83
119
  @curry
84
120
  def retrieve_async_method_call_fn(
85
- async_w3: "AsyncWeb3", module: "Module", method: Method[Callable[..., Any]]
121
+ async_w3: "AsyncWeb3",
122
+ module: "Module",
123
+ method: Method[Callable[..., Any]],
86
124
  ) -> Callable[..., Coroutine[Any, Any, Optional[Union[RPCResponse, AsyncLogFilter]]]]:
87
125
  async def caller(*args: Any, **kwargs: Any) -> Union[RPCResponse, AsyncLogFilter]:
88
126
  try:
@@ -93,12 +131,11 @@ def retrieve_async_method_call_fn(
93
131
  return AsyncLogFilter(eth_module=module, filter_id=err.filter_id)
94
132
 
95
133
  if isinstance(async_w3.provider, PersistentConnectionProvider):
96
- # TODO: The typing does not seem to be correct for response_formatters.
97
- # For now, keep the expected typing but ignore it here.
98
134
  provider = async_w3.provider
99
135
  cache_key = provider._request_processor.cache_request_information(
100
- cast(RPCEndpoint, method_str), params, response_formatters # type: ignore # noqa: E501
136
+ cast(RPCEndpoint, method_str), params, response_formatters
101
137
  )
138
+
102
139
  try:
103
140
  method_str = cast(RPCEndpoint, method_str)
104
141
  return await async_w3.manager.send(method_str, params)
@@ -139,6 +176,9 @@ class Module:
139
176
  self.retrieve_caller_fn = retrieve_async_method_call_fn(w3, self)
140
177
  else:
141
178
  self.retrieve_caller_fn = retrieve_blocking_method_call_fn(w3, self)
179
+ self.retrieve_request_information = retrieve_request_information_for_batching(
180
+ w3, self
181
+ )
142
182
  self.w3 = w3
143
183
 
144
184
  @property
@@ -152,8 +192,8 @@ class Module:
152
192
  ) -> None:
153
193
  for method_name, method_class in methods.items():
154
194
  klass = (
155
- method_class.__get__(obj=self)()
195
+ method_class.__get__(module=self)()
156
196
  if method_class.is_property
157
- else method_class.__get__(obj=self)
197
+ else method_class.__get__(module=self)
158
198
  )
159
199
  setattr(self, method_name, klass)