From 8a3bafb34601577a71f8a26adedfb32e2c7a0cfc Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Thu, 22 May 2025 13:35:04 -0400 Subject: [PATCH 1/2] Algorand 4.1.x API updates support, includes enhanced Application Box search. --- algosdk/v2client/algod.py | 30 +++++++++++++++---- algosdk/v2client/indexer.py | 5 ++++ algosdk/v2client/models/application_params.py | 29 ++++++++++++++++++ tests/steps/account_v2_steps.py | 9 ++++++ tests/steps/application_v2_steps.py | 6 ++-- 5 files changed, 71 insertions(+), 8 deletions(-) diff --git a/algosdk/v2client/algod.py b/algosdk/v2client/algod.py index 7f621057..e4dcb586 100644 --- a/algosdk/v2client/algod.py +++ b/algosdk/v2client/algod.py @@ -194,21 +194,41 @@ def application_box_by_name( return self.algod_request("GET", req, params=params, **kwargs) def application_boxes( - self, application_id: int, limit: int = 0, **kwargs: Any + self, + application_id: int, + limit: int = 0, + prefix: Optional[str] = None, + next: Optional[str] = None, + values: Optional[bool] = False, + **kwargs: Any, ) -> AlgodResponseType: """ - Given an application ID, return all Box names. No particular ordering is guaranteed. Request fails when client or server-side configured limits prevent returning all Box names. - + Given an application ID, return boxes in lexographical order by name. If the results must be truncated, a next-token is supplied to continue the request. NOTE: box names are returned as base64-encoded strings. Args: application_id (int): The ID of the application to look up. limit (int, optional): Max number of box names to return. If max is not set, or max == 0, returns all box-names up to the maximum configured by the algod server being queried. + prefix (str, optional): A box name prefix, in the goal app + call arg form 'encoding:value'. For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. + For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'. + next (str, optional): A box name, in the goal app call arg + form 'encoding:value'. When provided, the returned boxes begin (lexographically) with the supplied name. Callers may + implement pagination by reinvoking the endpoint with the token from a previous call's next-token. + values (bool, optional): If true, box values will be returned. """ + query = {} + if limit: + query["max"] = limit + if prefix: + query["prefix"] = prefix + if next: + query["next"] = next + if values: + query["values"] = "true" req = "/applications/" + str(application_id) + "/boxes" - params = {"max": limit} if limit else {} - return self.algod_request("GET", req, params=params, **kwargs) + return self.algod_request("GET", req, params=query, **kwargs) def account_asset_info( self, address: str, asset_id: int, **kwargs: Any diff --git a/algosdk/v2client/indexer.py b/algosdk/v2client/indexer.py index 59d305bd..9c3e4601 100644 --- a/algosdk/v2client/indexer.py +++ b/algosdk/v2client/indexer.py @@ -106,6 +106,7 @@ def accounts( round_num=None, include_all=False, exclude=None, + online_only=False, **kwargs ): """ @@ -144,6 +145,8 @@ def accounts( application local data stored for this account, asset parameters created by this account, and application parameters created by this account. + online_only (bool, optional): return only accounts whose participation + status is currently online. Defaults to false. """ req = "/accounts" query = dict() @@ -166,6 +169,8 @@ def accounts( query["include-all"] = include_all if exclude: query["exclude"] = exclude + if online_only: + query["online-only"] = "true" return self.indexer_request("GET", req, query, **kwargs) def asset_balances( diff --git a/algosdk/v2client/models/application_params.py b/algosdk/v2client/models/application_params.py index 774c9802..d9c6c179 100644 --- a/algosdk/v2client/models/application_params.py +++ b/algosdk/v2client/models/application_params.py @@ -20,6 +20,7 @@ class ApplicationParams(object): "local_state_schema": "ApplicationStateSchema", "global_state_schema": "ApplicationStateSchema", "global_state": "list[TealKeyValue]", + "version": "int", } attribute_map = { @@ -29,6 +30,7 @@ class ApplicationParams(object): "local_state_schema": "local-state-schema", "global_state_schema": "global-state-schema", "global_state": "global-state", + "version": "version", } def __init__( @@ -39,6 +41,7 @@ def __init__( local_state_schema=None, global_state_schema=None, global_state=None, + version=None, ): # noqa: E501 """ApplicationParams - a model defined in OpenAPI""" # noqa: E501 @@ -48,6 +51,7 @@ def __init__( self._local_state_schema = None self._global_state_schema = None self._global_state = None + self._version = None self.creator = creator self.approval_program = approval_program @@ -58,6 +62,8 @@ def __init__( self.global_state_schema = global_state_schema if global_state is not None: self.global_state = global_state + if version is not None: + self.version = version @property def creator(self): @@ -193,6 +199,29 @@ def global_state(self, global_state): self._global_state = global_state + @property + def version(self): + """Gets the version of this Application programs. # noqa: E501 + + Represents the number of updates to the application programs. # noqa: E501 + + :return: The version of this Application programs. # noqa: E501 + :rtype: int + """ + return self._version + + @version.setter + def version(self, version): + """Sets the version of this Application programs. + + Represents the number of updates to the application programs. # noqa: E501 + + :param version: The the version of this Application programs. # noqa: E501 + :type version: int + """ + + self._version = version + def dictify(self): """Returns the model properties as a dict""" result = {} diff --git a/tests/steps/account_v2_steps.py b/tests/steps/account_v2_steps.py index 892b3bd7..1347446a 100644 --- a/tests/steps/account_v2_steps.py +++ b/tests/steps/account_v2_steps.py @@ -177,6 +177,15 @@ def search_accounts( ) +@when( + 'we make a Search Accounts call with onlineOnly "{onlineOnly:MaybeBool}"' +) +def search_accounts_online_only(context, onlineOnly): + context.response = context.icl.accounts( + online_only=onlineOnly, + ) + + @when( 'we make a Search Accounts call with assetID {index} limit {limit} currencyGreaterThan {currencyGreaterThan} currencyLessThan {currencyLessThan} round {block} and authenticating address "{authAddr:MaybeString}"' ) diff --git a/tests/steps/application_v2_steps.py b/tests/steps/application_v2_steps.py index 885e81b6..8ccb671d 100644 --- a/tests/steps/application_v2_steps.py +++ b/tests/steps/application_v2_steps.py @@ -181,11 +181,11 @@ def application_box_by_name(context, app_id, box_name): @when( - "we make a GetApplicationBoxes call for applicationID {app_id} with max {max_results}" + 'we make a GetApplicationBoxes call for applicationID {app_id} with max {max_results} prefix "{prefix:MaybeString}" next "{next:MaybeString}" values "{values:MaybeBool}"' ) -def application_boxes(context, app_id, max_results): +def application_boxes(context, app_id, max_results, prefix, next, values): context.response = context.acl.application_boxes( - app_id, limit=int(max_results) + app_id, limit=int(max_results), prefix=prefix, next=next, values=values ) From dbd0905e29da757074cb8404f8106043540da071 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Thu, 22 May 2025 13:42:10 -0400 Subject: [PATCH 2/2] Type assignment. --- algosdk/v2client/algod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algosdk/v2client/algod.py b/algosdk/v2client/algod.py index e4dcb586..69ab2f34 100644 --- a/algosdk/v2client/algod.py +++ b/algosdk/v2client/algod.py @@ -218,7 +218,7 @@ def application_boxes( implement pagination by reinvoking the endpoint with the token from a previous call's next-token. values (bool, optional): If true, box values will be returned. """ - query = {} + query: Dict[str, Union[int, str]] = {} if limit: query["max"] = limit if prefix: