web3 7.7.0__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.
- ens/async_ens.py +1 -1
- ens/ens.py +1 -1
- web3/_utils/events.py +1 -1
- web3/_utils/http_session_manager.py +19 -1
- web3/_utils/module_testing/eth_module.py +3 -0
- web3/_utils/module_testing/module_testing_utils.py +0 -42
- web3/_utils/module_testing/persistent_connection_provider.py +38 -0
- web3/beacon/api_endpoints.py +10 -0
- web3/beacon/async_beacon.py +47 -0
- web3/beacon/beacon.py +45 -0
- web3/contract/async_contract.py +2 -206
- web3/contract/base_contract.py +208 -12
- web3/contract/contract.py +2 -205
- web3/datastructures.py +4 -0
- web3/manager.py +62 -18
- web3/middleware/base.py +14 -6
- web3/providers/async_base.py +5 -4
- web3/providers/base.py +3 -3
- web3/providers/persistent/request_processor.py +11 -1
- web3/providers/persistent/subscription_manager.py +110 -45
- web3/providers/rpc/async_rpc.py +8 -3
- web3/providers/rpc/rpc.py +8 -3
- web3/types.py +10 -11
- web3/utils/subscriptions.py +5 -1
- {web3-7.7.0.dist-info → web3-7.8.0.dist-info}/LICENSE +1 -1
- {web3-7.7.0.dist-info → web3-7.8.0.dist-info}/METADATA +1 -1
- {web3-7.7.0.dist-info → web3-7.8.0.dist-info}/RECORD +29 -29
- {web3-7.7.0.dist-info → web3-7.8.0.dist-info}/WHEEL +0 -0
- {web3-7.7.0.dist-info → web3-7.8.0.dist-info}/top_level.txt +0 -0
web3/contract/contract.py
CHANGED
|
@@ -13,7 +13,6 @@ from typing import (
|
|
|
13
13
|
|
|
14
14
|
from eth_typing import (
|
|
15
15
|
ABI,
|
|
16
|
-
ABIFunction,
|
|
17
16
|
ChecksumAddress,
|
|
18
17
|
)
|
|
19
18
|
from eth_utils import (
|
|
@@ -31,7 +30,6 @@ from hexbytes import (
|
|
|
31
30
|
|
|
32
31
|
from web3._utils.abi import (
|
|
33
32
|
fallback_func_abi_exists,
|
|
34
|
-
get_name_from_abi_element_identifier,
|
|
35
33
|
receive_func_abi_exists,
|
|
36
34
|
)
|
|
37
35
|
from web3._utils.abi_element_identifiers import (
|
|
@@ -42,8 +40,6 @@ from web3._utils.compat import (
|
|
|
42
40
|
Self,
|
|
43
41
|
)
|
|
44
42
|
from web3._utils.contracts import (
|
|
45
|
-
copy_contract_event,
|
|
46
|
-
copy_contract_function,
|
|
47
43
|
parse_block_identifier,
|
|
48
44
|
)
|
|
49
45
|
from web3._utils.datatypes import (
|
|
@@ -86,12 +82,6 @@ from web3.contract.utils import (
|
|
|
86
82
|
transact_with_contract_function,
|
|
87
83
|
)
|
|
88
84
|
from web3.exceptions import (
|
|
89
|
-
ABIEventNotFound,
|
|
90
|
-
ABIFunctionNotFound,
|
|
91
|
-
MismatchedABI,
|
|
92
|
-
NoABIEventsFound,
|
|
93
|
-
NoABIFound,
|
|
94
|
-
NoABIFunctionsFound,
|
|
95
85
|
Web3AttributeError,
|
|
96
86
|
Web3TypeError,
|
|
97
87
|
Web3ValidationError,
|
|
@@ -103,12 +93,6 @@ from web3.types import (
|
|
|
103
93
|
StateOverride,
|
|
104
94
|
TxParams,
|
|
105
95
|
)
|
|
106
|
-
from web3.utils.abi import (
|
|
107
|
-
_filter_by_argument_count,
|
|
108
|
-
_get_any_abi_signature_with_name,
|
|
109
|
-
_mismatched_abi_error_diagnosis,
|
|
110
|
-
get_abi_element,
|
|
111
|
-
)
|
|
112
96
|
|
|
113
97
|
if TYPE_CHECKING:
|
|
114
98
|
from ens import ENS # noqa: F401
|
|
@@ -119,9 +103,6 @@ class ContractEvent(BaseContractEvent):
|
|
|
119
103
|
# mypy types
|
|
120
104
|
w3: "Web3"
|
|
121
105
|
|
|
122
|
-
def __call__(self, *args: Any, **kwargs: Any) -> "ContractEvent":
|
|
123
|
-
return copy_contract_event(self, *args, **kwargs)
|
|
124
|
-
|
|
125
106
|
@combomethod
|
|
126
107
|
def get_logs(
|
|
127
108
|
self,
|
|
@@ -255,162 +236,18 @@ class ContractEvent(BaseContractEvent):
|
|
|
255
236
|
builder.address = self.address
|
|
256
237
|
return builder
|
|
257
238
|
|
|
258
|
-
@classmethod
|
|
259
|
-
def factory(cls, class_name: str, **kwargs: Any) -> Self:
|
|
260
|
-
return PropertyCheckingFactory(class_name, (cls,), kwargs)()
|
|
261
239
|
|
|
262
|
-
|
|
263
|
-
class ContractEvents(BaseContractEvents):
|
|
240
|
+
class ContractEvents(BaseContractEvents[ContractEvent]):
|
|
264
241
|
def __init__(
|
|
265
242
|
self, abi: ABI, w3: "Web3", address: Optional[ChecksumAddress] = None
|
|
266
243
|
) -> None:
|
|
267
244
|
super().__init__(abi, w3, ContractEvent, address)
|
|
268
245
|
|
|
269
|
-
def __getattr__(self, event_name: str) -> "ContractEvent":
|
|
270
|
-
if super().__getattribute__("abi") is None:
|
|
271
|
-
raise NoABIFound(
|
|
272
|
-
"There is no ABI found for this contract.",
|
|
273
|
-
)
|
|
274
|
-
elif "_events" not in self.__dict__ or len(self._events) == 0:
|
|
275
|
-
raise NoABIEventsFound(
|
|
276
|
-
"The abi for this contract contains no event definitions. ",
|
|
277
|
-
"Are you sure you provided the correct contract abi?",
|
|
278
|
-
)
|
|
279
|
-
elif get_name_from_abi_element_identifier(event_name) not in [
|
|
280
|
-
get_name_from_abi_element_identifier(event["name"])
|
|
281
|
-
for event in self._events
|
|
282
|
-
]:
|
|
283
|
-
raise ABIEventNotFound(
|
|
284
|
-
f"The event '{event_name}' was not found in this contract's abi. ",
|
|
285
|
-
"Are you sure you provided the correct contract abi?",
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
if "(" not in event_name:
|
|
289
|
-
event_name = _get_any_abi_signature_with_name(event_name, self._events)
|
|
290
|
-
else:
|
|
291
|
-
event_name = f"_{event_name}"
|
|
292
|
-
|
|
293
|
-
return super().__getattribute__(event_name)
|
|
294
|
-
|
|
295
|
-
def __getitem__(self, event_name: str) -> "ContractEvent":
|
|
296
|
-
return getattr(self, event_name)
|
|
297
|
-
|
|
298
|
-
def __iter__(self) -> Iterable["ContractEvent"]:
|
|
299
|
-
if not hasattr(self, "_events") or not self._events:
|
|
300
|
-
return
|
|
301
|
-
|
|
302
|
-
for event in self._events:
|
|
303
|
-
yield self[abi_to_signature(event)]
|
|
304
|
-
|
|
305
246
|
|
|
306
247
|
class ContractFunction(BaseContractFunction):
|
|
307
248
|
# mypy types
|
|
308
249
|
w3: "Web3"
|
|
309
250
|
|
|
310
|
-
def __call__(self, *args: Any, **kwargs: Any) -> "ContractFunction":
|
|
311
|
-
# When a function is called, check arguments to obtain the correct function
|
|
312
|
-
# in the contract. self will be used if all args and kwargs are
|
|
313
|
-
# encodable to self.abi, otherwise the correct function is obtained from
|
|
314
|
-
# the contract.
|
|
315
|
-
if (
|
|
316
|
-
self.abi_element_identifier in [FallbackFn, ReceiveFn]
|
|
317
|
-
or self.abi_element_identifier == "constructor"
|
|
318
|
-
):
|
|
319
|
-
return copy_contract_function(self, *args, **kwargs)
|
|
320
|
-
|
|
321
|
-
all_functions = cast(
|
|
322
|
-
List[ABIFunction],
|
|
323
|
-
filter_abi_by_type(
|
|
324
|
-
"function",
|
|
325
|
-
self.contract_abi,
|
|
326
|
-
),
|
|
327
|
-
)
|
|
328
|
-
# Filter functions by name to obtain function signatures
|
|
329
|
-
function_name = get_name_from_abi_element_identifier(
|
|
330
|
-
self.abi_element_identifier
|
|
331
|
-
)
|
|
332
|
-
function_abis = [
|
|
333
|
-
function for function in all_functions if function["name"] == function_name
|
|
334
|
-
]
|
|
335
|
-
num_args = len(args) + len(kwargs)
|
|
336
|
-
function_abis_with_arg_count = cast(
|
|
337
|
-
List[ABIFunction],
|
|
338
|
-
_filter_by_argument_count(
|
|
339
|
-
num_args,
|
|
340
|
-
function_abis,
|
|
341
|
-
),
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
if not len(function_abis_with_arg_count):
|
|
345
|
-
# Build an ABI without arguments to determine if one exists
|
|
346
|
-
function_abis_with_arg_count = [
|
|
347
|
-
ABIFunction({"type": "function", "name": function_name})
|
|
348
|
-
]
|
|
349
|
-
|
|
350
|
-
# Check that arguments in call match a function ABI
|
|
351
|
-
num_attempts = 0
|
|
352
|
-
function_abi_matches = []
|
|
353
|
-
contract_function = None
|
|
354
|
-
for abi in function_abis_with_arg_count:
|
|
355
|
-
try:
|
|
356
|
-
num_attempts += 1
|
|
357
|
-
|
|
358
|
-
# Search for a function ABI that matches the arguments used
|
|
359
|
-
function_abi_matches.append(
|
|
360
|
-
cast(
|
|
361
|
-
ABIFunction,
|
|
362
|
-
get_abi_element(
|
|
363
|
-
function_abis,
|
|
364
|
-
abi_to_signature(abi),
|
|
365
|
-
*args,
|
|
366
|
-
abi_codec=self.w3.codec,
|
|
367
|
-
**kwargs,
|
|
368
|
-
),
|
|
369
|
-
)
|
|
370
|
-
)
|
|
371
|
-
except MismatchedABI:
|
|
372
|
-
# ignore exceptions
|
|
373
|
-
continue
|
|
374
|
-
|
|
375
|
-
if len(function_abi_matches) == 1:
|
|
376
|
-
function_abi = function_abi_matches[0]
|
|
377
|
-
if abi_to_signature(self.abi) == abi_to_signature(function_abi):
|
|
378
|
-
contract_function = self
|
|
379
|
-
else:
|
|
380
|
-
# Found a match that is not self
|
|
381
|
-
contract_function = ContractFunction.factory(
|
|
382
|
-
abi_to_signature(function_abi),
|
|
383
|
-
w3=self.w3,
|
|
384
|
-
contract_abi=self.contract_abi,
|
|
385
|
-
address=self.address,
|
|
386
|
-
abi_element_identifier=abi_to_signature(function_abi),
|
|
387
|
-
abi=function_abi,
|
|
388
|
-
)
|
|
389
|
-
else:
|
|
390
|
-
for abi in function_abi_matches:
|
|
391
|
-
if abi_to_signature(self.abi) == abi_to_signature(abi):
|
|
392
|
-
contract_function = self
|
|
393
|
-
break
|
|
394
|
-
else:
|
|
395
|
-
# Raise exception if multiple found
|
|
396
|
-
raise MismatchedABI(
|
|
397
|
-
_mismatched_abi_error_diagnosis(
|
|
398
|
-
function_name,
|
|
399
|
-
self.contract_abi,
|
|
400
|
-
len(function_abi_matches),
|
|
401
|
-
num_args,
|
|
402
|
-
*args,
|
|
403
|
-
abi_codec=self.w3.codec,
|
|
404
|
-
**kwargs,
|
|
405
|
-
)
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
return copy_contract_function(contract_function, *args, **kwargs)
|
|
409
|
-
|
|
410
|
-
@classmethod
|
|
411
|
-
def factory(cls, class_name: str, **kwargs: Any) -> Self:
|
|
412
|
-
return PropertyCheckingFactory(class_name, (cls,), kwargs)()
|
|
413
|
-
|
|
414
251
|
def call(
|
|
415
252
|
self,
|
|
416
253
|
transaction: Optional[TxParams] = None,
|
|
@@ -555,7 +392,7 @@ class ContractFunction(BaseContractFunction):
|
|
|
555
392
|
return cast(ContractFunction, NonExistentReceiveFunction())
|
|
556
393
|
|
|
557
394
|
|
|
558
|
-
class ContractFunctions(BaseContractFunctions):
|
|
395
|
+
class ContractFunctions(BaseContractFunctions[ContractFunction]):
|
|
559
396
|
def __init__(
|
|
560
397
|
self,
|
|
561
398
|
abi: ABI,
|
|
@@ -565,46 +402,6 @@ class ContractFunctions(BaseContractFunctions):
|
|
|
565
402
|
) -> None:
|
|
566
403
|
super().__init__(abi, w3, ContractFunction, address, decode_tuples)
|
|
567
404
|
|
|
568
|
-
def __iter__(self) -> Iterable["ContractFunction"]:
|
|
569
|
-
if not hasattr(self, "_functions") or not self._functions:
|
|
570
|
-
return
|
|
571
|
-
|
|
572
|
-
for func in self._functions:
|
|
573
|
-
yield self[abi_to_signature(func)]
|
|
574
|
-
|
|
575
|
-
def __getattr__(self, function_name: str) -> "ContractFunction":
|
|
576
|
-
if super().__getattribute__("abi") is None:
|
|
577
|
-
raise NoABIFound(
|
|
578
|
-
"There is no ABI found for this contract.",
|
|
579
|
-
)
|
|
580
|
-
elif "_functions" not in self.__dict__ or len(self._functions) == 0:
|
|
581
|
-
raise NoABIFunctionsFound(
|
|
582
|
-
"The abi for this contract contains no function definitions. ",
|
|
583
|
-
"Are you sure you provided the correct contract abi?",
|
|
584
|
-
)
|
|
585
|
-
elif get_name_from_abi_element_identifier(function_name) not in [
|
|
586
|
-
get_name_from_abi_element_identifier(function["name"])
|
|
587
|
-
for function in self._functions
|
|
588
|
-
]:
|
|
589
|
-
raise ABIFunctionNotFound(
|
|
590
|
-
f"The function '{function_name}' was not found in this ",
|
|
591
|
-
"contract's abi.",
|
|
592
|
-
)
|
|
593
|
-
|
|
594
|
-
if "(" not in function_name:
|
|
595
|
-
function_name = _get_any_abi_signature_with_name(
|
|
596
|
-
function_name, self._functions
|
|
597
|
-
)
|
|
598
|
-
else:
|
|
599
|
-
function_name = f"_{function_name}"
|
|
600
|
-
|
|
601
|
-
return super().__getattribute__(
|
|
602
|
-
function_name,
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
def __getitem__(self, function_name: str) -> "ContractFunction":
|
|
606
|
-
return getattr(self, function_name)
|
|
607
|
-
|
|
608
405
|
|
|
609
406
|
class Contract(BaseContract):
|
|
610
407
|
# mypy types
|
web3/datastructures.py
CHANGED
|
@@ -17,6 +17,7 @@ from typing import (
|
|
|
17
17
|
Tuple,
|
|
18
18
|
TypeVar,
|
|
19
19
|
Union,
|
|
20
|
+
ValuesView,
|
|
20
21
|
cast,
|
|
21
22
|
)
|
|
22
23
|
|
|
@@ -337,3 +338,6 @@ class NamedElementOnion(Mapping[TKey, TValue]):
|
|
|
337
338
|
# This leads to typing issues, so it's better to use
|
|
338
339
|
# ``as_tuple_of_middleware()`` to achieve the same result.
|
|
339
340
|
return iter(self._reversed_middleware()) # type: ignore
|
|
341
|
+
|
|
342
|
+
def values(self) -> ValuesView[TValue]:
|
|
343
|
+
return ValuesView(self._queue)
|
web3/manager.py
CHANGED
|
@@ -8,6 +8,7 @@ from typing import (
|
|
|
8
8
|
Coroutine,
|
|
9
9
|
Dict,
|
|
10
10
|
List,
|
|
11
|
+
NoReturn,
|
|
11
12
|
Optional,
|
|
12
13
|
Sequence,
|
|
13
14
|
Tuple,
|
|
@@ -266,6 +267,30 @@ def _validate_response(
|
|
|
266
267
|
_raise_bad_response_format(response)
|
|
267
268
|
|
|
268
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
|
+
|
|
269
294
|
class RequestManager:
|
|
270
295
|
logger = logging.getLogger("web3.manager.RequestManager")
|
|
271
296
|
|
|
@@ -436,17 +461,23 @@ class RequestManager:
|
|
|
436
461
|
request_func = provider.batch_request_func(
|
|
437
462
|
cast("Web3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
|
|
438
463
|
)
|
|
439
|
-
|
|
464
|
+
response = request_func(
|
|
440
465
|
[
|
|
441
466
|
(method, params)
|
|
442
467
|
for (method, params), _response_formatters in requests_info
|
|
443
468
|
]
|
|
444
469
|
)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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)
|
|
450
481
|
|
|
451
482
|
async def _async_make_batch_request(
|
|
452
483
|
self,
|
|
@@ -465,25 +496,31 @@ class RequestManager:
|
|
|
465
496
|
# since we add items to the batch without awaiting, we unpack the coroutines
|
|
466
497
|
# and await them all here
|
|
467
498
|
unpacked_requests_info = await asyncio.gather(*requests_info)
|
|
468
|
-
|
|
499
|
+
response = await request_func(
|
|
469
500
|
[
|
|
470
501
|
(method, params)
|
|
471
502
|
for (method, params), _response_formatters in unpacked_requests_info
|
|
472
503
|
]
|
|
473
504
|
)
|
|
474
505
|
|
|
475
|
-
if isinstance(
|
|
476
|
-
#
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
for
|
|
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)
|
|
480
519
|
]
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
]
|
|
486
|
-
return list(formatted_responses)
|
|
520
|
+
return list(formatted_responses)
|
|
521
|
+
else:
|
|
522
|
+
# expect a single response with an error
|
|
523
|
+
_raise_error_for_batch_response(response, self.logger)
|
|
487
524
|
|
|
488
525
|
def _format_batched_response(
|
|
489
526
|
self,
|
|
@@ -491,6 +528,13 @@ class RequestManager:
|
|
|
491
528
|
response: RPCResponse,
|
|
492
529
|
) -> RPCResponse:
|
|
493
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
|
+
)
|
|
494
538
|
return apply_result_formatters(
|
|
495
539
|
result_formatters,
|
|
496
540
|
self.formatted_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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
123
|
+
for m, r in zip(methods, response)
|
|
116
124
|
]
|
|
117
125
|
return formatted_responses
|
|
118
126
|
|
web3/providers/async_base.py
CHANGED
|
@@ -77,7 +77,8 @@ class AsyncBaseProvider:
|
|
|
77
77
|
|
|
78
78
|
_is_batching: bool = False
|
|
79
79
|
_batch_request_func_cache: Tuple[
|
|
80
|
-
Tuple[Middleware, ...],
|
|
80
|
+
Tuple[Middleware, ...],
|
|
81
|
+
Callable[..., Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]]],
|
|
81
82
|
] = (None, None)
|
|
82
83
|
|
|
83
84
|
is_async = True
|
|
@@ -119,7 +120,7 @@ class AsyncBaseProvider:
|
|
|
119
120
|
|
|
120
121
|
async def batch_request_func(
|
|
121
122
|
self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
|
|
122
|
-
) -> Callable[..., Coroutine[Any, Any, List[RPCResponse]]]:
|
|
123
|
+
) -> Callable[..., Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]]]:
|
|
123
124
|
middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
|
|
124
125
|
|
|
125
126
|
cache_key = self._batch_request_func_cache[0]
|
|
@@ -141,8 +142,8 @@ class AsyncBaseProvider:
|
|
|
141
142
|
|
|
142
143
|
async def make_batch_request(
|
|
143
144
|
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
144
|
-
) -> List[RPCResponse]:
|
|
145
|
-
raise NotImplementedError("
|
|
145
|
+
) -> Union[List[RPCResponse], RPCResponse]:
|
|
146
|
+
raise NotImplementedError("Providers must implement this method")
|
|
146
147
|
|
|
147
148
|
async def is_connected(self, show_traceback: bool = False) -> bool:
|
|
148
149
|
raise NotImplementedError("Providers must implement this method")
|
web3/providers/base.py
CHANGED
|
@@ -118,7 +118,7 @@ class JSONBaseProvider(BaseProvider):
|
|
|
118
118
|
|
|
119
119
|
_is_batching: bool = False
|
|
120
120
|
_batch_request_func_cache: Tuple[
|
|
121
|
-
Tuple[Middleware, ...], Callable[..., List[RPCResponse]]
|
|
121
|
+
Tuple[Middleware, ...], Callable[..., Union[List[RPCResponse], RPCResponse]]
|
|
122
122
|
] = (None, None)
|
|
123
123
|
|
|
124
124
|
def __init__(self, **kwargs: Any) -> None:
|
|
@@ -168,7 +168,7 @@ class JSONBaseProvider(BaseProvider):
|
|
|
168
168
|
|
|
169
169
|
def batch_request_func(
|
|
170
170
|
self, w3: "Web3", middleware_onion: MiddlewareOnion
|
|
171
|
-
) -> Callable[..., List[RPCResponse]]:
|
|
171
|
+
) -> Callable[..., Union[List[RPCResponse], RPCResponse]]:
|
|
172
172
|
middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
|
|
173
173
|
|
|
174
174
|
cache_key = self._batch_request_func_cache[0]
|
|
@@ -199,5 +199,5 @@ class JSONBaseProvider(BaseProvider):
|
|
|
199
199
|
|
|
200
200
|
def make_batch_request(
|
|
201
201
|
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
202
|
-
) -> List[RPCResponse]:
|
|
202
|
+
) -> Union[List[RPCResponse], RPCResponse]:
|
|
203
203
|
raise NotImplementedError("Providers must implement this method")
|
|
@@ -6,6 +6,7 @@ from typing import (
|
|
|
6
6
|
Callable,
|
|
7
7
|
Dict,
|
|
8
8
|
Generic,
|
|
9
|
+
List,
|
|
9
10
|
Optional,
|
|
10
11
|
Tuple,
|
|
11
12
|
TypeVar,
|
|
@@ -270,6 +271,15 @@ class RequestProcessor:
|
|
|
270
271
|
|
|
271
272
|
# raw response cache
|
|
272
273
|
|
|
274
|
+
def _is_batch_response(
|
|
275
|
+
self, raw_response: Union[List[RPCResponse], RPCResponse]
|
|
276
|
+
) -> bool:
|
|
277
|
+
return isinstance(raw_response, list) or (
|
|
278
|
+
isinstance(raw_response, dict)
|
|
279
|
+
and raw_response.get("id") is None
|
|
280
|
+
and self._provider._is_batching
|
|
281
|
+
)
|
|
282
|
+
|
|
273
283
|
async def cache_raw_response(
|
|
274
284
|
self, raw_response: Any, subscription: bool = False
|
|
275
285
|
) -> None:
|
|
@@ -296,7 +306,7 @@ class RequestProcessor:
|
|
|
296
306
|
# otherwise, put it in the subscription response queue so a response
|
|
297
307
|
# can be yielded by the message stream
|
|
298
308
|
await self._subscription_response_queue.put(raw_response)
|
|
299
|
-
elif
|
|
309
|
+
elif self._is_batch_response(raw_response):
|
|
300
310
|
# Since only one batch should be in the cache at all times, we use a
|
|
301
311
|
# constant cache key for the batch response.
|
|
302
312
|
cache_key = generate_cache_key(BATCH_REQUEST_ID)
|