From b2cf95bdafb9cee73e1c3f2b9f8bc85d030ff82a Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Jul 2025 20:58:44 +0200 Subject: [PATCH 1/2] Added better typing --- async_substrate_interface/async_substrate.py | 56 +++++++++++--------- async_substrate_interface/sync_substrate.py | 45 +++++++++------- async_substrate_interface/types.py | 2 +- async_substrate_interface/utils/cache.py | 4 +- async_substrate_interface/utils/decoding.py | 2 +- async_substrate_interface/utils/storage.py | 6 +-- 6 files changed, 64 insertions(+), 51 deletions(-) diff --git a/async_substrate_interface/async_substrate.py b/async_substrate_interface/async_substrate.py index efe1985..ec6703a 100644 --- a/async_substrate_interface/async_substrate.py +++ b/async_substrate_interface/async_substrate.py @@ -689,7 +689,7 @@ async def _start_receiving(self): except ConnectionClosed: await self.connect(force=True) - async def send(self, payload: dict) -> int: + async def send(self, payload: dict) -> str: """ Sends a payload to the websocket connection. @@ -714,6 +714,7 @@ async def send(self, payload: dict) -> int: return original_id except (ConnectionClosed, ssl.SSLError, EOFError): await self.connect(force=True) + return await self.send(payload) async def retrieve(self, item_id: int) -> Optional[dict]: """ @@ -911,7 +912,7 @@ async def name(self): return self._name async def get_storage_item( - self, module: str, storage_function: str, block_hash: str = None + self, module: str, storage_function: str, block_hash: Optional[str] = None ): runtime = await self.init_runtime(block_hash=block_hash) metadata_pallet = runtime.metadata.get_metadata_pallet(module) @@ -1154,7 +1155,7 @@ async def create_storage_key( pallet: str, storage_function: str, params: Optional[list] = None, - block_hash: str = None, + block_hash: Optional[str] = None, ) -> StorageKey: """ Create a `StorageKey` instance providing storage function details. See `subscribe_storage()`. @@ -1169,7 +1170,7 @@ async def create_storage_key( StorageKey """ runtime = await self.init_runtime(block_hash=block_hash) - + params = params or [] return StorageKey.create_from_storage_function( pallet, storage_function, @@ -1424,7 +1425,7 @@ async def get_metadata_error( return error async def get_metadata_runtime_call_functions( - self, block_hash: str = None, runtime: Optional[Runtime] = None + self, block_hash: Optional[str] = None, runtime: Optional[Runtime] = None ) -> list[GenericRuntimeCallDefinition]: """ Get a list of available runtime API calls @@ -1763,7 +1764,7 @@ async def get_block_header( ignore_decoding_errors: bool = False, include_author: bool = False, finalized_only: bool = False, - ) -> dict: + ) -> Optional[dict]: """ Retrieves a block header and decodes its containing log digest items. If `block_hash` and `block_number` is omitted the chain tip will be retrieved, or the finalized head if `finalized_only` is set to true. @@ -1790,7 +1791,7 @@ async def get_block_header( block_hash = await self.get_block_hash(block_number) if block_hash is None: - return + return None if block_hash and finalized_only: raise ValueError( @@ -1820,7 +1821,7 @@ async def get_block_header( async def subscribe_block_headers( self, - subscription_handler: callable, + subscription_handler: Callable, ignore_decoding_errors: bool = False, include_author: bool = False, finalized_only=False, @@ -1902,7 +1903,7 @@ def retrieve_extrinsic_by_hash( ) async def get_extrinsics( - self, block_hash: str = None, block_number: int = None + self, block_hash: Optional[str] = None, block_number: Optional[int] = None ) -> Optional[list["AsyncExtrinsicReceipt"]]: """ Return all extrinsics for given block_hash or block_number @@ -2780,7 +2781,7 @@ async def create_signed_extrinsic( self, call: GenericCall, keypair: Keypair, - era: Optional[dict] = None, + era: Optional[Union[dict, str]] = None, nonce: Optional[int] = None, tip: int = 0, tip_asset_id: Optional[int] = None, @@ -2932,12 +2933,12 @@ async def _do_runtime_call_old( params: Optional[Union[list, dict]] = None, block_hash: Optional[str] = None, runtime: Optional[Runtime] = None, - ) -> ScaleType: + ) -> ScaleObj: logger.debug( f"Decoding old runtime call: {api}.{method} with params: {params} at block hash: {block_hash}" ) runtime_call_def = _TYPE_REGISTRY["runtime_api"][api]["methods"][method] - + params = params or [] # Encode params param_data = b"" @@ -3245,7 +3246,7 @@ async def get_payment_info( return result.value async def get_type_registry( - self, block_hash: str = None, max_recursion: int = 4 + self, block_hash: Optional[str] = None, max_recursion: int = 4 ) -> dict: """ Generates an exhaustive list of which RUST types exist in the runtime specified at given block_hash (or @@ -3284,7 +3285,7 @@ async def get_type_registry( return type_registry async def get_type_definition( - self, type_string: str, block_hash: str = None + self, type_string: str, block_hash: Optional[str] = None ) -> str: """ Retrieves SCALE encoding specifications of given type_string @@ -3589,11 +3590,11 @@ async def create_multisig_extrinsic( keypair: Keypair, multisig_account: MultiAccountId, max_weight: Optional[Union[dict, int]] = None, - era: dict = None, - nonce: int = None, + era: Optional[dict] = None, + nonce: Optional[int] = None, tip: int = 0, - tip_asset_id: int = None, - signature: Union[bytes, str] = None, + tip_asset_id: Optional[int] = None, + signature: Optional[Union[bytes, str]] = None, ) -> GenericExtrinsic: """ Create a Multisig extrinsic that will be signed by one of the signatories. Checks on-chain if the threshold @@ -3878,6 +3879,9 @@ async def get_block_number(self, block_hash: Optional[str] = None) -> int: elif "result" in response: if response["result"]: return int(response["result"]["number"], 16) + raise SubstrateRequestException( + f"Unable to retrieve block number for {block_hash}" + ) async def close(self): """ @@ -3973,14 +3977,14 @@ async def get_async_substrate_interface( """ substrate = AsyncSubstrateInterface( url, - use_remote_preset, - auto_discover, - ss58_format, - type_registry, - chain_name, - max_retries, - retry_timeout, - _mock, + use_remote_preset=use_remote_preset, + auto_discover=auto_discover, + ss58_format=ss58_format, + type_registry=type_registry, + chain_name=chain_name, + max_retries=max_retries, + retry_timeout=retry_timeout, + _mock=_mock, ) await substrate.initialize() return substrate diff --git a/async_substrate_interface/sync_substrate.py b/async_substrate_interface/sync_substrate.py index f6d1876..927caf6 100644 --- a/async_substrate_interface/sync_substrate.py +++ b/async_substrate_interface/sync_substrate.py @@ -641,7 +641,7 @@ def connect(self, init=False): raise ConnectionError(e) def get_storage_item( - self, module: str, storage_function: str, block_hash: str = None + self, module: str, storage_function: str, block_hash: Optional[str] = None ): self.init_runtime(block_hash=block_hash) metadata_pallet = self.runtime.metadata.get_metadata_pallet(module) @@ -659,7 +659,9 @@ def _get_current_block_hash( return self.last_block_hash return block_hash - def _load_registry_at_block(self, block_hash: Optional[str]) -> MetadataV15: + def _load_registry_at_block( + self, block_hash: Optional[str] + ) -> tuple[Optional[MetadataV15], Optional[PortableRegistry]]: # Should be called for any block that fails decoding. # Possibly the metadata was different. try: @@ -864,7 +866,7 @@ def create_storage_key( pallet: str, storage_function: str, params: Optional[list] = None, - block_hash: str = None, + block_hash: Optional[str] = None, ) -> StorageKey: """ Create a `StorageKey` instance providing storage function details. See `subscribe_storage()`. @@ -883,7 +885,7 @@ def create_storage_key( return StorageKey.create_from_storage_function( pallet, storage_function, - params, + params or [], runtime_config=self.runtime_config, metadata=self.runtime.metadata, ) @@ -1104,7 +1106,7 @@ def get_metadata_error(self, module_name, error_name, block_hash=None): return error def get_metadata_runtime_call_functions( - self, block_hash: str = None + self, block_hash: Optional[str] = None ) -> list[GenericRuntimeCallDefinition]: """ Get a list of available runtime API calls @@ -1124,7 +1126,7 @@ def get_metadata_runtime_call_functions( return call_functions def get_metadata_runtime_call_function( - self, api: str, method: str, block_hash: str = None + self, api: str, method: str, block_hash: Optional[str] = None ) -> GenericRuntimeCallDefinition: """ Get details of a runtime API call @@ -1416,7 +1418,7 @@ def get_block_header( ignore_decoding_errors: bool = False, include_author: bool = False, finalized_only: bool = False, - ) -> dict: + ) -> Optional[dict]: """ Retrieves a block header and decodes its containing log digest items. If `block_hash` and `block_number` is omitted the chain tip will be retrieved, or the finalized head if `finalized_only` is set to true. @@ -1473,7 +1475,7 @@ def get_block_header( def subscribe_block_headers( self, - subscription_handler: callable, + subscription_handler: Callable, ignore_decoding_errors: bool = False, include_author: bool = False, finalized_only=False, @@ -1555,7 +1557,7 @@ def retrieve_extrinsic_by_hash( ) def get_extrinsics( - self, block_hash: str = None, block_number: int = None + self, block_hash: str = None, block_number: Optional[int] = None ) -> Optional[list["ExtrinsicReceipt"]]: """ Return all extrinsics for given block_hash or block_number @@ -2349,7 +2351,7 @@ def create_signed_extrinsic( self, call: GenericCall, keypair: Keypair, - era: Optional[dict] = None, + era: Optional[Union[dict, str]] = None, nonce: Optional[int] = None, tip: int = 0, tip_asset_id: Optional[int] = None, @@ -2496,7 +2498,7 @@ def _do_runtime_call_old( method: str, params: Optional[Union[list, dict]] = None, block_hash: Optional[str] = None, - ) -> ScaleType: + ) -> ScaleObj: logger.debug( f"Decoding old runtime call: {api}.{method} with params: {params} at block hash: {block_hash}" ) @@ -2544,7 +2546,7 @@ def runtime_call( method: str, params: Optional[Union[list, dict]] = None, block_hash: Optional[str] = None, - ) -> ScaleType: + ) -> ScaleObj: """ Calls a runtime API method @@ -2770,7 +2772,9 @@ def get_payment_info(self, call: GenericCall, keypair: Keypair) -> dict[str, Any return result.value - def get_type_registry(self, block_hash: str = None, max_recursion: int = 4) -> dict: + def get_type_registry( + self, block_hash: Optional[str] = None, max_recursion: int = 4 + ) -> dict: """ Generates an exhaustive list of which RUST types exist in the runtime specified at given block_hash (or chaintip if block_hash is omitted) @@ -2807,7 +2811,9 @@ def get_type_registry(self, block_hash: str = None, max_recursion: int = 4) -> d return type_registry - def get_type_definition(self, type_string: str, block_hash: str = None) -> str: + def get_type_definition( + self, type_string: str, block_hash: Optional[str] = None + ) -> str: """ Retrieves SCALE encoding specifications of given type_string @@ -3052,11 +3058,11 @@ def create_multisig_extrinsic( keypair: Keypair, multisig_account: MultiAccountId, max_weight: Optional[Union[dict, int]] = None, - era: dict = None, - nonce: int = None, + era: Optional[dict] = None, + nonce: Optional[int] = None, tip: int = 0, - tip_asset_id: int = None, - signature: Union[bytes, str] = None, + tip_asset_id: Optional[int] = None, + signature: Optional[Union[bytes, str]] = None, ) -> GenericExtrinsic: """ Create a Multisig extrinsic that will be signed by one of the signatories. Checks on-chain if the threshold @@ -3333,6 +3339,9 @@ def get_block_number(self, block_hash: Optional[str] = None) -> int: elif "result" in response: if response["result"]: return int(response["result"]["number"], 16) + raise SubstrateRequestException( + f"Unable to determine block number for {block_hash}" + ) def close(self): """ diff --git a/async_substrate_interface/types.py b/async_substrate_interface/types.py index 95575bf..bcf2fe1 100644 --- a/async_substrate_interface/types.py +++ b/async_substrate_interface/types.py @@ -372,7 +372,7 @@ def __init__(self, payloads): self.responses = defaultdict(lambda: {"complete": False, "results": []}) self.payloads_count = len(payloads) - def add_request(self, item_id: int, request_id: Any): + def add_request(self, item_id: Union[int, str], request_id: Any): """ Adds an outgoing request to the responses map for later retrieval """ diff --git a/async_substrate_interface/utils/cache.py b/async_substrate_interface/utils/cache.py index 23bbf9f..cf40539 100644 --- a/async_substrate_interface/utils/cache.py +++ b/async_substrate_interface/utils/cache.py @@ -127,7 +127,7 @@ def inner(self, *args, **kwargs): return decorator -def async_sql_lru_cache(maxsize=None): +def async_sql_lru_cache(maxsize: Optional[int] = None): def decorator(func): @cached_fetcher(max_size=maxsize) async def inner(self, *args, **kwargs): @@ -283,7 +283,7 @@ def __get__(self, instance, owner): return self._instances[instance] -def cached_fetcher(max_size: int, cache_key_index: int = 0): +def cached_fetcher(max_size: Optional[int] = None, cache_key_index: int = 0): """Wrapper for CachedFetcher. See example in CachedFetcher docstring.""" def wrapper(method): diff --git a/async_substrate_interface/utils/decoding.py b/async_substrate_interface/utils/decoding.py index af8d969..1dc494a 100644 --- a/async_substrate_interface/utils/decoding.py +++ b/async_substrate_interface/utils/decoding.py @@ -160,7 +160,7 @@ def concat_hash_len(key_hasher: str) -> int: def legacy_scale_decode( - type_string: str, scale_bytes: Union[str, ScaleBytes], runtime: "Runtime" + type_string: str, scale_bytes: Union[str, bytes, ScaleBytes], runtime: "Runtime" ): if isinstance(scale_bytes, (str, bytes)): scale_bytes = ScaleBytes(scale_bytes) diff --git a/async_substrate_interface/utils/storage.py b/async_substrate_interface/utils/storage.py index 5778887..f697c8a 100644 --- a/async_substrate_interface/utils/storage.py +++ b/async_substrate_interface/utils/storage.py @@ -48,9 +48,9 @@ def create_from_data( data: bytes, runtime_config: RuntimeConfigurationObject, metadata: GenericMetadataVersioned, - value_scale_type: str = None, - pallet: str = None, - storage_function: str = None, + value_scale_type: Optional[str] = None, + pallet: Optional[str] = None, + storage_function: Optional[str] = None, ) -> "StorageKey": """ Create a StorageKey instance providing raw storage key bytes From eeabb539d2de26736e30c4ab4f5215387dad3270 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Jul 2025 21:03:23 +0200 Subject: [PATCH 2/2] Typing --- async_substrate_interface/async_substrate.py | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/async_substrate_interface/async_substrate.py b/async_substrate_interface/async_substrate.py index ec6703a..f609bfe 100644 --- a/async_substrate_interface/async_substrate.py +++ b/async_substrate_interface/async_substrate.py @@ -1015,7 +1015,7 @@ async def decode_scale( # Decode AccountId bytes to SS58 address return ss58_encode(scale_bytes, self.ss58_format) else: - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) if runtime.metadata_v15 is not None and force_legacy is False: obj = decode_by_type_string(type_string, runtime.registry, scale_bytes) @@ -1318,7 +1318,7 @@ async def get_metadata_storage_functions( Returns: list of storage functions """ - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) storage_list = [] @@ -1356,7 +1356,7 @@ async def get_metadata_storage_function( Returns: Metadata storage function """ - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) pallet = runtime.metadata.get_metadata_pallet(module_name) @@ -1377,7 +1377,7 @@ async def get_metadata_errors( Returns: list of errors in the metadata """ - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) error_list = [] @@ -1415,7 +1415,7 @@ async def get_metadata_error( error """ - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) for module_idx, module in enumerate(runtime.metadata.pallets): @@ -1433,7 +1433,7 @@ async def get_metadata_runtime_call_functions( Returns: list of runtime call functions """ - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) call_functions = [] @@ -1467,7 +1467,7 @@ async def get_metadata_runtime_call_function( Returns: GenericRuntimeCallDefinition """ - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) try: @@ -2142,7 +2142,7 @@ async def _preprocess( """ params = query_for if query_for else [] # Search storage call in metadata - if not runtime: + if runtime is None: runtime = self.runtime metadata_pallet = runtime.metadata.get_metadata_pallet(module) @@ -2504,7 +2504,7 @@ async def query_multiple( block_hash = await self._get_current_block_hash(block_hash, reuse_block_hash) if block_hash: self.last_block_hash = block_hash - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) preprocessed: tuple[Preprocessed] = await asyncio.gather( *[ @@ -2562,7 +2562,7 @@ async def query_multi( Returns: list of `(storage_key, scale_obj)` tuples """ - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) # Retrieve corresponding value @@ -2617,7 +2617,7 @@ async def create_scale_object( Returns: The created Scale Type object """ - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) if "metadata" not in kwargs: kwargs["metadata"] = runtime.metadata @@ -3160,7 +3160,7 @@ async def get_metadata_constant( Returns: MetadataModuleConstants """ - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) for module in runtime.metadata.pallets: @@ -3361,7 +3361,7 @@ async def query( block_hash = await self._get_current_block_hash(block_hash, reuse_block_hash) if block_hash: self.last_block_hash = block_hash - if not runtime: + if runtime is None: runtime = await self.init_runtime(block_hash=block_hash) preprocessed: Preprocessed = await self._preprocess( params,