web3 7.12.1__py3-none-any.whl → 7.14.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 +9 -5
- ens/base_ens.py +1 -1
- ens/utils.py +2 -1
- web3/_utils/abi.py +5 -5
- web3/_utils/async_transactions.py +12 -9
- web3/_utils/batching.py +1 -1
- web3/_utils/caching/caching_utils.py +2 -2
- web3/_utils/contracts.py +5 -5
- web3/_utils/ens.py +1 -1
- web3/_utils/events.py +1 -1
- web3/_utils/module_testing/eth_module.py +133 -125
- web3/_utils/module_testing/go_ethereum_admin_module.py +7 -6
- web3/_utils/module_testing/go_ethereum_debug_module.py +5 -4
- web3/_utils/module_testing/go_ethereum_txpool_module.py +6 -3
- web3/_utils/module_testing/module_testing_utils.py +1 -1
- web3/_utils/module_testing/net_module.py +4 -3
- web3/_utils/module_testing/persistent_connection_provider.py +132 -20
- web3/_utils/module_testing/utils.py +7 -6
- web3/_utils/module_testing/web3_module.py +15 -11
- web3/_utils/normalizers.py +3 -3
- web3/_utils/transactions.py +2 -1
- web3/contract/async_contract.py +13 -13
- web3/contract/base_contract.py +11 -12
- web3/contract/utils.py +7 -7
- web3/eth/async_eth.py +3 -14
- web3/exceptions.py +8 -1
- web3/gas_strategies/time_based.py +1 -1
- web3/main.py +24 -11
- web3/manager.py +11 -13
- web3/middleware/__init__.py +1 -1
- web3/middleware/base.py +5 -5
- web3/middleware/buffered_gas_estimate.py +1 -1
- web3/middleware/filter.py +10 -9
- web3/middleware/formatting.py +11 -5
- web3/middleware/gas_price_strategy.py +1 -1
- web3/middleware/names.py +4 -4
- web3/middleware/signing.py +3 -3
- web3/middleware/stalecheck.py +2 -2
- web3/middleware/validation.py +2 -2
- web3/module.py +3 -3
- web3/providers/async_base.py +2 -2
- web3/providers/base.py +2 -2
- web3/providers/eth_tester/defaults.py +2 -2
- web3/providers/eth_tester/main.py +1 -1
- web3/providers/eth_tester/middleware.py +2 -2
- web3/providers/persistent/persistent.py +8 -7
- web3/providers/persistent/persistent_connection.py +1 -1
- web3/providers/persistent/subscription_manager.py +67 -14
- web3/providers/persistent/utils.py +1 -1
- web3/types.py +16 -3
- web3/utils/subscriptions.py +26 -4
- {web3-7.12.1.dist-info → web3-7.14.0.dist-info}/METADATA +1 -1
- {web3-7.12.1.dist-info → web3-7.14.0.dist-info}/RECORD +56 -57
- ens/specs/.DS_Store +0 -0
- {web3-7.12.1.dist-info → web3-7.14.0.dist-info}/WHEEL +0 -0
- {web3-7.12.1.dist-info → web3-7.14.0.dist-info}/licenses/LICENSE +0 -0
- {web3-7.12.1.dist-info → web3-7.14.0.dist-info}/top_level.txt +0 -0
web3/middleware/formatting.py
CHANGED
|
@@ -16,6 +16,7 @@ from eth_utils.toolz import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from web3.exceptions import (
|
|
19
|
+
BadResponseFormat,
|
|
19
20
|
Web3ValueError,
|
|
20
21
|
)
|
|
21
22
|
from web3.middleware.base import (
|
|
@@ -77,7 +78,12 @@ def _apply_response_formatters(
|
|
|
77
78
|
response, response_type, method_response_formatter(appropriate_response)
|
|
78
79
|
)
|
|
79
80
|
|
|
80
|
-
if
|
|
81
|
+
if not isinstance(response, dict):
|
|
82
|
+
raise BadResponseFormat(
|
|
83
|
+
"Malformed response: expected a valid JSON-RPC response object, got: "
|
|
84
|
+
"`{}`".format(response)
|
|
85
|
+
)
|
|
86
|
+
elif response.get("result") is not None and method in result_formatters:
|
|
81
87
|
return _format_response("result", result_formatters[method])
|
|
82
88
|
elif (
|
|
83
89
|
# eth_subscription responses
|
|
@@ -94,7 +100,7 @@ def _apply_response_formatters(
|
|
|
94
100
|
|
|
95
101
|
SYNC_FORMATTERS_BUILDER = Callable[["Web3", RPCEndpoint], FormattersDict]
|
|
96
102
|
ASYNC_FORMATTERS_BUILDER = Callable[
|
|
97
|
-
["AsyncWeb3", RPCEndpoint], Coroutine[Any, Any, FormattersDict]
|
|
103
|
+
["AsyncWeb3[Any]", RPCEndpoint], Coroutine[Any, Any, FormattersDict]
|
|
98
104
|
]
|
|
99
105
|
|
|
100
106
|
|
|
@@ -108,7 +114,7 @@ class FormattingMiddlewareBuilder(Web3MiddlewareBuilder):
|
|
|
108
114
|
@staticmethod
|
|
109
115
|
@curry
|
|
110
116
|
def build(
|
|
111
|
-
w3: Union["
|
|
117
|
+
w3: Union["Web3", "AsyncWeb3[Any]"],
|
|
112
118
|
# formatters option:
|
|
113
119
|
request_formatters: Optional[Formatters] = None,
|
|
114
120
|
result_formatters: Optional[Formatters] = None,
|
|
@@ -180,7 +186,7 @@ class FormattingMiddlewareBuilder(Web3MiddlewareBuilder):
|
|
|
180
186
|
formatters = merge(
|
|
181
187
|
FORMATTER_DEFAULTS,
|
|
182
188
|
await self.async_formatters_builder(
|
|
183
|
-
cast("AsyncWeb3", self._w3), method
|
|
189
|
+
cast("AsyncWeb3[Any]", self._w3), method
|
|
184
190
|
),
|
|
185
191
|
)
|
|
186
192
|
self.request_formatters = formatters.pop("request_formatters")
|
|
@@ -198,7 +204,7 @@ class FormattingMiddlewareBuilder(Web3MiddlewareBuilder):
|
|
|
198
204
|
formatters = merge(
|
|
199
205
|
FORMATTER_DEFAULTS,
|
|
200
206
|
await self.async_formatters_builder(
|
|
201
|
-
cast("AsyncWeb3", self._w3), method
|
|
207
|
+
cast("AsyncWeb3[Any]", self._w3), method
|
|
202
208
|
),
|
|
203
209
|
)
|
|
204
210
|
self.result_formatters = formatters["result_formatters"]
|
|
@@ -106,7 +106,7 @@ class GasPriceStrategyMiddleware(Web3Middleware):
|
|
|
106
106
|
async def async_request_processor(self, method: RPCEndpoint, params: Any) -> Any:
|
|
107
107
|
if method == "eth_sendTransaction":
|
|
108
108
|
transaction = params[0]
|
|
109
|
-
w3 = cast("AsyncWeb3", self._w3)
|
|
109
|
+
w3 = cast("AsyncWeb3[Any]", self._w3)
|
|
110
110
|
generated_gas_price = w3.eth.generate_gas_price(transaction)
|
|
111
111
|
latest_block = await w3.eth.get_block("latest")
|
|
112
112
|
transaction = validate_transaction_params(
|
web3/middleware/names.py
CHANGED
|
@@ -53,7 +53,7 @@ def _is_logs_subscription_with_optional_args(method: RPCEndpoint, params: Any) -
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
async def async_format_all_ens_names_to_address(
|
|
56
|
-
async_web3: "AsyncWeb3",
|
|
56
|
+
async_web3: "AsyncWeb3[Any]",
|
|
57
57
|
abi_types_for_method: Sequence[Any],
|
|
58
58
|
data: Sequence[Any],
|
|
59
59
|
) -> Sequence[Any]:
|
|
@@ -69,7 +69,7 @@ async def async_format_all_ens_names_to_address(
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
async def async_apply_ens_to_address_conversion(
|
|
72
|
-
async_web3: "AsyncWeb3",
|
|
72
|
+
async_web3: "AsyncWeb3[Any]",
|
|
73
73
|
params: Any,
|
|
74
74
|
abi_types_for_method: Union[Sequence[str], Dict[str, str]],
|
|
75
75
|
) -> Any:
|
|
@@ -125,7 +125,7 @@ class ENSNameToAddressMiddleware(Web3Middleware):
|
|
|
125
125
|
# eth_subscribe optional logs params are unique.
|
|
126
126
|
# Handle them separately here.
|
|
127
127
|
(formatted_dict,) = await async_apply_ens_to_address_conversion(
|
|
128
|
-
cast("AsyncWeb3", self._w3),
|
|
128
|
+
cast("AsyncWeb3[Any]", self._w3),
|
|
129
129
|
(params[1],),
|
|
130
130
|
{
|
|
131
131
|
"address": "address",
|
|
@@ -136,7 +136,7 @@ class ENSNameToAddressMiddleware(Web3Middleware):
|
|
|
136
136
|
|
|
137
137
|
else:
|
|
138
138
|
params = await async_apply_ens_to_address_conversion(
|
|
139
|
-
cast("AsyncWeb3", self._w3),
|
|
139
|
+
cast("AsyncWeb3[Any]", self._w3),
|
|
140
140
|
params,
|
|
141
141
|
abi_types_for_method,
|
|
142
142
|
)
|
web3/middleware/signing.py
CHANGED
|
@@ -93,7 +93,7 @@ _PrivateKey = Union[LocalAccount, PrivateKey, HexStr, bytes]
|
|
|
93
93
|
|
|
94
94
|
@to_dict
|
|
95
95
|
def gen_normalized_accounts(
|
|
96
|
-
val: Union[_PrivateKey, Collection[_PrivateKey]]
|
|
96
|
+
val: Union[_PrivateKey, Collection[_PrivateKey]],
|
|
97
97
|
) -> Iterable[Tuple[ChecksumAddress, LocalAccount]]:
|
|
98
98
|
if isinstance(
|
|
99
99
|
val,
|
|
@@ -158,7 +158,7 @@ class SignAndSendRawMiddlewareBuilder(Web3MiddlewareBuilder):
|
|
|
158
158
|
@curry
|
|
159
159
|
def build(
|
|
160
160
|
private_key_or_account: Union[_PrivateKey, Collection[_PrivateKey]],
|
|
161
|
-
w3: Union["Web3", "AsyncWeb3"],
|
|
161
|
+
w3: Union["Web3", "AsyncWeb3[Any]"],
|
|
162
162
|
) -> "SignAndSendRawMiddlewareBuilder":
|
|
163
163
|
middleware = SignAndSendRawMiddlewareBuilder(w3)
|
|
164
164
|
middleware._accounts = gen_normalized_accounts(private_key_or_account)
|
|
@@ -199,7 +199,7 @@ class SignAndSendRawMiddlewareBuilder(Web3MiddlewareBuilder):
|
|
|
199
199
|
return method, params
|
|
200
200
|
|
|
201
201
|
else:
|
|
202
|
-
w3 = cast("AsyncWeb3", self._w3)
|
|
202
|
+
w3 = cast("AsyncWeb3[Any]", self._w3)
|
|
203
203
|
|
|
204
204
|
formatted_transaction = format_transaction(params[0])
|
|
205
205
|
filled_transaction = await async_fill_transaction_defaults(
|
web3/middleware/stalecheck.py
CHANGED
|
@@ -51,7 +51,7 @@ class StalecheckMiddlewareBuilder(Web3MiddlewareBuilder):
|
|
|
51
51
|
@curry
|
|
52
52
|
def build(
|
|
53
53
|
allowable_delay: int,
|
|
54
|
-
w3: Union["Web3", "AsyncWeb3"],
|
|
54
|
+
w3: Union["Web3", "AsyncWeb3[Any]"],
|
|
55
55
|
skip_stalecheck_for_methods: Collection[str] = SKIP_STALECHECK_FOR_METHODS,
|
|
56
56
|
) -> Web3Middleware:
|
|
57
57
|
if allowable_delay <= 0:
|
|
@@ -82,7 +82,7 @@ class StalecheckMiddlewareBuilder(Web3MiddlewareBuilder):
|
|
|
82
82
|
async def async_request_processor(self, method: "RPCEndpoint", params: Any) -> Any:
|
|
83
83
|
if method not in self.skip_stalecheck_for_methods:
|
|
84
84
|
if not _is_fresh(self.cache["latest"], self.allowable_delay):
|
|
85
|
-
w3 = cast("AsyncWeb3", self._w3)
|
|
85
|
+
w3 = cast("AsyncWeb3[Any]", self._w3)
|
|
86
86
|
latest = await w3.eth.get_block("latest")
|
|
87
87
|
|
|
88
88
|
if _is_fresh(latest, self.allowable_delay):
|
web3/middleware/validation.py
CHANGED
|
@@ -121,7 +121,7 @@ def _chain_id_validator(web3_chain_id: int) -> Callable[..., Any]:
|
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
def _build_formatters_dict(
|
|
124
|
-
request_formatters: Dict[RPCEndpoint, Any]
|
|
124
|
+
request_formatters: Dict[RPCEndpoint, Any],
|
|
125
125
|
) -> FormattersDict:
|
|
126
126
|
return dict(
|
|
127
127
|
request_formatters=request_formatters,
|
|
@@ -149,7 +149,7 @@ def build_method_validators(w3: "Web3", method: RPCEndpoint) -> FormattersDict:
|
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
async def async_build_method_validators(
|
|
152
|
-
async_w3: "AsyncWeb3", method: RPCEndpoint
|
|
152
|
+
async_w3: "AsyncWeb3[Any]", method: RPCEndpoint
|
|
153
153
|
) -> FormattersDict:
|
|
154
154
|
request_formatters: Formatters = {}
|
|
155
155
|
if RPCEndpoint(method) in METHODS_TO_VALIDATE:
|
web3/module.py
CHANGED
|
@@ -60,7 +60,7 @@ TReturn = TypeVar("TReturn")
|
|
|
60
60
|
|
|
61
61
|
@curry
|
|
62
62
|
def retrieve_request_information_for_batching(
|
|
63
|
-
w3: Union["AsyncWeb3", "Web3"],
|
|
63
|
+
w3: Union["AsyncWeb3[Any]", "Web3"],
|
|
64
64
|
module: "Module",
|
|
65
65
|
method: Method[Callable[..., Any]],
|
|
66
66
|
) -> Union[
|
|
@@ -119,7 +119,7 @@ def retrieve_blocking_method_call_fn(
|
|
|
119
119
|
|
|
120
120
|
@curry
|
|
121
121
|
def retrieve_async_method_call_fn(
|
|
122
|
-
async_w3: "AsyncWeb3",
|
|
122
|
+
async_w3: "AsyncWeb3[Any]",
|
|
123
123
|
module: "Module",
|
|
124
124
|
method: Method[Callable[..., Any]],
|
|
125
125
|
) -> Callable[
|
|
@@ -167,7 +167,7 @@ def retrieve_async_method_call_fn(
|
|
|
167
167
|
class Module:
|
|
168
168
|
is_async = False
|
|
169
169
|
|
|
170
|
-
def __init__(self, w3: Union["AsyncWeb3", "Web3"]) -> None:
|
|
170
|
+
def __init__(self, w3: Union["AsyncWeb3[Any]", "Web3"]) -> None:
|
|
171
171
|
if self.is_async:
|
|
172
172
|
self.retrieve_caller_fn = retrieve_async_method_call_fn(w3, self)
|
|
173
173
|
else:
|
web3/providers/async_base.py
CHANGED
|
@@ -112,7 +112,7 @@ class AsyncBaseProvider:
|
|
|
112
112
|
return self._batching_context.get() is not None
|
|
113
113
|
|
|
114
114
|
async def request_func(
|
|
115
|
-
self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
|
|
115
|
+
self, async_w3: "AsyncWeb3[Any]", middleware_onion: MiddlewareOnion
|
|
116
116
|
) -> Callable[..., Coroutine[Any, Any, RPCResponse]]:
|
|
117
117
|
middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
|
|
118
118
|
|
|
@@ -129,7 +129,7 @@ class AsyncBaseProvider:
|
|
|
129
129
|
return self._request_func_cache[-1]
|
|
130
130
|
|
|
131
131
|
async def batch_request_func(
|
|
132
|
-
self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
|
|
132
|
+
self, async_w3: "AsyncWeb3[Any]", middleware_onion: MiddlewareOnion
|
|
133
133
|
) -> Callable[..., Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]]]:
|
|
134
134
|
middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
|
|
135
135
|
|
web3/providers/base.py
CHANGED
|
@@ -192,9 +192,9 @@ class JSONBaseProvider(BaseProvider):
|
|
|
192
192
|
# type ignore bc in order to wrap the method, we have to call
|
|
193
193
|
# `wrap_make_batch_request` with the accumulator_fn as the argument
|
|
194
194
|
# which breaks the type hinting for this particular case.
|
|
195
|
-
accumulator_fn = initialized.wrap_make_batch_request(
|
|
195
|
+
accumulator_fn = initialized.wrap_make_batch_request( # type: ignore
|
|
196
196
|
accumulator_fn
|
|
197
|
-
)
|
|
197
|
+
)
|
|
198
198
|
self._batch_request_func_cache = (middleware, accumulator_fn)
|
|
199
199
|
|
|
200
200
|
return self._batch_request_func_cache[-1]
|
|
@@ -116,7 +116,7 @@ def call_eth_tester(
|
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
def without_eth_tester(
|
|
119
|
-
fn: Callable[[TParams], TReturn]
|
|
119
|
+
fn: Callable[[TParams], TReturn],
|
|
120
120
|
) -> Callable[["EthereumTester", TParams], TReturn]:
|
|
121
121
|
# workaround for: https://github.com/pytoolz/cytoolz/issues/103
|
|
122
122
|
# @functools.wraps(fn)
|
|
@@ -127,7 +127,7 @@ def without_eth_tester(
|
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
def without_params(
|
|
130
|
-
fn: Callable[[TParams], TReturn]
|
|
130
|
+
fn: Callable[[TParams], TReturn],
|
|
131
131
|
) -> Callable[["EthereumTester", TParams], TReturn]:
|
|
132
132
|
# workaround for: https://github.com/pytoolz/cytoolz/issues/103
|
|
133
133
|
# @functools.wraps(fn)
|
|
@@ -82,7 +82,7 @@ class AsyncEthereumTesterProvider(AsyncBaseProvider):
|
|
|
82
82
|
self.api_endpoints = API_ENDPOINTS
|
|
83
83
|
|
|
84
84
|
async def request_func(
|
|
85
|
-
self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion"
|
|
85
|
+
self, async_w3: "AsyncWeb3[Any]", middleware_onion: "MiddlewareOnion"
|
|
86
86
|
) -> Callable[..., Coroutine[Any, Any, RPCResponse]]:
|
|
87
87
|
# override the request_func to add the ethereum_tester_middleware
|
|
88
88
|
|
|
@@ -376,7 +376,7 @@ def fill_default(
|
|
|
376
376
|
|
|
377
377
|
|
|
378
378
|
async def async_guess_from(
|
|
379
|
-
async_w3: "AsyncWeb3", _: TxParams
|
|
379
|
+
async_w3: "AsyncWeb3[Any]", _: TxParams
|
|
380
380
|
) -> Optional[ChecksumAddress]:
|
|
381
381
|
accounts = await async_w3.eth.accounts
|
|
382
382
|
if accounts is not None and len(accounts) > 0:
|
|
@@ -388,7 +388,7 @@ async def async_guess_from(
|
|
|
388
388
|
async def async_fill_default(
|
|
389
389
|
field: str,
|
|
390
390
|
guess_func: Callable[..., Any],
|
|
391
|
-
async_w3: "AsyncWeb3",
|
|
391
|
+
async_w3: "AsyncWeb3[Any]",
|
|
392
392
|
transaction: TxParams,
|
|
393
393
|
) -> TxParams:
|
|
394
394
|
# type ignored b/c TxParams keys must be string literal types
|
|
@@ -106,7 +106,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
106
106
|
# -- cached middleware request/response functions -- #
|
|
107
107
|
|
|
108
108
|
async def send_func(
|
|
109
|
-
self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion"
|
|
109
|
+
self, async_w3: "AsyncWeb3[Any]", middleware_onion: "MiddlewareOnion"
|
|
110
110
|
) -> Callable[..., Coroutine[Any, Any, RPCRequest]]:
|
|
111
111
|
"""
|
|
112
112
|
Cache the middleware chain for `send`.
|
|
@@ -130,7 +130,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
130
130
|
return self._send_func_cache[1]
|
|
131
131
|
|
|
132
132
|
async def recv_func(
|
|
133
|
-
self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion"
|
|
133
|
+
self, async_w3: "AsyncWeb3[Any]", middleware_onion: "MiddlewareOnion"
|
|
134
134
|
) -> Any:
|
|
135
135
|
"""
|
|
136
136
|
Cache and compose the middleware stack for `recv`.
|
|
@@ -156,7 +156,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
156
156
|
return self._recv_func_cache[1]
|
|
157
157
|
|
|
158
158
|
async def send_batch_func(
|
|
159
|
-
self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion"
|
|
159
|
+
self, async_w3: "AsyncWeb3[Any]", middleware_onion: "MiddlewareOnion"
|
|
160
160
|
) -> Callable[..., Coroutine[Any, Any, List[RPCRequest]]]:
|
|
161
161
|
middleware = middleware_onion.as_tuple_of_middleware()
|
|
162
162
|
cache_key = hash(tuple(id(mw) for mw in middleware))
|
|
@@ -164,7 +164,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
164
164
|
if cache_key != self._send_batch_func_cache[0]:
|
|
165
165
|
|
|
166
166
|
async def send_func(
|
|
167
|
-
requests: List[Tuple[RPCEndpoint, Any]]
|
|
167
|
+
requests: List[Tuple[RPCEndpoint, Any]],
|
|
168
168
|
) -> List[RPCRequest]:
|
|
169
169
|
for mw in middleware:
|
|
170
170
|
initialized = mw(async_w3)
|
|
@@ -179,7 +179,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
179
179
|
return self._send_batch_func_cache[1]
|
|
180
180
|
|
|
181
181
|
async def recv_batch_func(
|
|
182
|
-
self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion"
|
|
182
|
+
self, async_w3: "AsyncWeb3[Any]", middleware_onion: "MiddlewareOnion"
|
|
183
183
|
) -> Callable[..., Coroutine[Any, Any, List[RPCResponse]]]:
|
|
184
184
|
middleware = middleware_onion.as_tuple_of_middleware()
|
|
185
185
|
cache_key = hash(tuple(id(mw) for mw in middleware))
|
|
@@ -376,11 +376,12 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
376
376
|
) -> None:
|
|
377
377
|
# Puts a `TaskNotRunning` in appropriate queues to signal the end of the
|
|
378
378
|
# listener task to any listeners relying on the queues.
|
|
379
|
+
message = "Message listener task has ended."
|
|
379
380
|
self._request_processor._subscription_response_queue.put_nowait(
|
|
380
|
-
TaskNotRunning(message_listener_task)
|
|
381
|
+
TaskNotRunning(message_listener_task, message=message)
|
|
381
382
|
)
|
|
382
383
|
self._request_processor._handler_subscription_queue.put_nowait(
|
|
383
|
-
TaskNotRunning(message_listener_task)
|
|
384
|
+
TaskNotRunning(message_listener_task, message=message)
|
|
384
385
|
)
|
|
385
386
|
|
|
386
387
|
def _raise_stray_errors_from_cache(self) -> None:
|
|
@@ -30,7 +30,7 @@ class PersistentConnection:
|
|
|
30
30
|
via a `AsyncWeb3` instance instantiated with a `PersistentConnectionProvider` class.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
def __init__(self, w3: "AsyncWeb3"):
|
|
33
|
+
def __init__(self, w3: "AsyncWeb3[Any]"):
|
|
34
34
|
self._manager = w3.manager
|
|
35
35
|
self.provider = cast("PersistentConnectionProvider", self._manager.provider)
|
|
36
36
|
|
|
@@ -5,6 +5,7 @@ from typing import (
|
|
|
5
5
|
Any,
|
|
6
6
|
List,
|
|
7
7
|
Sequence,
|
|
8
|
+
Set,
|
|
8
9
|
Union,
|
|
9
10
|
cast,
|
|
10
11
|
overload,
|
|
@@ -15,6 +16,7 @@ from eth_typing import (
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
from web3.exceptions import (
|
|
19
|
+
SubscriptionHandlerTaskException,
|
|
18
20
|
SubscriptionProcessingFinished,
|
|
19
21
|
TaskNotRunning,
|
|
20
22
|
Web3TypeError,
|
|
@@ -50,19 +52,26 @@ class SubscriptionManager:
|
|
|
50
52
|
logger: logging.Logger = logging.getLogger(
|
|
51
53
|
"web3.providers.persistent.subscription_manager"
|
|
52
54
|
)
|
|
53
|
-
total_handler_calls: int = 0
|
|
54
55
|
|
|
55
|
-
def __init__(self, w3: "AsyncWeb3") -> None:
|
|
56
|
+
def __init__(self, w3: "AsyncWeb3[Any]") -> None:
|
|
56
57
|
self._w3 = w3
|
|
57
58
|
self._provider = cast("PersistentConnectionProvider", w3.provider)
|
|
58
59
|
self._subscription_container = SubscriptionContainer()
|
|
59
60
|
|
|
61
|
+
# parallelize all subscription handler calls
|
|
62
|
+
self.parallelize = False
|
|
63
|
+
self.task_timeout = 1
|
|
64
|
+
# TODO: can remove quotes from type hints once Python 3.8 support is dropped
|
|
65
|
+
self._tasks: Set["asyncio.Task[None]"] = set()
|
|
66
|
+
|
|
60
67
|
# share the subscription container with the request processor so it can separate
|
|
61
68
|
# subscriptions into different queues based on ``sub._handler`` presence
|
|
62
69
|
self._provider._request_processor._subscription_container = (
|
|
63
70
|
self._subscription_container
|
|
64
71
|
)
|
|
65
72
|
|
|
73
|
+
self.total_handler_calls: int = 0
|
|
74
|
+
|
|
66
75
|
def _add_subscription(self, subscription: EthSubscription[Any]) -> None:
|
|
67
76
|
self._subscription_container.add_subscription(subscription)
|
|
68
77
|
|
|
@@ -86,6 +95,35 @@ class SubscriptionManager:
|
|
|
86
95
|
f"labels.\n label: {subscription._label}"
|
|
87
96
|
)
|
|
88
97
|
|
|
98
|
+
# TODO: can remove quotes from type hints once Python 3.8 support is dropped
|
|
99
|
+
def _handler_task_callback(self, task: "asyncio.Task[None]") -> None:
|
|
100
|
+
"""
|
|
101
|
+
Callback when a handler task completes. Similar to _message_listener_callback.
|
|
102
|
+
Puts handler exceptions into the queue to be raised in the main loop, else
|
|
103
|
+
removes the task from the set of active tasks.
|
|
104
|
+
"""
|
|
105
|
+
if task.done() and not task.cancelled():
|
|
106
|
+
try:
|
|
107
|
+
task.result()
|
|
108
|
+
self._tasks.discard(task)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
self.logger.exception("Subscription handler task raised an exception.")
|
|
111
|
+
self._provider._request_processor._handler_subscription_queue.put_nowait( # noqa: E501
|
|
112
|
+
SubscriptionHandlerTaskException(task, message=str(e))
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
async def _cleanup_remaining_tasks(self) -> None:
|
|
116
|
+
"""Cancel and clean up all remaining tasks."""
|
|
117
|
+
if not self._tasks:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
self.logger.debug("Cleaning up %d remaining tasks...", len(self._tasks))
|
|
121
|
+
for task in self._tasks:
|
|
122
|
+
if not task.done():
|
|
123
|
+
task.cancel()
|
|
124
|
+
|
|
125
|
+
self._tasks.clear()
|
|
126
|
+
|
|
89
127
|
@property
|
|
90
128
|
def subscriptions(self) -> List[EthSubscription[Any]]:
|
|
91
129
|
return self._subscription_container.subscriptions
|
|
@@ -281,14 +319,23 @@ class SubscriptionManager:
|
|
|
281
319
|
sub_id
|
|
282
320
|
)
|
|
283
321
|
if sub:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
**sub._handler_context,
|
|
290
|
-
)
|
|
322
|
+
sub_context = EthSubscriptionContext(
|
|
323
|
+
self._w3,
|
|
324
|
+
sub,
|
|
325
|
+
formatted_sub_response["result"],
|
|
326
|
+
**sub._handler_context,
|
|
291
327
|
)
|
|
328
|
+
if sub.parallelize is True or (
|
|
329
|
+
sub.parallelize is None and self.parallelize
|
|
330
|
+
):
|
|
331
|
+
# run the handler in a task to allow parallel processing
|
|
332
|
+
task = asyncio.create_task(sub._handler(sub_context))
|
|
333
|
+
self._tasks.add(task)
|
|
334
|
+
task.add_done_callback(self._handler_task_callback)
|
|
335
|
+
else:
|
|
336
|
+
# await the handler in the main loop to ensure order
|
|
337
|
+
await sub._handler(sub_context)
|
|
338
|
+
|
|
292
339
|
except SubscriptionProcessingFinished:
|
|
293
340
|
if not run_forever:
|
|
294
341
|
self.logger.info(
|
|
@@ -296,14 +343,20 @@ class SubscriptionManager:
|
|
|
296
343
|
"Stopping subscription handling."
|
|
297
344
|
)
|
|
298
345
|
break
|
|
299
|
-
except
|
|
300
|
-
await asyncio.sleep(0)
|
|
301
|
-
self._provider._handle_listener_task_exceptions()
|
|
346
|
+
except SubscriptionHandlerTaskException:
|
|
302
347
|
self.logger.error(
|
|
303
|
-
"
|
|
304
|
-
"
|
|
348
|
+
"An exception occurred in a subscription handler task. "
|
|
349
|
+
"Stopping subscription handling."
|
|
305
350
|
)
|
|
351
|
+
await self._cleanup_remaining_tasks()
|
|
352
|
+
raise
|
|
353
|
+
except TaskNotRunning as e:
|
|
354
|
+
self.logger.error("Stopping subscription handling: %s", e.message)
|
|
355
|
+
self._provider._handle_listener_task_exceptions()
|
|
306
356
|
break
|
|
307
357
|
|
|
308
358
|
# no active handler subscriptions, clear the handler subscription queue
|
|
309
359
|
self._provider._request_processor._reset_handler_subscription_queue()
|
|
360
|
+
|
|
361
|
+
if self._tasks:
|
|
362
|
+
await self._cleanup_remaining_tasks()
|
|
@@ -26,7 +26,7 @@ def persistent_connection_provider_method(message: str = None) -> Callable[...,
|
|
|
26
26
|
|
|
27
27
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
28
28
|
@functools.wraps(func)
|
|
29
|
-
def inner(self: "AsyncWeb3", *args: Any, **kwargs: Any) -> Any:
|
|
29
|
+
def inner(self: "AsyncWeb3[Any]", *args: Any, **kwargs: Any) -> Any:
|
|
30
30
|
nonlocal message
|
|
31
31
|
if message is None:
|
|
32
32
|
message = (
|
web3/types.py
CHANGED
|
@@ -62,6 +62,19 @@ ABIElementIdentifier = Union[str, Type[FallbackFn], Type[ReceiveFn]]
|
|
|
62
62
|
|
|
63
63
|
# bytes, hexbytes, or hexstr representing a 32 byte hash
|
|
64
64
|
_Hash32 = Union[Hash32, HexBytes, HexStr]
|
|
65
|
+
|
|
66
|
+
# --- ``TopicFilter`` type for event log filtering with AND/OR patterns --- #
|
|
67
|
+
# - ``None``: wildcard, matches any value at this position
|
|
68
|
+
# - ``_Hash32``: single topic that must match exactly
|
|
69
|
+
# - ``Sequence[Union[None, _Hash32]]``: OR condition, at least one must match
|
|
70
|
+
# - ``Sequence[Sequence[...]]``: nested OR conditions
|
|
71
|
+
TopicFilter = Union[
|
|
72
|
+
None,
|
|
73
|
+
_Hash32,
|
|
74
|
+
Sequence[Union[None, _Hash32]],
|
|
75
|
+
Sequence["TopicFilter"],
|
|
76
|
+
]
|
|
77
|
+
|
|
65
78
|
EnodeURI = NewType("EnodeURI", str)
|
|
66
79
|
ENS = NewType("ENS", str)
|
|
67
80
|
Nonce = NewType("Nonce", int)
|
|
@@ -345,7 +358,7 @@ class FilterParams(TypedDict, total=False):
|
|
|
345
358
|
blockHash: HexBytes
|
|
346
359
|
fromBlock: BlockIdentifier
|
|
347
360
|
toBlock: BlockIdentifier
|
|
348
|
-
topics: Sequence[
|
|
361
|
+
topics: Sequence[TopicFilter]
|
|
349
362
|
|
|
350
363
|
|
|
351
364
|
class FeeHistory(TypedDict):
|
|
@@ -367,7 +380,7 @@ StateOverride = Dict[Union[str, Address, ChecksumAddress], StateOverrideParams]
|
|
|
367
380
|
|
|
368
381
|
|
|
369
382
|
GasPriceStrategy = Union[
|
|
370
|
-
Callable[["Web3", TxParams], Wei], Callable[["AsyncWeb3", TxParams], Wei]
|
|
383
|
+
Callable[["Web3", TxParams], Wei], Callable[["AsyncWeb3[Any]", TxParams], Wei]
|
|
371
384
|
]
|
|
372
385
|
|
|
373
386
|
|
|
@@ -667,4 +680,4 @@ class LogsSubscriptionArg(TypedDict, total=False):
|
|
|
667
680
|
ENS,
|
|
668
681
|
Sequence[Union[Address, ChecksumAddress, ENS]],
|
|
669
682
|
]
|
|
670
|
-
topics: Sequence[
|
|
683
|
+
topics: Sequence[TopicFilter]
|