From a3e1c8b88669cd148d51d3856afd2e9fdf8a8c9b Mon Sep 17 00:00:00 2001 From: Tristan Menzel Date: Fri, 5 Apr 2024 13:36:47 -0700 Subject: [PATCH] fix: Ensure clients generated for contracts with no abi methods are still valid python --- examples/minimal/application.json | 38 ++ examples/minimal/client.py | 490 ++++++++++++++++++++++ examples/minimal/minimal.py | 12 + examples/minimal/test_client.py | 22 + scripts/update_approvals.py | 2 +- src/algokit_client_generator/generator.py | 11 +- tests/test_generator.py | 2 +- 7 files changed, 569 insertions(+), 8 deletions(-) create mode 100644 examples/minimal/application.json create mode 100644 examples/minimal/client.py create mode 100644 examples/minimal/minimal.py create mode 100644 examples/minimal/test_client.py diff --git a/examples/minimal/application.json b/examples/minimal/application.json new file mode 100644 index 0000000..544791b --- /dev/null +++ b/examples/minimal/application.json @@ -0,0 +1,38 @@ +{ + "hints": {}, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMQp0eG4gTnVtQXBwQXJncwppbnRjXzAgLy8gMAo9PQpibnogbWFpbl9sMgplcnIKbWFpbl9sMjoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQpibnogbWFpbl9sOAp0eG4gT25Db21wbGV0aW9uCnB1c2hpbnQgNCAvLyBVcGRhdGVBcHBsaWNhdGlvbgo9PQpibnogbWFpbl9sNwp0eG4gT25Db21wbGV0aW9uCnB1c2hpbnQgNSAvLyBEZWxldGVBcHBsaWNhdGlvbgo9PQpibnogbWFpbl9sNgplcnIKbWFpbl9sNjoKdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KYXNzZXJ0CmNhbGxzdWIgZGVsZXRlXzEKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDc6CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CmFzc2VydApjYWxsc3ViIHVwZGF0ZV8wCmludGNfMSAvLyAxCnJldHVybgptYWluX2w4Ogp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAo9PQphc3NlcnQKaW50Y18xIC8vIDEKcmV0dXJuCgovLyB1cGRhdGUKdXBkYXRlXzA6CnByb3RvIDAgMAp0eG4gU2VuZGVyCmdsb2JhbCBDcmVhdG9yQWRkcmVzcwo9PQovLyB1bmF1dGhvcml6ZWQKYXNzZXJ0CnB1c2hpbnQgVE1QTF9VUERBVEFCTEUgLy8gVE1QTF9VUERBVEFCTEUKLy8gQ2hlY2sgYXBwIGlzIHVwZGF0YWJsZQphc3NlcnQKcmV0c3ViCgovLyBkZWxldGUKZGVsZXRlXzE6CnByb3RvIDAgMAp0eG4gU2VuZGVyCmdsb2JhbCBDcmVhdG9yQWRkcmVzcwo9PQovLyB1bmF1dGhvcml6ZWQKYXNzZXJ0CnB1c2hpbnQgVE1QTF9ERUxFVEFCTEUgLy8gVE1QTF9ERUxFVEFCTEUKLy8gQ2hlY2sgYXBwIGlzIGRlbGV0YWJsZQphc3NlcnQKcmV0c3Vi", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "MinimalApp", + "methods": [], + "networks": {}, + "desc": "An app that has no abi methods" + }, + "bare_call_config": { + "delete_application": "CALL", + "no_op": "CREATE", + "update_application": "CALL" + } +} \ No newline at end of file diff --git a/examples/minimal/client.py b/examples/minimal/client.py new file mode 100644 index 0000000..5286032 --- /dev/null +++ b/examples/minimal/client.py @@ -0,0 +1,490 @@ +# flake8: noqa +# fmt: off +# mypy: disable-error-code="no-any-return, no-untyped-call, misc, type-arg" +# This file was automatically generated by algokit-client-generator. +# DO NOT MODIFY IT BY HAND. +# requires: algokit-utils@^1.2.0 +import base64 +import dataclasses +import decimal +import typing +from abc import ABC, abstractmethod + +import algokit_utils +import algosdk +from algosdk.v2client import models +from algosdk.atomic_transaction_composer import ( + AtomicTransactionComposer, + AtomicTransactionResponse, + SimulateAtomicTransactionResponse, + TransactionSigner, + TransactionWithSigner +) + +_APP_SPEC_JSON = r"""{ + "hints": {}, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMQp0eG4gTnVtQXBwQXJncwppbnRjXzAgLy8gMAo9PQpibnogbWFpbl9sMgplcnIKbWFpbl9sMjoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQpibnogbWFpbl9sOAp0eG4gT25Db21wbGV0aW9uCnB1c2hpbnQgNCAvLyBVcGRhdGVBcHBsaWNhdGlvbgo9PQpibnogbWFpbl9sNwp0eG4gT25Db21wbGV0aW9uCnB1c2hpbnQgNSAvLyBEZWxldGVBcHBsaWNhdGlvbgo9PQpibnogbWFpbl9sNgplcnIKbWFpbl9sNjoKdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KYXNzZXJ0CmNhbGxzdWIgZGVsZXRlXzEKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDc6CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CmFzc2VydApjYWxsc3ViIHVwZGF0ZV8wCmludGNfMSAvLyAxCnJldHVybgptYWluX2w4Ogp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAo9PQphc3NlcnQKaW50Y18xIC8vIDEKcmV0dXJuCgovLyB1cGRhdGUKdXBkYXRlXzA6CnByb3RvIDAgMAp0eG4gU2VuZGVyCmdsb2JhbCBDcmVhdG9yQWRkcmVzcwo9PQovLyB1bmF1dGhvcml6ZWQKYXNzZXJ0CnB1c2hpbnQgVE1QTF9VUERBVEFCTEUgLy8gVE1QTF9VUERBVEFCTEUKLy8gQ2hlY2sgYXBwIGlzIHVwZGF0YWJsZQphc3NlcnQKcmV0c3ViCgovLyBkZWxldGUKZGVsZXRlXzE6CnByb3RvIDAgMAp0eG4gU2VuZGVyCmdsb2JhbCBDcmVhdG9yQWRkcmVzcwo9PQovLyB1bmF1dGhvcml6ZWQKYXNzZXJ0CnB1c2hpbnQgVE1QTF9ERUxFVEFCTEUgLy8gVE1QTF9ERUxFVEFCTEUKLy8gQ2hlY2sgYXBwIGlzIGRlbGV0YWJsZQphc3NlcnQKcmV0c3Vi", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "MinimalApp", + "methods": [], + "networks": {}, + "desc": "An app that has no abi methods" + }, + "bare_call_config": { + "delete_application": "CALL", + "no_op": "CREATE", + "update_application": "CALL" + } +}""" +APP_SPEC = algokit_utils.ApplicationSpecification.from_json(_APP_SPEC_JSON) +_TReturn = typing.TypeVar("_TReturn") + + +class _ArgsBase(ABC, typing.Generic[_TReturn]): + @staticmethod + @abstractmethod + def method() -> str: + ... + + +_TArgs = typing.TypeVar("_TArgs", bound=_ArgsBase[typing.Any]) + + +@dataclasses.dataclass(kw_only=True) +class _TArgsHolder(typing.Generic[_TArgs]): + args: _TArgs + + +def _filter_none(value: dict | typing.Any) -> dict | typing.Any: + if isinstance(value, dict): + return {k: _filter_none(v) for k, v in value.items() if v is not None} + return value + + +def _as_dict(data: typing.Any, *, convert_all: bool = True) -> dict[str, typing.Any]: + if data is None: + return {} + if not dataclasses.is_dataclass(data): + raise TypeError(f"{data} must be a dataclass") + if convert_all: + result = dataclasses.asdict(data) + else: + result = {f.name: getattr(data, f.name) for f in dataclasses.fields(data)} + return _filter_none(result) + + +def _convert_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.TransactionParametersDict: + return typing.cast(algokit_utils.TransactionParametersDict, _as_dict(transaction_parameters)) + + +def _convert_call_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.OnCompleteCallParametersDict: + return typing.cast(algokit_utils.OnCompleteCallParametersDict, _as_dict(transaction_parameters)) + + +def _convert_create_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, + on_complete: algokit_utils.OnCompleteActionName, +) -> algokit_utils.CreateCallParametersDict: + result = typing.cast(algokit_utils.CreateCallParametersDict, _as_dict(transaction_parameters)) + on_complete_enum = on_complete.replace("_", " ").title().replace(" ", "") + "OC" + result["on_complete"] = getattr(algosdk.transaction.OnComplete, on_complete_enum) + return result + + +def _convert_deploy_args( + deploy_args: algokit_utils.DeployCallArgs | None, +) -> algokit_utils.ABICreateCallArgsDict | None: + if deploy_args is None: + return None + + deploy_args_dict = typing.cast(algokit_utils.ABICreateCallArgsDict, _as_dict(deploy_args)) + if isinstance(deploy_args, _TArgsHolder): + deploy_args_dict["args"] = _as_dict(deploy_args.args) + deploy_args_dict["method"] = deploy_args.args.method() + + return deploy_args_dict + + +@dataclasses.dataclass(kw_only=True) +class SimulateOptions: + allow_more_logs: bool = dataclasses.field(default=False) + allow_empty_signatures: bool = dataclasses.field(default=False) + extra_opcode_budget: int = dataclasses.field(default=0) + exec_trace_config: models.SimulateTraceConfig | None = dataclasses.field(default=None) + + +class Composer: + + def __init__(self, app_client: algokit_utils.ApplicationClient, atc: AtomicTransactionComposer): + self.app_client = app_client + self.atc = atc + + def build(self) -> AtomicTransactionComposer: + return self.atc + + def simulate(self, options: SimulateOptions | None = None) -> SimulateAtomicTransactionResponse: + request = models.SimulateRequest( + allow_more_logs=options.allow_more_logs, + allow_empty_signatures=options.allow_empty_signatures, + extra_opcode_budget=options.extra_opcode_budget, + exec_trace_config=options.exec_trace_config, + txn_groups=[] + ) if options else None + result = self.atc.simulate(self.app_client.algod_client, request) + return result + + def execute(self) -> AtomicTransactionResponse: + return self.app_client.execute_atc(self.atc) + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> "Composer": + """Adds a call to create an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + self.app_client.compose_create( + self.atc, + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return self + + def update_bare( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a calls to the update_application bare method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + self.app_client.compose_update( + self.atc, + call_abi_method=False, + transaction_parameters=_convert_transaction_parameters(transaction_parameters), + ) + return self + + def delete_bare( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a calls to the delete_application bare method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + self.app_client.compose_delete( + self.atc, + call_abi_method=False, + transaction_parameters=_convert_transaction_parameters(transaction_parameters), + ) + return self + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> "Composer": + """Adds a call to the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass""" + + self.app_client.compose_clear_state(self.atc, _convert_transaction_parameters(transaction_parameters), app_args) + return self + + +class MinimalAppClient: + """An app that has no abi methods + + A class for interacting with the MinimalApp app providing high productivity and + strongly typed methods to deploy and call the app""" + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account | None = None, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + """ + MinimalAppClient can be created with an app_id to interact with an existing application, alternatively + it can be created with a creator and indexer_client specified to find existing applications by name and creator. + + :param AlgodClient algod_client: AlgoSDK algod client + :param int app_id: The app_id of an existing application, to instead find the application by creator and name + use the creator and indexer_client parameters + :param str | Account creator: The address or Account of the app creator to resolve the app_id + :param IndexerClient indexer_client: AlgoSDK indexer client, only required if deploying or finding app_id by + creator and app name + :param AppLookup existing_deployments: + :param TransactionSigner | Account signer: Account or signer to use to sign transactions, if not specified and + creator was passed as an Account will use that. + :param str sender: Address to use as the sender for all transactions, will use the address associated with the + signer if not specified. + :param TemplateValueMapping template_values: Values to use for TMPL_* template variables, dictionary keys should + *NOT* include the TMPL_ prefix + :param str | None app_name: Name of application to use when deploying, defaults to name defined on the + Application Specification + """ + + self.app_spec = APP_SPEC + + # calling full __init__ signature, so ignoring mypy warning about overloads + self.app_client = algokit_utils.ApplicationClient( # type: ignore[call-overload, misc] + algod_client=algod_client, + app_spec=self.app_spec, + app_id=app_id, + creator=creator, + indexer_client=indexer_client, + existing_deployments=existing_deployments, + signer=signer, + sender=sender, + suggested_params=suggested_params, + template_values=template_values, + app_name=app_name, + ) + + @property + def algod_client(self) -> algosdk.v2client.algod.AlgodClient: + return self.app_client.algod_client + + @property + def app_id(self) -> int: + return self.app_client.app_id + + @app_id.setter + def app_id(self, value: int) -> None: + self.app_client.app_id = value + + @property + def app_address(self) -> str: + return self.app_client.app_address + + @property + def sender(self) -> str | None: + return self.app_client.sender + + @sender.setter + def sender(self, value: str) -> None: + self.app_client.sender = value + + @property + def signer(self) -> TransactionSigner | None: + return self.app_client.signer + + @signer.setter + def signer(self, value: TransactionSigner) -> None: + self.app_client.signer = value + + @property + def suggested_params(self) -> algosdk.transaction.SuggestedParams | None: + return self.app_client.suggested_params + + @suggested_params.setter + def suggested_params(self, value: algosdk.transaction.SuggestedParams | None) -> None: + self.app_client.suggested_params = value + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> algokit_utils.TransactionResponse: + """Creates an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + result = self.app_client.create( + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return result + + def update_bare( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.TransactionResponse: + """Calls the update_application bare method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + result = self.app_client.update( + call_abi_method=False, + transaction_parameters=_convert_transaction_parameters(transaction_parameters), + ) + return result + + def delete_bare( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.TransactionResponse: + """Calls the delete_application bare method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + result = self.app_client.delete( + call_abi_method=False, + transaction_parameters=_convert_transaction_parameters(transaction_parameters), + ) + return result + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> algokit_utils.TransactionResponse: + """Calls the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + return self.app_client.clear_state(_convert_transaction_parameters(transaction_parameters), app_args) + + def deploy( + self, + version: str | None = None, + *, + signer: TransactionSigner | None = None, + sender: str | None = None, + allow_update: bool | None = None, + allow_delete: bool | None = None, + on_update: algokit_utils.OnUpdate = algokit_utils.OnUpdate.Fail, + on_schema_break: algokit_utils.OnSchemaBreak = algokit_utils.OnSchemaBreak.Fail, + template_values: algokit_utils.TemplateValueMapping | None = None, + create_args: algokit_utils.DeployCallArgs | None = None, + update_args: algokit_utils.DeployCallArgs | None = None, + delete_args: algokit_utils.DeployCallArgs | None = None, + ) -> algokit_utils.DeployResponse: + """Deploy an application and update client to reference it. + + Idempotently deploy (create, update/delete if changed) an app against the given name via the given creator + account, including deploy-time template placeholder substitutions. + To understand the architecture decisions behind this functionality please see + + + ```{note} + If there is a breaking state schema change to an existing app (and `on_schema_break` is set to + 'ReplaceApp' the existing app will be deleted and re-created. + ``` + + ```{note} + If there is an update (different TEAL code) to an existing app (and `on_update` is set to 'ReplaceApp') + the existing app will be deleted and re-created. + ``` + + :param str version: version to use when creating or updating app, if None version will be auto incremented + :param algosdk.atomic_transaction_composer.TransactionSigner signer: signer to use when deploying app + , if None uses self.signer + :param str sender: sender address to use when deploying app, if None uses self.sender + :param bool allow_delete: Used to set the `TMPL_DELETABLE` template variable to conditionally control if an app + can be deleted + :param bool allow_update: Used to set the `TMPL_UPDATABLE` template variable to conditionally control if an app + can be updated + :param OnUpdate on_update: Determines what action to take if an application update is required + :param OnSchemaBreak on_schema_break: Determines what action to take if an application schema requirements + has increased beyond the current allocation + :param dict[str, int|str|bytes] template_values: Values to use for `TMPL_*` template variables, dictionary keys + should *NOT* include the TMPL_ prefix + :param algokit_utils.DeployCallArgs | None create_args: Arguments used when creating an application + :param algokit_utils.DeployCallArgs | None update_args: Arguments used when updating an application + :param algokit_utils.DeployCallArgs | None delete_args: Arguments used when deleting an application + :return DeployResponse: details action taken and relevant transactions + :raises DeploymentError: If the deployment failed""" + + return self.app_client.deploy( + version, + signer=signer, + sender=sender, + allow_update=allow_update, + allow_delete=allow_delete, + on_update=on_update, + on_schema_break=on_schema_break, + template_values=template_values, + create_args=_convert_deploy_args(create_args), + update_args=_convert_deploy_args(update_args), + delete_args=_convert_deploy_args(delete_args), + ) + + def compose(self, atc: AtomicTransactionComposer | None = None) -> Composer: + return Composer(self.app_client, atc or AtomicTransactionComposer()) diff --git a/examples/minimal/minimal.py b/examples/minimal/minimal.py new file mode 100644 index 0000000..2f0857e --- /dev/null +++ b/examples/minimal/minimal.py @@ -0,0 +1,12 @@ +import beaker + +from examples.deployment_standard import ( + deploy_time_immutability_control, + deploy_time_permanence_control, +) + +app = ( + beaker.Application("MinimalApp", descr="An app that has no abi methods") + .apply(deploy_time_immutability_control) + .apply(deploy_time_permanence_control) +) diff --git a/examples/minimal/test_client.py b/examples/minimal/test_client.py new file mode 100644 index 0000000..8ccd862 --- /dev/null +++ b/examples/minimal/test_client.py @@ -0,0 +1,22 @@ +import pytest +from algokit_utils import OnUpdate, get_localnet_default_account +from algosdk.v2client.algod import AlgodClient +from algosdk.v2client.indexer import IndexerClient + +from examples.minimal.client import MinimalAppClient + + +@pytest.fixture(scope="session") +def minimal_client(algod_client: AlgodClient, indexer_client: IndexerClient) -> MinimalAppClient: + client = MinimalAppClient( + algod_client=algod_client, + indexer_client=indexer_client, + creator=get_localnet_default_account(algod_client), + ) + + client.deploy(allow_delete=True, allow_update=True, on_update=OnUpdate.UpdateApp) + return client + + +def test_delete(minimal_client: MinimalAppClient) -> None: + minimal_client.delete_bare() diff --git a/scripts/update_approvals.py b/scripts/update_approvals.py index d2041b8..1e9672f 100644 --- a/scripts/update_approvals.py +++ b/scripts/update_approvals.py @@ -5,7 +5,7 @@ def update_approvals() -> None: examples = pathlib.Path(__file__).parent.parent / "examples" - for app in ["helloworld", "lifecycle", "state", "voting"]: + for app in ["helloworld", "lifecycle", "minimal", "state", "voting"]: app_path = examples / app app_spec = app_path / "application.json" approved_path = app_path / "client.py" diff --git a/src/algokit_client_generator/generator.py b/src/algokit_client_generator/generator.py index 396c279..e1a243f 100644 --- a/src/algokit_client_generator/generator.py +++ b/src/algokit_client_generator/generator.py @@ -133,17 +133,16 @@ def helpers(context: GenerateContext) -> DocumentParts: has_abi_create = any(m.abi for m in context.methods.create) has_abi_update = any(m.abi for m in context.methods.update_application) has_abi_delete = any(m.abi for m in context.methods.delete_application) - if context.methods.has_abi_methods: - yield '_TReturn = typing.TypeVar("_TReturn")' - yield Part.Gap2 - yield utils.indented( - """ + yield '_TReturn = typing.TypeVar("_TReturn")' + yield Part.Gap2 + yield utils.indented( + """ class _ArgsBase(ABC, typing.Generic[_TReturn]): @staticmethod @abstractmethod def method() -> str: ...""" - ) + ) yield Part.Gap2 yield '_TArgs = typing.TypeVar("_TArgs", bound=_ArgsBase[typing.Any])' yield Part.Gap2 diff --git a/tests/test_generator.py b/tests/test_generator.py index 80fb39f..3a585f0 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -4,7 +4,7 @@ from algokit_client_generator import generate_client -@pytest.mark.parametrize("app", ["helloworld", "lifecycle", "state", "voting"]) +@pytest.mark.parametrize("app", ["helloworld", "lifecycle", "minimal", "state", "voting"]) def test_generate_clients(app: str) -> None: examples = pathlib.Path(__file__).parent.parent / "examples" app_path = examples / app