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.
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
- responses = request_func(
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
- formatted_responses = [
446
- self._format_batched_response(info, resp)
447
- for info, resp in zip(requests_info, responses)
448
- ]
449
- 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)
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
- responses = await request_func(
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(self.provider, PersistentConnectionProvider):
476
- # call _process_response for each response in the batch
477
- return [
478
- cast(RPCResponse, await self._process_response(resp))
479
- for resp in 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)
480
519
  ]
481
-
482
- formatted_responses = [
483
- self._format_batched_response(info, resp)
484
- for info, resp in zip(unpacked_requests_info, responses)
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
- 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
 
@@ -77,7 +77,8 @@ class AsyncBaseProvider:
77
77
 
78
78
  _is_batching: bool = False
79
79
  _batch_request_func_cache: Tuple[
80
- Tuple[Middleware, ...], Callable[..., Coroutine[Any, Any, List[RPCResponse]]]
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("Only AsyncHTTPProvider supports this method")
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 isinstance(raw_response, list):
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)