5
5
"""
6
6
7
7
import asyncio
8
+ import inspect
8
9
import json
9
10
import random
10
11
from collections import defaultdict
@@ -1171,14 +1172,14 @@ async def _get_block_handler(
1171
1172
include_author : bool = False ,
1172
1173
header_only : bool = False ,
1173
1174
finalized_only : bool = False ,
1174
- subscription_handler : Optional [Callable ] = None ,
1175
+ subscription_handler : Optional [Callable [[ dict ], Awaitable [ Any ]] ] = None ,
1175
1176
):
1176
1177
try :
1177
1178
await self .init_runtime (block_hash = block_hash )
1178
1179
except BlockNotFound :
1179
1180
return None
1180
1181
1181
- async def decode_block (block_data , block_data_hash = None ):
1182
+ async def decode_block (block_data , block_data_hash = None ) -> dict [ str , Any ] :
1182
1183
if block_data :
1183
1184
if block_data_hash :
1184
1185
block_data ["header" ]["hash" ] = block_data_hash
@@ -1193,12 +1194,12 @@ async def decode_block(block_data, block_data_hash=None):
1193
1194
1194
1195
if "extrinsics" in block_data :
1195
1196
for idx , extrinsic_data in enumerate (block_data ["extrinsics" ]):
1196
- extrinsic_decoder = extrinsic_cls (
1197
- data = ScaleBytes (extrinsic_data ),
1198
- metadata = self .__metadata ,
1199
- runtime_config = self .runtime_config ,
1200
- )
1201
1197
try :
1198
+ extrinsic_decoder = extrinsic_cls (
1199
+ data = ScaleBytes (extrinsic_data ),
1200
+ metadata = self .__metadata ,
1201
+ runtime_config = self .runtime_config ,
1202
+ )
1202
1203
extrinsic_decoder .decode (check_remaining = True )
1203
1204
block_data ["extrinsics" ][idx ] = extrinsic_decoder
1204
1205
@@ -1314,23 +1315,29 @@ async def decode_block(block_data, block_data_hash=None):
1314
1315
if callable (subscription_handler ):
1315
1316
rpc_method_prefix = "Finalized" if finalized_only else "New"
1316
1317
1317
- async def result_handler (message , update_nr , subscription_id ):
1318
- new_block = await decode_block ({"header" : message ["params" ]["result" ]})
1318
+ async def result_handler (
1319
+ message : dict , subscription_id : str
1320
+ ) -> tuple [Any , bool ]:
1321
+ reached = False
1322
+ subscription_result = None
1323
+ if "params" in message :
1324
+ new_block = await decode_block (
1325
+ {"header" : message ["params" ]["result" ]}
1326
+ )
1319
1327
1320
- subscription_result = subscription_handler (
1321
- new_block , update_nr , subscription_id
1322
- )
1328
+ subscription_result = await subscription_handler (new_block )
1323
1329
1324
- if subscription_result is not None :
1325
- # Handler returned end result: unsubscribe from further updates
1326
- self ._forgettable_task = asyncio .create_task (
1327
- self .rpc_request (
1328
- f"chain_unsubscribe{ rpc_method_prefix } Heads" ,
1329
- [subscription_id ],
1330
+ if subscription_result is not None :
1331
+ reached = True
1332
+ # Handler returned end result: unsubscribe from further updates
1333
+ self ._forgettable_task = asyncio .create_task (
1334
+ self .rpc_request (
1335
+ f"chain_unsubscribe{ rpc_method_prefix } Heads" ,
1336
+ [subscription_id ],
1337
+ )
1330
1338
)
1331
- )
1332
1339
1333
- return subscription_result
1340
+ return subscription_result , reached
1334
1341
1335
1342
result = await self ._make_rpc_request (
1336
1343
[
@@ -1343,7 +1350,7 @@ async def result_handler(message, update_nr, subscription_id):
1343
1350
result_handler = result_handler ,
1344
1351
)
1345
1352
1346
- return result
1353
+ return result [ "_get_block_handler" ][ - 1 ]
1347
1354
1348
1355
else :
1349
1356
if header_only :
@@ -2770,3 +2777,49 @@ async def close(self):
2770
2777
await self .ws .shutdown ()
2771
2778
except AttributeError :
2772
2779
pass
2780
+
2781
+ async def wait_for_block (
2782
+ self ,
2783
+ block : int ,
2784
+ result_handler : Callable [[dict ], Awaitable [Any ]],
2785
+ task_return : bool = True ,
2786
+ ) -> Union [asyncio .Task , Union [bool , Any ]]:
2787
+ """
2788
+ Executes the result_handler when the chain has reached the block specified.
2789
+
2790
+ Args:
2791
+ block: block number
2792
+ result_handler: coroutine executed upon reaching the block number. This can be basically anything, but
2793
+ must accept one single arg, a dict with the block data; whether you use this data or not is entirely
2794
+ up to you.
2795
+ task_return: True to immediately return the result of wait_for_block as an asyncio Task, False to wait
2796
+ for the block to be reached, and return the result of the result handler.
2797
+
2798
+ Returns:
2799
+ Either an asyncio.Task (which contains the running subscription, and whose `result()` will contain the
2800
+ return of the result_handler), or the result itself, depending on `task_return` flag.
2801
+ Note that if your result_handler returns `None`, this method will return `True`, otherwise
2802
+ the return will be the result of your result_handler.
2803
+ """
2804
+
2805
+ async def _handler (block_data : dict [str , Any ]):
2806
+ required_number = block
2807
+ number = block_data ["header" ]["number" ]
2808
+ if number >= required_number :
2809
+ return (
2810
+ r if (r := await result_handler (block_data )) is not None else True
2811
+ )
2812
+
2813
+ args = inspect .getfullargspec (result_handler ).args
2814
+ if len (args ) != 1 :
2815
+ raise ValueError (
2816
+ "result_handler must take exactly one arg: the dict block data."
2817
+ )
2818
+
2819
+ co = self ._get_block_handler (
2820
+ self .last_block_hash , subscription_handler = _handler
2821
+ )
2822
+ if task_return is True :
2823
+ return asyncio .create_task (co )
2824
+ else :
2825
+ return await co
0 commit comments