web3 7.6.1__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.
Files changed (46) hide show
  1. ens/async_ens.py +1 -1
  2. ens/ens.py +1 -1
  3. web3/_utils/caching/caching_utils.py +64 -0
  4. web3/_utils/caching/request_caching_validation.py +1 -0
  5. web3/_utils/events.py +1 -1
  6. web3/_utils/http_session_manager.py +32 -3
  7. web3/_utils/module_testing/eth_module.py +5 -18
  8. web3/_utils/module_testing/module_testing_utils.py +1 -43
  9. web3/_utils/module_testing/persistent_connection_provider.py +696 -207
  10. web3/_utils/module_testing/utils.py +99 -33
  11. web3/beacon/api_endpoints.py +10 -0
  12. web3/beacon/async_beacon.py +47 -0
  13. web3/beacon/beacon.py +45 -0
  14. web3/contract/async_contract.py +2 -206
  15. web3/contract/base_contract.py +217 -13
  16. web3/contract/contract.py +2 -205
  17. web3/datastructures.py +15 -16
  18. web3/eth/async_eth.py +23 -5
  19. web3/exceptions.py +7 -0
  20. web3/main.py +24 -3
  21. web3/manager.py +140 -48
  22. web3/method.py +1 -1
  23. web3/middleware/attrdict.py +12 -22
  24. web3/middleware/base.py +14 -6
  25. web3/module.py +17 -21
  26. web3/providers/async_base.py +23 -14
  27. web3/providers/base.py +6 -8
  28. web3/providers/ipc.py +7 -6
  29. web3/providers/legacy_websocket.py +1 -1
  30. web3/providers/persistent/async_ipc.py +5 -3
  31. web3/providers/persistent/persistent.py +121 -17
  32. web3/providers/persistent/persistent_connection.py +11 -4
  33. web3/providers/persistent/request_processor.py +49 -41
  34. web3/providers/persistent/subscription_container.py +56 -0
  35. web3/providers/persistent/subscription_manager.py +298 -0
  36. web3/providers/persistent/websocket.py +4 -4
  37. web3/providers/rpc/async_rpc.py +16 -3
  38. web3/providers/rpc/rpc.py +9 -5
  39. web3/types.py +28 -14
  40. web3/utils/__init__.py +4 -0
  41. web3/utils/subscriptions.py +289 -0
  42. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/LICENSE +1 -1
  43. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/METADATA +68 -56
  44. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/RECORD +46 -43
  45. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/WHEEL +1 -1
  46. {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/top_level.txt +0 -0
ens/async_ens.py CHANGED
@@ -152,12 +152,12 @@ class AsyncENS(BaseENS):
152
152
  :param int coin_type: if provided, look up the address for this coin type
153
153
  :raises InvalidName: if `name` has invalid syntax
154
154
  """
155
- r = await self.resolver(name)
156
155
  if coin_type is None:
157
156
  # don't validate `addr(bytes32)` interface id since extended resolvers
158
157
  # can implement a "resolve" function as of ENSIP-10
159
158
  return cast(ChecksumAddress, await self._resolve(name, "addr"))
160
159
  else:
160
+ r = await self.resolver(name)
161
161
  await _async_validate_resolver_and_interface_id(
162
162
  name, r, ENS_MULTICHAIN_ADDRESS_INTERFACE_ID, "addr(bytes32,uint256)"
163
163
  )
ens/ens.py CHANGED
@@ -154,12 +154,12 @@ class ENS(BaseENS):
154
154
  :raises UnsupportedFunction: if the resolver does not support the ``addr()``
155
155
  function
156
156
  """
157
- r = self.resolver(name)
158
157
  if coin_type is None:
159
158
  # don't validate `addr(bytes32)` interface id since extended resolvers
160
159
  # can implement a "resolve" function as of ENSIP-10
161
160
  return cast(ChecksumAddress, self._resolve(name, "addr"))
162
161
  else:
162
+ r = self.resolver(name)
163
163
  _validate_resolver_and_interface_id(
164
164
  name, r, ENS_MULTICHAIN_ADDRESS_INTERFACE_ID, "addr(bytes32,uint256)"
165
165
  )
@@ -64,10 +64,12 @@ if TYPE_CHECKING:
64
64
  from web3.providers import ( # noqa: F401
65
65
  AsyncBaseProvider,
66
66
  BaseProvider,
67
+ PersistentConnectionProvider,
67
68
  )
68
69
  from web3.types import ( # noqa: F401
69
70
  AsyncMakeRequestFn,
70
71
  MakeRequestFn,
72
+ RPCRequest,
71
73
  RPCResponse,
72
74
  )
73
75
 
@@ -367,3 +369,65 @@ def async_handle_request_caching(
367
369
  # save a reference to the decorator on the wrapped function
368
370
  wrapper._decorator = async_handle_request_caching # type: ignore
369
371
  return wrapper
372
+
373
+
374
+ def async_handle_send_caching(
375
+ func: Callable[
376
+ [ASYNC_PROVIDER_TYPE, RPCEndpoint, Any],
377
+ Coroutine[Any, Any, "RPCRequest"],
378
+ ],
379
+ ) -> Callable[..., Coroutine[Any, Any, "RPCRequest"]]:
380
+ async def wrapper(
381
+ provider: ASYNC_PROVIDER_TYPE, method: RPCEndpoint, params: Any
382
+ ) -> "RPCRequest":
383
+ if is_cacheable_request(provider, method, params):
384
+ request_cache = provider._request_cache
385
+ cache_key = generate_cache_key(
386
+ f"{threading.get_ident()}:{(method, params)}"
387
+ )
388
+ cached_response = request_cache.get_cache_entry(cache_key)
389
+ if cached_response is not None:
390
+ # The request data isn't used, this just prevents a cached request from
391
+ # being sent - return an empty request object
392
+ return {"id": -1, "method": RPCEndpoint(""), "params": []}
393
+ return await func(provider, method, params)
394
+
395
+ # save a reference to the decorator on the wrapped function
396
+ wrapper._decorator = async_handle_send_caching # type: ignore
397
+ return wrapper
398
+
399
+
400
+ def async_handle_recv_caching(
401
+ func: Callable[
402
+ ["PersistentConnectionProvider", "RPCRequest"],
403
+ Coroutine[Any, Any, "RPCResponse"],
404
+ ]
405
+ ) -> Callable[..., Coroutine[Any, Any, "RPCResponse"]]:
406
+ async def wrapper(
407
+ provider: "PersistentConnectionProvider",
408
+ rpc_request: "RPCRequest",
409
+ ) -> "RPCResponse":
410
+ method = rpc_request["method"]
411
+ params = rpc_request["params"]
412
+ if is_cacheable_request(provider, method, params):
413
+ request_cache = provider._request_cache
414
+ cache_key = generate_cache_key(
415
+ f"{threading.get_ident()}:{(method, params)}"
416
+ )
417
+ cache_result = request_cache.get_cache_entry(cache_key)
418
+ if cache_result is not None:
419
+ return cache_result
420
+ else:
421
+ response = await func(provider, rpc_request)
422
+ if await _async_should_cache_response(
423
+ provider, method, params, response
424
+ ):
425
+ async with provider._request_cache_lock:
426
+ request_cache.cache(cache_key, response)
427
+ return response
428
+ else:
429
+ return await func(provider, rpc_request)
430
+
431
+ # save a reference to the decorator on the wrapped function
432
+ wrapper._decorator = async_handle_recv_caching # type: ignore
433
+ return wrapper
@@ -19,6 +19,7 @@ if TYPE_CHECKING:
19
19
  from web3.providers import ( # noqa: F401
20
20
  AsyncBaseProvider,
21
21
  BaseProvider,
22
+ PersistentConnectionProvider,
22
23
  )
23
24
 
24
25
  UNCACHEABLE_BLOCK_IDS = {"finalized", "safe", "latest", "pending"}
web3/_utils/events.py CHANGED
@@ -325,7 +325,7 @@ normalize_topic_list = compose(
325
325
 
326
326
 
327
327
  def is_indexed(arg: Any) -> bool:
328
- if isinstance(arg, TopicArgumentFilter) is True:
328
+ if isinstance(arg, TopicArgumentFilter):
329
329
  return True
330
330
  return False
331
331
 
@@ -18,6 +18,7 @@ from aiohttp import (
18
18
  ClientResponse,
19
19
  ClientSession,
20
20
  ClientTimeout,
21
+ TCPConnector,
21
22
  )
22
23
  from eth_typing import (
23
24
  URI,
@@ -119,11 +120,19 @@ class HTTPSessionManager:
119
120
  def get_response_from_post_request(
120
121
  self, endpoint_uri: URI, *args: Any, **kwargs: Any
121
122
  ) -> requests.Response:
123
+ kwargs.setdefault("timeout", DEFAULT_HTTP_TIMEOUT)
122
124
  session = self.cache_and_return_session(
123
125
  endpoint_uri, request_timeout=kwargs["timeout"]
124
126
  )
125
127
  return session.post(endpoint_uri, *args, **kwargs)
126
128
 
129
+ def json_make_post_request(
130
+ self, endpoint_uri: URI, *args: Any, **kwargs: Any
131
+ ) -> Dict[str, Any]:
132
+ response = self.get_response_from_post_request(endpoint_uri, *args, **kwargs)
133
+ response.raise_for_status()
134
+ return response.json()
135
+
127
136
  def make_post_request(
128
137
  self, endpoint_uri: URI, data: Union[bytes, Dict[str, Any]], **kwargs: Any
129
138
  ) -> bytes:
@@ -142,8 +151,9 @@ class HTTPSessionManager:
142
151
  else:
143
152
  return response.content
144
153
 
154
+ @staticmethod
145
155
  def _handle_streaming_response(
146
- self, response: requests.Response, start: float, timeout: float
156
+ response: requests.Response, start: float, timeout: float
147
157
  ) -> bytes:
148
158
  response_body = b""
149
159
  for data in response.iter_content():
@@ -174,7 +184,12 @@ class HTTPSessionManager:
174
184
  async with async_lock(self.session_pool, self._lock):
175
185
  if cache_key not in self.session_cache:
176
186
  if session is None:
177
- session = ClientSession(raise_for_status=True)
187
+ session = ClientSession(
188
+ raise_for_status=True,
189
+ connector=TCPConnector(
190
+ force_close=True, enable_cleanup_closed=True
191
+ ),
192
+ )
178
193
 
179
194
  cached_session, evicted_items = self.session_cache.cache(
180
195
  cache_key, session
@@ -213,7 +228,12 @@ class HTTPSessionManager:
213
228
  )
214
229
 
215
230
  # replace stale session with a new session at the cache key
216
- _session = ClientSession(raise_for_status=True)
231
+ _session = ClientSession(
232
+ raise_for_status=True,
233
+ connector=TCPConnector(
234
+ force_close=True, enable_cleanup_closed=True
235
+ ),
236
+ )
217
237
  cached_session, evicted_items = self.session_cache.cache(
218
238
  cache_key, _session
219
239
  )
@@ -278,6 +298,15 @@ class HTTPSessionManager:
278
298
  response = await session.post(endpoint_uri, *args, **kwargs)
279
299
  return response
280
300
 
301
+ async def async_json_make_post_request(
302
+ self, endpoint_uri: URI, *args: Any, **kwargs: Any
303
+ ) -> Dict[str, Any]:
304
+ response = await self.async_get_response_from_post_request(
305
+ endpoint_uri, *args, **kwargs
306
+ )
307
+ response.raise_for_status()
308
+ return await response.json()
309
+
281
310
  async def async_make_post_request(
282
311
  self, endpoint_uri: URI, data: Union[bytes, Dict[str, Any]], **kwargs: Any
283
312
  ) -> bytes:
@@ -58,7 +58,6 @@ from web3._utils.module_testing.module_testing_utils import (
58
58
  assert_contains_log,
59
59
  async_mock_offchain_lookup_request_response,
60
60
  flaky_geth_dev_mining,
61
- flaky_with_xfail_on_exception,
62
61
  mock_offchain_lookup_request_response,
63
62
  )
64
63
  from web3._utils.module_testing.utils import (
@@ -77,7 +76,6 @@ from web3.exceptions import (
77
76
  MultipleFailedRequests,
78
77
  NameNotFound,
79
78
  OffchainLookup,
80
- RequestTimedOut,
81
79
  TimeExhausted,
82
80
  TooManyRequests,
83
81
  TransactionNotFound,
@@ -1991,9 +1989,8 @@ class AsyncEthModuleTest:
1991
1989
  # Test with None overflowing
1992
1990
  filter_params: FilterParams = {
1993
1991
  "fromBlock": BlockNumber(0),
1994
- "topics": [None, None, None],
1992
+ "topics": [None, None, None, None],
1995
1993
  }
1996
-
1997
1994
  result = await async_w3.eth.get_logs(filter_params)
1998
1995
  assert len(result) == 0
1999
1996
 
@@ -2391,10 +2388,7 @@ class AsyncEthModuleTest:
2391
2388
  with pytest.raises(Web3ValueError):
2392
2389
  await async_w3.eth.replace_transaction(txn_hash, txn_params)
2393
2390
 
2394
- @flaky_with_xfail_on_exception(
2395
- reason="Very flaky on CI runs, hard to reproduce locally.",
2396
- exception=RequestTimedOut,
2397
- )
2391
+ @flaky_geth_dev_mining
2398
2392
  @pytest.mark.asyncio
2399
2393
  async def test_async_eth_replace_transaction_gas_price_defaulting_minimum(
2400
2394
  self, async_w3: "AsyncWeb3", async_keyfile_account_address: ChecksumAddress
@@ -2418,10 +2412,7 @@ class AsyncEthModuleTest:
2418
2412
  gas_price * 1.125
2419
2413
  ) # minimum gas price
2420
2414
 
2421
- @flaky_with_xfail_on_exception(
2422
- reason="Very flaky on CI runs, hard to reproduce locally.",
2423
- exception=RequestTimedOut,
2424
- )
2415
+ @flaky_geth_dev_mining
2425
2416
  @pytest.mark.asyncio
2426
2417
  async def test_async_eth_replace_transaction_gas_price_defaulting_strategy_higher(
2427
2418
  self, async_w3: "AsyncWeb3", async_keyfile_account_address: ChecksumAddress
@@ -2437,7 +2428,7 @@ class AsyncEthModuleTest:
2437
2428
 
2438
2429
  two_gwei_in_wei = async_w3.to_wei(2, "gwei")
2439
2430
 
2440
- def higher_gas_price_strategy(async_w3: "AsyncWeb3", txn: TxParams) -> Wei:
2431
+ def higher_gas_price_strategy(_async_w3: "AsyncWeb3", _txn: TxParams) -> Wei:
2441
2432
  return two_gwei_in_wei
2442
2433
 
2443
2434
  async_w3.eth.set_gas_price_strategy(higher_gas_price_strategy)
@@ -2450,16 +2441,12 @@ class AsyncEthModuleTest:
2450
2441
  ) # Strategy provides higher gas price
2451
2442
  async_w3.eth.set_gas_price_strategy(None) # reset strategy
2452
2443
 
2453
- @flaky_with_xfail_on_exception(
2454
- reason="Very flaky on CI runs, hard to reproduce locally.",
2455
- exception=RequestTimedOut,
2456
- )
2444
+ @flaky_geth_dev_mining
2457
2445
  @pytest.mark.asyncio
2458
2446
  async def test_async_eth_replace_transaction_gas_price_defaulting_strategy_lower(
2459
2447
  self, async_w3: "AsyncWeb3", async_keyfile_account_address: ChecksumAddress
2460
2448
  ) -> None:
2461
2449
  gas_price = async_w3.to_wei(2, "gwei")
2462
-
2463
2450
  txn_params: TxParams = {
2464
2451
  "from": async_keyfile_account_address,
2465
2452
  "to": async_keyfile_account_address,
@@ -1,17 +1,12 @@
1
1
  import asyncio
2
- import functools
3
- import pytest
4
2
  from typing import (
5
3
  TYPE_CHECKING,
6
4
  Any,
7
- Callable,
8
5
  Collection,
9
6
  Dict,
10
7
  Generator,
11
8
  Literal,
12
9
  Sequence,
13
- Tuple,
14
- Type,
15
10
  Union,
16
11
  )
17
12
 
@@ -63,50 +58,13 @@ due to timing of the test running as a block is mined.
63
58
  flaky_geth_dev_mining = flaky(max_runs=3, min_passes=1)
64
59
 
65
60
 
66
- def flaky_with_xfail_on_exception(
67
- reason: str,
68
- exception: Union[Type[Exception], Tuple[Type[Exception], ...]],
69
- max_runs: int = 3,
70
- min_passes: int = 1,
71
- ) -> Callable[[Any], Any]:
72
- """
73
- Some tests inconsistently fail hard with a particular exception and retrying
74
- these tests often times does not get them "unstuck". If we've exhausted all flaky
75
- retries and this expected exception is raised, `xfail` the test with the given
76
- reason.
77
- """
78
- runs = max_runs
79
-
80
- def decorator(func: Any) -> Any:
81
- @flaky(max_runs=max_runs, min_passes=min_passes)
82
- @functools.wraps(func)
83
- async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
84
- nonlocal runs
85
- try:
86
- return await func(self, *args, **kwargs)
87
- except exception:
88
- # xfail the test only if the exception is raised and we have exhausted
89
- # all flaky retries
90
- if runs == 1:
91
- pytest.xfail(reason)
92
- runs -= 1
93
- pytest.fail(f"xfailed but {runs} run(s) remaining with flaky...")
94
- except Exception as e:
95
- # let flaky handle it
96
- raise e
97
-
98
- return wrapper
99
-
100
- return decorator
101
-
102
-
103
61
  def assert_contains_log(
104
62
  result: Sequence[LogReceipt],
105
63
  block_with_txn_with_log: BlockData,
106
64
  emitter_contract_address: ChecksumAddress,
107
65
  txn_hash_with_log: HexStr,
108
66
  ) -> None:
109
- assert len(result) == 1
67
+ assert len(result) > 0
110
68
  log_entry = result[0]
111
69
  assert log_entry["blockNumber"] == block_with_txn_with_log["number"]
112
70
  assert log_entry["blockHash"] == block_with_txn_with_log["hash"]