From 2ee583ca18565453f5e30a20de936eeca31df7d6 Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Fri, 31 Jan 2025 20:10:48 +0100 Subject: [PATCH 1/8] fix: prevent pytest from collecting class starting with word Test* --- src/algokit_utils/clients/dispenser_api_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/algokit_utils/clients/dispenser_api_client.py b/src/algokit_utils/clients/dispenser_api_client.py index e471989e..4f262d63 100644 --- a/src/algokit_utils/clients/dispenser_api_client.py +++ b/src/algokit_utils/clients/dispenser_api_client.py @@ -71,6 +71,9 @@ class TestNetDispenserApiClient: Default request timeout is 15 seconds. Modify by passing `request_timeout` to the constructor. """ + # NOTE: ensures pytest does not think this is a test + # https://docs.pytest.org/en/stable/example/pythoncollection.html#customizing-test-collection + __test__ = False auth_token: str request_timeout = DISPENSER_REQUEST_TIMEOUT From 3e479be36dedc6f1eb25a7e6f279da3a4ec43dc8 Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Fri, 31 Jan 2025 20:11:20 +0100 Subject: [PATCH 2/8] docs: fix typo in a docstring --- src/algokit_utils/accounts/account_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algokit_utils/accounts/account_manager.py b/src/algokit_utils/accounts/account_manager.py index 693e96ea..363da650 100644 --- a/src/algokit_utils/accounts/account_manager.py +++ b/src/algokit_utils/accounts/account_manager.py @@ -256,7 +256,7 @@ def get_account(self, sender: str) -> TransactionSignerAccountProtocol: :raises ValueError: If no account is found or if the account is not a regular account :example: - >>> sender = account_manager.random() + >>> sender = account_manager.random().address >>> # ... >>> # Returns the `TransactionSignerAccountProtocol` for `sender` that has previously been registered >>> account = account_manager.get_account(sender) From db7d4f4476783fa9902c19e3ef5a4fe75d93bc4d Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Fri, 31 Jan 2025 20:12:21 +0100 Subject: [PATCH 3/8] fix: ensure runtime errors thrown are of type LogicError (if isinstance check is True) --- src/algokit_utils/applications/app_client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/algokit_utils/applications/app_client.py b/src/algokit_utils/applications/app_client.py index e0f6e186..fb5d898a 100644 --- a/src/algokit_utils/applications/app_client.py +++ b/src/algokit_utils/applications/app_client.py @@ -1682,18 +1682,22 @@ def get_line_for_pc(input_pc: int) -> int | None: get_line_for_pc=custom_get_line_for_pc, traces=None, ) - if error_message: import re message = e.logic_error_str if isinstance(e, LogicError) else str(e) app_id = re.search(r"(?<=app=)\d+", message) tx_id = re.search(r"(?<=transaction )\S+(?=:)", message) - error = Exception( + runtime_error_message = ( f"Runtime error when executing {app_spec.name} " f"(appId: {app_id.group() if app_id else 'N/A'}) in transaction " f"{tx_id.group() if tx_id else 'N/A'}: {error_message}" ) + if isinstance(e, LogicError): + e.message = runtime_error_message + error = e + else: + e = Exception(runtime_error_message) error.__cause__ = e return error From fa96a8d69ae09e2715af16c06e86bbe12404d89d Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Sat, 1 Feb 2025 00:39:06 +0100 Subject: [PATCH 4/8] chore: removing unused beaker dependency --- legacy_v2_tests/app_client_test.py | 1 + legacy_v2_tests/conftest.py | 4 -- poetry.lock | 75 +----------------------------- pyproject.toml | 1 - 4 files changed, 2 insertions(+), 79 deletions(-) diff --git a/legacy_v2_tests/app_client_test.py b/legacy_v2_tests/app_client_test.py index 34e04358..93b347de 100644 --- a/legacy_v2_tests/app_client_test.py +++ b/legacy_v2_tests/app_client_test.py @@ -1,3 +1,4 @@ +# type: ignore # noqa: PGH003 from typing import Literal import beaker diff --git a/legacy_v2_tests/conftest.py b/legacy_v2_tests/conftest.py index f8989eb8..ef046763 100644 --- a/legacy_v2_tests/conftest.py +++ b/legacy_v2_tests/conftest.py @@ -24,7 +24,6 @@ get_kmd_client_from_algod_client, replace_template_variables, ) -from legacy_v2_tests import app_client_test if TYPE_CHECKING: from algosdk.kmd import KMDClient @@ -156,9 +155,6 @@ def funded_account(algod_client: "AlgodClient") -> Account: @pytest.fixture(scope="session") def app_spec() -> ApplicationSpecification: - app_spec = app_client_test.app.build() - path = Path(__file__).parent / "app_client_test.json" - path.write_text(app_spec.to_json()) return read_spec("app_client_test.json", deletable=True, updatable=True, template_values={"VERSION": 1}) diff --git a/poetry.lock b/poetry.lock index 88a18c0d..40cfb15c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -76,21 +76,6 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] -[[package]] -name = "beaker-pyteal" -version = "1.1.1" -description = "A Framework for building PyTeal Applications" -optional = false -python-versions = ">=3.10,<4.0" -files = [ - {file = "beaker_pyteal-1.1.1-py3-none-any.whl", hash = "sha256:a85a4568213acbd097cd70d8acd71d0e7187b71a4dc5c1fd9760ae8c1433571e"}, -] - -[package.dependencies] -algokit-utils = ">=2.0.0,<3.0.0" -py-algorand-sdk = ">=2.0.0" -pyteal = ">=0.24,<0.25" - [[package]] name = "beautifulsoup4" version = "4.12.3" @@ -555,17 +540,6 @@ files = [ {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] -[[package]] -name = "docstring-parser" -version = "0.14.1" -description = "Parse Python docstrings in reST, Google and Numpydoc format" -optional = false -python-versions = ">=3.6,<4.0" -files = [ - {file = "docstring_parser-0.14.1-py3-none-any.whl", hash = "sha256:14ac6ec1f1ba6905c4d8cb90fd0bc55394f5678183752c90e44812bf28d7a515"}, - {file = "docstring_parser-0.14.1.tar.gz", hash = "sha256:2c77522e31b7c88b1ab457a1f3c9ae38947ad719732260ba77ee8a3deb58622a"}, -] - [[package]] name = "docstring-parser-fork" version = "0.0.12" @@ -627,20 +601,6 @@ files = [ [package.extras] testing = ["hatch", "pre-commit", "pytest", "tox"] -[[package]] -name = "executing" -version = "1.2.0" -description = "Get the currently executing AST node of a frame, and other information" -optional = false -python-versions = "*" -files = [ - {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, - {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, -] - -[package.extras] -tests = ["asttokens", "littleutils", "pytest", "rich"] - [[package]] name = "filelock" version = "3.17.0" @@ -1719,24 +1679,6 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] -[[package]] -name = "pyteal" -version = "0.24.1" -description = "Algorand Smart Contracts in Python" -optional = false -python-versions = ">=3.10" -files = [ - {file = "pyteal-0.24.1-py3-none-any.whl", hash = "sha256:19c601f0ea4d1a0be41a3fe48cd3807558a0e907cd47d0dca5df60977d78f2c4"}, - {file = "pyteal-0.24.1.tar.gz", hash = "sha256:172d796981f8f9d3a9a8fbe71a71a49cf185509780f46d82e29aaa692386d1fa"}, -] - -[package.dependencies] -docstring-parser = "0.14.1" -executing = "1.2.0" -py-algorand-sdk = ">=2.0.0,<3.0.0" -semantic-version = ">=2.9.0,<3.0.0" -tabulate = ">=0.9.0,<0.10.0" - [[package]] name = "pytest" version = "8.3.4" @@ -2117,21 +2059,6 @@ files = [ cryptography = ">=2.0" jeepney = ">=0.6" -[[package]] -name = "semantic-version" -version = "2.10.0" -description = "A library implementing the 'SemVer' scheme." -optional = false -python-versions = ">=2.7" -files = [ - {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, - {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, -] - -[package.extras] -dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] -doc = ["Sphinx", "sphinx-rtd-theme"] - [[package]] name = "semver" version = "2.13.0" @@ -2888,4 +2815,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "c831facca8536a1b7d018cd049682ee762ee374ed77ddec03c9a0acd62086b19" +content-hash = "fe358878f59fa576e63385a47abc9a76fb813bf22e584bfce569552ad6d331a8" diff --git a/pyproject.toml b/pyproject.toml index 278a5a01..a50a44fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,6 @@ pre-commit = "^3.4.0" python-dotenv = "^1.0.0" sphinx = "^8.0.0" poethepoet = ">=0.19,<0.26" -beaker-pyteal = "^1.1.1" pytest-httpx = "^0.35" pytest-xdist = "^3.6.1" linkify-it-py = "^2.0.3" From c3fe207766ce7a1f0e61dc321f69d727d5470190 Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Sat, 1 Feb 2025 01:06:39 +0100 Subject: [PATCH 5/8] chore: add additional AlgoAmount helpers from utils-ts --- legacy_v2_tests/app_client_test.py | 1 - legacy_v2_tests/conftest.py | 4 + .../test_app_client_template_values.py | 23 ------ poetry.lock | 75 ++++++++++++++++++- pyproject.toml | 1 + src/algokit_utils/models/amount.py | 35 ++++++++- tests/models/test_algo_amount.py | 9 ++- tests/test_debug_utils.py | 2 + 8 files changed, 123 insertions(+), 27 deletions(-) diff --git a/legacy_v2_tests/app_client_test.py b/legacy_v2_tests/app_client_test.py index 93b347de..34e04358 100644 --- a/legacy_v2_tests/app_client_test.py +++ b/legacy_v2_tests/app_client_test.py @@ -1,4 +1,3 @@ -# type: ignore # noqa: PGH003 from typing import Literal import beaker diff --git a/legacy_v2_tests/conftest.py b/legacy_v2_tests/conftest.py index ef046763..f8989eb8 100644 --- a/legacy_v2_tests/conftest.py +++ b/legacy_v2_tests/conftest.py @@ -24,6 +24,7 @@ get_kmd_client_from_algod_client, replace_template_variables, ) +from legacy_v2_tests import app_client_test if TYPE_CHECKING: from algosdk.kmd import KMDClient @@ -155,6 +156,9 @@ def funded_account(algod_client: "AlgodClient") -> Account: @pytest.fixture(scope="session") def app_spec() -> ApplicationSpecification: + app_spec = app_client_test.app.build() + path = Path(__file__).parent / "app_client_test.json" + path.write_text(app_spec.to_json()) return read_spec("app_client_test.json", deletable=True, updatable=True, template_values={"VERSION": 1}) diff --git a/legacy_v2_tests/test_app_client_template_values.py b/legacy_v2_tests/test_app_client_template_values.py index a01f53d9..ae12a964 100644 --- a/legacy_v2_tests/test_app_client_template_values.py +++ b/legacy_v2_tests/test_app_client_template_values.py @@ -146,26 +146,3 @@ def test_deploy_with_missing_template_values( client.deploy( allow_delete=True, allow_update=True, create_args=algokit_utils.ABICreateCallArgs(method="create") ) - - -def test_deploy_with_multi_underscore_template_value( - algod_client: "AlgodClient", - indexer_client: "IndexerClient", - funded_account: algokit_utils.Account, -) -> None: - from legacy_v2_tests.app_multi_underscore_template_var import app - - some_value = 123 - app_spec = app.build(algod_client) - client = algokit_utils.ApplicationClient( - algod_client, - app_spec, - creator=funded_account, - indexer_client=indexer_client, - app_name=get_unique_name(), - template_values={"SOME_VALUE": some_value}, - ) - - client.deploy(allow_update=True, allow_delete=True) - result = client.call("some_value") - assert result.return_value == some_value diff --git a/poetry.lock b/poetry.lock index 40cfb15c..88a18c0d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -76,6 +76,21 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] +[[package]] +name = "beaker-pyteal" +version = "1.1.1" +description = "A Framework for building PyTeal Applications" +optional = false +python-versions = ">=3.10,<4.0" +files = [ + {file = "beaker_pyteal-1.1.1-py3-none-any.whl", hash = "sha256:a85a4568213acbd097cd70d8acd71d0e7187b71a4dc5c1fd9760ae8c1433571e"}, +] + +[package.dependencies] +algokit-utils = ">=2.0.0,<3.0.0" +py-algorand-sdk = ">=2.0.0" +pyteal = ">=0.24,<0.25" + [[package]] name = "beautifulsoup4" version = "4.12.3" @@ -540,6 +555,17 @@ files = [ {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] +[[package]] +name = "docstring-parser" +version = "0.14.1" +description = "Parse Python docstrings in reST, Google and Numpydoc format" +optional = false +python-versions = ">=3.6,<4.0" +files = [ + {file = "docstring_parser-0.14.1-py3-none-any.whl", hash = "sha256:14ac6ec1f1ba6905c4d8cb90fd0bc55394f5678183752c90e44812bf28d7a515"}, + {file = "docstring_parser-0.14.1.tar.gz", hash = "sha256:2c77522e31b7c88b1ab457a1f3c9ae38947ad719732260ba77ee8a3deb58622a"}, +] + [[package]] name = "docstring-parser-fork" version = "0.0.12" @@ -601,6 +627,20 @@ files = [ [package.extras] testing = ["hatch", "pre-commit", "pytest", "tox"] +[[package]] +name = "executing" +version = "1.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = "*" +files = [ + {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, + {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, +] + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich"] + [[package]] name = "filelock" version = "3.17.0" @@ -1679,6 +1719,24 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyteal" +version = "0.24.1" +description = "Algorand Smart Contracts in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pyteal-0.24.1-py3-none-any.whl", hash = "sha256:19c601f0ea4d1a0be41a3fe48cd3807558a0e907cd47d0dca5df60977d78f2c4"}, + {file = "pyteal-0.24.1.tar.gz", hash = "sha256:172d796981f8f9d3a9a8fbe71a71a49cf185509780f46d82e29aaa692386d1fa"}, +] + +[package.dependencies] +docstring-parser = "0.14.1" +executing = "1.2.0" +py-algorand-sdk = ">=2.0.0,<3.0.0" +semantic-version = ">=2.9.0,<3.0.0" +tabulate = ">=0.9.0,<0.10.0" + [[package]] name = "pytest" version = "8.3.4" @@ -2059,6 +2117,21 @@ files = [ cryptography = ">=2.0" jeepney = ">=0.6" +[[package]] +name = "semantic-version" +version = "2.10.0" +description = "A library implementing the 'SemVer' scheme." +optional = false +python-versions = ">=2.7" +files = [ + {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, + {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, +] + +[package.extras] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] +doc = ["Sphinx", "sphinx-rtd-theme"] + [[package]] name = "semver" version = "2.13.0" @@ -2815,4 +2888,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "fe358878f59fa576e63385a47abc9a76fb813bf22e584bfce569552ad6d331a8" +content-hash = "c831facca8536a1b7d018cd049682ee762ee374ed77ddec03c9a0acd62086b19" diff --git a/pyproject.toml b/pyproject.toml index a50a44fd..278a5a01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ pre-commit = "^3.4.0" python-dotenv = "^1.0.0" sphinx = "^8.0.0" poethepoet = ">=0.19,<0.26" +beaker-pyteal = "^1.1.1" pytest-httpx = "^0.35" pytest-xdist = "^3.6.1" linkify-it-py = "^2.0.3" diff --git a/src/algokit_utils/models/amount.py b/src/algokit_utils/models/amount.py index 0baa0e03..85f21d42 100644 --- a/src/algokit_utils/models/amount.py +++ b/src/algokit_utils/models/amount.py @@ -5,7 +5,7 @@ import algosdk from typing_extensions import Self -__all__ = ["AlgoAmount"] +__all__ = ["ALGORAND_MIN_TX_FEE", "AlgoAmount", "algo", "micro_algo", "transaction_fees"] class AlgoAmount: @@ -196,3 +196,36 @@ def __isub__(self, other: AlgoAmount) -> Self: else: raise TypeError(f"Unsupported operand type(s) for -: 'AlgoAmount' and '{type(other).__name__}'") return self + + +# Helper functions +def algo(algos: int) -> AlgoAmount: + """Create an AlgoAmount object representing the given number of Algo. + + :param int algos: The number of Algo to create an AlgoAmount object for. + :return AlgoAmount: An AlgoAmount object representing the given number of Algo. + """ + return AlgoAmount.from_algos(algos) + + +def micro_algo(microalgos: int) -> AlgoAmount: + """Create an AlgoAmount object representing the given number of µAlgo. + + :param int microalgos: The number of µAlgo to create an AlgoAmount object for. + :return AlgoAmount: An AlgoAmount object representing the given number of µAlgo. + """ + return AlgoAmount.from_micro_algos(microalgos) + + +ALGORAND_MIN_TX_FEE = micro_algo(1_000) + + +def transaction_fees(number_of_transactions: int) -> AlgoAmount: + """Calculate the total transaction fees for a given number of transactions. + + :param int number_of_transactions: The number of transactions to calculate the fees for. + :return AlgoAmount: The total transaction fees. + """ + + total_micro_algos = number_of_transactions * ALGORAND_MIN_TX_FEE.micro_algos + return micro_algo(total_micro_algos) diff --git a/tests/models/test_algo_amount.py b/tests/models/test_algo_amount.py index 8b7d2c6a..347a0cce 100644 --- a/tests/models/test_algo_amount.py +++ b/tests/models/test_algo_amount.py @@ -2,7 +2,7 @@ import pytest -from algokit_utils.models.amount import AlgoAmount +from algokit_utils.models.amount import ALGORAND_MIN_TX_FEE, AlgoAmount, algo, micro_algo, transaction_fees def test_initialization() -> None: @@ -100,3 +100,10 @@ def test_type_safety() -> None: with pytest.raises(TypeError, match="Unsupported operand type"): AlgoAmount.from_algos(5) - "invalid" # type: ignore # noqa: PGH003 + + +def test_helper_functions() -> None: + assert algo(1).micro_algos == 1_000_000 + assert micro_algo(1_000_000).micro_algos == 1_000_000 + assert ALGORAND_MIN_TX_FEE.micro_algos == 1_000 + assert transaction_fees(1).micro_algos == 1_000 diff --git a/tests/test_debug_utils.py b/tests/test_debug_utils.py index bdb0d5b3..1dab0291 100644 --- a/tests/test_debug_utils.py +++ b/tests/test_debug_utils.py @@ -311,6 +311,8 @@ def now(cls, tz: timezone | None = None) -> datetime: # noqa: ARG003 @dataclass class TestFile: + __test__ = False + name: str content: bytes mtime: datetime From 51a63678be209d2bdf9a55079e8931a7c88fc780 Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Sun, 2 Feb 2025 01:16:17 +0100 Subject: [PATCH 6/8] refactor: a more idiomatic approach towards constructor of AlgoAmount abstraction --- src/algokit_utils/models/amount.py | 75 ++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/src/algokit_utils/models/amount.py b/src/algokit_utils/models/amount.py index 85f21d42..37fbf73f 100644 --- a/src/algokit_utils/models/amount.py +++ b/src/algokit_utils/models/amount.py @@ -1,6 +1,7 @@ from __future__ import annotations from decimal import Decimal +from typing import overload import algosdk from typing_extensions import Self @@ -11,24 +12,48 @@ class AlgoAmount: """Wrapper class to ensure safe, explicit conversion between µAlgo, Algo and numbers. - :param amount: A dictionary containing either algos, algo, microAlgos, or microAlgo as key - and their corresponding value as an integer or Decimal. - :raises ValueError: If an invalid amount format is provided. - :example: - >>> amount = AlgoAmount({"algos": 1}) - >>> amount = AlgoAmount({"microAlgos": 1_000_000}) + >>> amount = AlgoAmount(algos=1) + >>> amount = AlgoAmount(algo=1) + >>> amount = AlgoAmount.from_algos(1) + >>> amount = AlgoAmount.from_algo(1) + >>> amount = AlgoAmount(micro_algos=1_000_000) + >>> amount = AlgoAmount(micro_algo=1_000_000) + >>> amount = AlgoAmount.from_micro_algos(1_000_000) + >>> amount = AlgoAmount.from_micro_algo(1_000_000) """ - def __init__(self, amount: dict[str, int | Decimal]): - if "microAlgos" in amount: - self.amount_in_micro_algo = int(amount["microAlgos"]) - elif "microAlgo" in amount: - self.amount_in_micro_algo = int(amount["microAlgo"]) - elif "algos" in amount: - self.amount_in_micro_algo = int(amount["algos"] * algosdk.constants.MICROALGOS_TO_ALGOS_RATIO) - elif "algo" in amount: - self.amount_in_micro_algo = int(amount["algo"] * algosdk.constants.MICROALGOS_TO_ALGOS_RATIO) + @overload + def __init__(self, *, micro_algos: int) -> None: ... + + @overload + def __init__(self, *, micro_algo: int) -> None: ... + + @overload + def __init__(self, *, algos: int | Decimal) -> None: ... + + @overload + def __init__(self, *, algo: int | Decimal) -> None: ... + + def __init__( + self, + *, + micro_algos: int | None = None, + micro_algo: int | None = None, + algos: int | Decimal | None = None, + algo: int | Decimal | None = None, + ): + if micro_algos is None and micro_algo is None and algos is None and algo is None: + raise ValueError("No amount provided") + + if micro_algos is not None: + self.amount_in_micro_algo = int(micro_algos) + elif micro_algo is not None: + self.amount_in_micro_algo = int(micro_algo) + elif algos is not None: + self.amount_in_micro_algo = int(algos * algosdk.constants.MICROALGOS_TO_ALGOS_RATIO) + elif algo is not None: + self.amount_in_micro_algo = int(algo * algosdk.constants.MICROALGOS_TO_ALGOS_RATIO) else: raise ValueError("Invalid amount provided") @@ -74,7 +99,7 @@ def from_algos(amount: int | Decimal) -> AlgoAmount: :example: >>> amount = AlgoAmount.from_algos(1) """ - return AlgoAmount({"algos": amount}) + return AlgoAmount(algos=amount) @staticmethod def from_algo(amount: int | Decimal) -> AlgoAmount: @@ -86,7 +111,7 @@ def from_algo(amount: int | Decimal) -> AlgoAmount: :example: >>> amount = AlgoAmount.from_algo(1) """ - return AlgoAmount({"algo": amount}) + return AlgoAmount(algo=amount) @staticmethod def from_micro_algos(amount: int) -> AlgoAmount: @@ -98,7 +123,7 @@ def from_micro_algos(amount: int) -> AlgoAmount: :example: >>> amount = AlgoAmount.from_micro_algos(1_000_000) """ - return AlgoAmount({"microAlgos": amount}) + return AlgoAmount(micro_algos=amount) @staticmethod def from_micro_algo(amount: int) -> AlgoAmount: @@ -110,7 +135,7 @@ def from_micro_algo(amount: int) -> AlgoAmount: :example: >>> amount = AlgoAmount.from_micro_algo(1_000_000) """ - return AlgoAmount({"microAlgo": amount}) + return AlgoAmount(micro_algo=amount) def __str__(self) -> str: return f"{self.micro_algo:,} µALGO" @@ -202,8 +227,8 @@ def __isub__(self, other: AlgoAmount) -> Self: def algo(algos: int) -> AlgoAmount: """Create an AlgoAmount object representing the given number of Algo. - :param int algos: The number of Algo to create an AlgoAmount object for. - :return AlgoAmount: An AlgoAmount object representing the given number of Algo. + :param algos: The number of Algo to create an AlgoAmount object for. + :return: An AlgoAmount object representing the given number of Algo. """ return AlgoAmount.from_algos(algos) @@ -211,8 +236,8 @@ def algo(algos: int) -> AlgoAmount: def micro_algo(microalgos: int) -> AlgoAmount: """Create an AlgoAmount object representing the given number of µAlgo. - :param int microalgos: The number of µAlgo to create an AlgoAmount object for. - :return AlgoAmount: An AlgoAmount object representing the given number of µAlgo. + :param microalgos: The number of µAlgo to create an AlgoAmount object for. + :return: An AlgoAmount object representing the given number of µAlgo. """ return AlgoAmount.from_micro_algos(microalgos) @@ -223,8 +248,8 @@ def micro_algo(microalgos: int) -> AlgoAmount: def transaction_fees(number_of_transactions: int) -> AlgoAmount: """Calculate the total transaction fees for a given number of transactions. - :param int number_of_transactions: The number of transactions to calculate the fees for. - :return AlgoAmount: The total transaction fees. + :param number_of_transactions: The number of transactions to calculate the fees for. + :return: The total transaction fees. """ total_micro_algos = number_of_transactions * ALGORAND_MIN_TX_FEE.micro_algos From 17440012ec2f8ff6d7318c0cf0449fb9e2cce46d Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Sun, 2 Feb 2025 01:16:40 +0100 Subject: [PATCH 7/8] refactor: further refine dataclasses for appclient calls --- .../accounts/account_manager/index.md | 2 +- .../applications/app_client/index.md | 236 +++++------------- .../applications/app_factory/index.md | 36 ++- .../algokit_utils/models/amount/index.md | 64 ++++- docs/markdown/capabilities/amount.md | 8 +- docs/source/capabilities/amount.md | 8 +- src/algokit_utils/applications/app_client.py | 217 ++++++---------- src/algokit_utils/applications/app_factory.py | 10 +- tests/models/test_algo_amount.py | 18 +- 9 files changed, 233 insertions(+), 366 deletions(-) diff --git a/docs/markdown/autoapi/algokit_utils/accounts/account_manager/index.md b/docs/markdown/autoapi/algokit_utils/accounts/account_manager/index.md index 79d214c7..d72789bb 100644 --- a/docs/markdown/autoapi/algokit_utils/accounts/account_manager/index.md +++ b/docs/markdown/autoapi/algokit_utils/accounts/account_manager/index.md @@ -234,7 +234,7 @@ Returns the TransactionSignerAccountProtocol for the given sender address. * **Example:** ```pycon ->>> sender = account_manager.random() +>>> sender = account_manager.random().address >>> # ... >>> # Returns the `TransactionSignerAccountProtocol` for `sender` that has previously been registered >>> account = account_manager.get_account(sender) diff --git a/docs/markdown/autoapi/algokit_utils/applications/app_client/index.md b/docs/markdown/autoapi/algokit_utils/applications/app_client/index.md index 7a135152..d5a6ea28 100644 --- a/docs/markdown/autoapi/algokit_utils/applications/app_client/index.md +++ b/docs/markdown/autoapi/algokit_utils/applications/app_client/index.md @@ -10,14 +10,15 @@ | [`AppClientCompilationResult`](#algokit_utils.applications.app_client.AppClientCompilationResult) | Result of compiling an application's TEAL code. | |-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------| | [`AppClientCompilationParams`](#algokit_utils.applications.app_client.AppClientCompilationParams) | Parameters for compiling an application's TEAL code. | +| [`CommonAppCallParams`](#algokit_utils.applications.app_client.CommonAppCallParams) | Common configuration for app call transaction parameters | +| [`AppClientCreateSchema`](#algokit_utils.applications.app_client.AppClientCreateSchema) | Schema for application creation. | +| [`CommonAppCallCreateParams`](#algokit_utils.applications.app_client.CommonAppCallCreateParams) | Common configuration for app create call transaction parameters. | | [`FundAppAccountParams`](#algokit_utils.applications.app_client.FundAppAccountParams) | Parameters for funding an application's account. | -| [`AppClientCallParams`](#algokit_utils.applications.app_client.AppClientCallParams) | Parameters for calling an application. | -| [`BaseAppClientMethodCallParams`](#algokit_utils.applications.app_client.BaseAppClientMethodCallParams) | Base parameters for application method calls. | -| [`AppClientMethodCallParams`](#algokit_utils.applications.app_client.AppClientMethodCallParams) | Parameters for application method calls. | | [`AppClientBareCallParams`](#algokit_utils.applications.app_client.AppClientBareCallParams) | Parameters for bare application calls. | -| [`AppClientCreateSchema`](#algokit_utils.applications.app_client.AppClientCreateSchema) | Schema for application creation. | | [`AppClientBareCallCreateParams`](#algokit_utils.applications.app_client.AppClientBareCallCreateParams) | Parameters for creating application with bare call. | -| [`AppClientMethodCallCreateParams`](#algokit_utils.applications.app_client.AppClientMethodCallCreateParams) | Parameters for creating application with method call. | +| [`BaseAppClientMethodCallParams`](#algokit_utils.applications.app_client.BaseAppClientMethodCallParams) | Base parameters for application method calls. | +| [`AppClientMethodCallParams`](#algokit_utils.applications.app_client.AppClientMethodCallParams) | Parameters for application method calls. | +| [`AppClientMethodCallCreateParams`](#algokit_utils.applications.app_client.AppClientMethodCallCreateParams) | Parameters for creating application with method call | | [`AppClientParams`](#algokit_utils.applications.app_client.AppClientParams) | Full parameters for creating an app client | | [`AppClient`](#algokit_utils.applications.app_client.AppClient) | A client for interacting with an Algorand smart contract application. | @@ -78,119 +79,26 @@ Parameters for compiling an application’s TEAL code. #### deletable *: bool | None* -### *class* algokit_utils.applications.app_client.FundAppAccountParams - -Parameters for funding an application’s account. - -* **Variables:** - * **sender** – Optional sender address - * **signer** – Optional transaction signer - * **rekey_to** – Optional address to rekey to - * **note** – Optional transaction note - * **lease** – Optional lease - * **static_fee** – Optional static fee - * **extra_fee** – Optional extra fee - * **max_fee** – Optional maximum fee - * **validity_window** – Optional validity window in rounds - * **first_valid_round** – Optional first valid round - * **last_valid_round** – Optional last valid round - * **amount** – Amount to fund - * **close_remainder_to** – Optional address to close remainder to - * **on_complete** – Optional on complete action - -#### sender *: str | None* *= None* - -#### signer *: algosdk.atomic_transaction_composer.TransactionSigner | None* *= None* - -#### rekey_to *: str | None* *= None* - -#### note *: bytes | None* *= None* - -#### lease *: bytes | None* *= None* - -#### static_fee *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount) | None* *= None* - -#### extra_fee *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount) | None* *= None* - -#### max_fee *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount) | None* *= None* - -#### validity_window *: int | None* *= None* - -#### first_valid_round *: int | None* *= None* - -#### last_valid_round *: int | None* *= None* +### *class* algokit_utils.applications.app_client.CommonAppCallParams -#### amount *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount)* - -#### close_remainder_to *: str | None* *= None* - -#### on_complete *: algosdk.transaction.OnComplete | None* *= None* - -### *class* algokit_utils.applications.app_client.AppClientCallParams - -Parameters for calling an application. +Common configuration for app call transaction parameters * **Variables:** - * **method** – Optional ABI method name or signature - * **args** – Optional arguments to pass to method - * **boxes** – Optional box references to load - * **accounts** – Optional account addresses to load - * **apps** – Optional app IDs to load - * **assets** – Optional asset IDs to load - * **lease** – Optional lease - * **sender** – Optional sender address - * **note** – Optional transaction note - * **send_params** – Optional parameters to control transaction sending - -#### method *: str | None* *= None* - -#### args *: list | None* *= None* - -#### boxes *: list | None* *= None* - -#### accounts *: list[str] | None* *= None* - -#### apps *: list[int] | None* *= None* - -#### assets *: list[int] | None* *= None* - -#### lease *: str | bytes | None* *= None* - -#### sender *: str | None* *= None* - -#### note *: bytes | dict | str | None* *= None* - -#### send_params *: dict | None* *= None* - -### *class* algokit_utils.applications.app_client.BaseAppClientMethodCallParams - -Bases: `Generic`[`ArgsT`, `MethodT`] - -Base parameters for application method calls. - -* **Variables:** - * **method** – Method to call - * **args** – Optional arguments to pass to method - * **account_references** – Optional account references - * **app_references** – Optional application references - * **asset_references** – Optional asset references - * **box_references** – Optional box references - * **extra_fee** – Optional extra fee - * **first_valid_round** – Optional first valid round - * **lease** – Optional lease - * **max_fee** – Optional maximum fee - * **note** – Optional note - * **rekey_to** – Optional rekey to address - * **sender** – Optional sender address - * **signer** – Optional transaction signer - * **static_fee** – Optional static fee - * **validity_window** – Optional validity window - * **last_valid_round** – Optional last valid round - * **on_complete** – Optional on complete action - -#### method *: MethodT* - -#### args *: ArgsT | None* *= None* + * **account_references** – List of account addresses to reference + * **app_references** – List of app IDs to reference + * **asset_references** – List of asset IDs to reference + * **box_references** – List of box references to include + * **extra_fee** – Additional fee to add to transaction + * **lease** – Transaction lease value + * **max_fee** – Maximum fee allowed for transaction + * **note** – Arbitrary note for the transaction + * **rekey_to** – Address to rekey account to + * **sender** – Sender address override + * **signer** – Custom transaction signer + * **static_fee** – Fixed fee for transaction + * **validity_window** – Number of rounds valid + * **first_valid_round** – First valid round number + * **last_valid_round** – Last valid round number #### account_references *: list[str] | None* *= None* @@ -198,12 +106,10 @@ Base parameters for application method calls. #### asset_references *: list[int] | None* *= None* -#### box_references *: collections.abc.Sequence[[algokit_utils.models.state.BoxReference](../../models/state/index.md#algokit_utils.models.state.BoxReference) | algokit_utils.models.state.BoxIdentifier] | None* *= None* +#### box_references *: list[[algokit_utils.models.state.BoxReference](../../models/state/index.md#algokit_utils.models.state.BoxReference) | algokit_utils.models.state.BoxIdentifier] | None* *= None* #### extra_fee *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount) | None* *= None* -#### first_valid_round *: int | None* *= None* - #### lease *: bytes | None* *= None* #### max_fee *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount) | None* *= None* @@ -220,95 +126,91 @@ Base parameters for application method calls. #### validity_window *: int | None* *= None* +#### first_valid_round *: int | None* *= None* + #### last_valid_round *: int | None* *= None* #### on_complete *: algosdk.transaction.OnComplete | None* *= None* -### *class* algokit_utils.applications.app_client.AppClientMethodCallParams +### *class* algokit_utils.applications.app_client.AppClientCreateSchema -Bases: [`BaseAppClientMethodCallParams`](#algokit_utils.applications.app_client.BaseAppClientMethodCallParams)[`collections.abc.Sequence`[`algokit_utils.applications.abi.ABIValue | algokit_utils.applications.abi.ABIStruct | algokit_utils.transactions.transaction_composer.AppMethodCallTransactionArgument | None`], `str`] +Schema for application creation. -Parameters for application method calls. +* **Variables:** + * **extra_program_pages** – Optional number of extra program pages + * **schema** – Optional application creation schema -### *class* algokit_utils.applications.app_client.AppClientBareCallParams +#### extra_program_pages *: int | None* *= None* -Parameters for bare application calls. +#### schema *: [algokit_utils.transactions.transaction_composer.AppCreateSchema](../../transactions/transaction_composer/index.md#algokit_utils.transactions.transaction_composer.AppCreateSchema) | None* *= None* -* **Variables:** - * **signer** – Optional transaction signer - * **rekey_to** – Optional rekey to address - * **lease** – Optional lease - * **static_fee** – Optional static fee - * **extra_fee** – Optional extra fee - * **max_fee** – Optional maximum fee - * **validity_window** – Optional validity window - * **first_valid_round** – Optional first valid round - * **last_valid_round** – Optional last valid round - * **sender** – Optional sender address - * **note** – Optional note - * **args** – Optional arguments - * **account_references** – Optional account references - * **app_references** – Optional application references - * **asset_references** – Optional asset references - * **box_references** – Optional box references +### *class* algokit_utils.applications.app_client.CommonAppCallCreateParams -#### signer *: algosdk.atomic_transaction_composer.TransactionSigner | None* *= None* +Bases: [`AppClientCreateSchema`](#algokit_utils.applications.app_client.AppClientCreateSchema), [`CommonAppCallParams`](#algokit_utils.applications.app_client.CommonAppCallParams) -#### rekey_to *: str | None* *= None* +Common configuration for app create call transaction parameters. -#### lease *: bytes | None* *= None* +#### on_complete *: CreateOnComplete | None* *= None* -#### static_fee *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount) | None* *= None* +### *class* algokit_utils.applications.app_client.FundAppAccountParams -#### extra_fee *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount) | None* *= None* +Bases: [`CommonAppCallParams`](#algokit_utils.applications.app_client.CommonAppCallParams) -#### max_fee *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount) | None* *= None* +Parameters for funding an application’s account. -#### validity_window *: int | None* *= None* +* **Variables:** + * **amount** – Amount to fund + * **close_remainder_to** – Optional address to close remainder to -#### first_valid_round *: int | None* *= None* +#### amount *: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount)* -#### last_valid_round *: int | None* *= None* +#### close_remainder_to *: str | None* *= None* -#### sender *: str | None* *= None* +### *class* algokit_utils.applications.app_client.AppClientBareCallParams -#### note *: bytes | None* *= None* +Bases: [`CommonAppCallParams`](#algokit_utils.applications.app_client.CommonAppCallParams) + +Parameters for bare application calls. + +* **Variables:** + **args** – Optional arguments #### args *: list[bytes] | None* *= None* -#### account_references *: list[str] | None* *= None* +### *class* algokit_utils.applications.app_client.AppClientBareCallCreateParams -#### app_references *: list[int] | None* *= None* +Bases: [`CommonAppCallCreateParams`](#algokit_utils.applications.app_client.CommonAppCallCreateParams) -#### asset_references *: list[int] | None* *= None* +Parameters for creating application with bare call. -#### box_references *: list[[algokit_utils.models.state.BoxReference](../../models/state/index.md#algokit_utils.models.state.BoxReference) | algokit_utils.models.state.BoxIdentifier] | None* *= None* +#### on_complete *: CreateOnComplete | None* *= None* -### *class* algokit_utils.applications.app_client.AppClientCreateSchema +### *class* algokit_utils.applications.app_client.BaseAppClientMethodCallParams -Schema for application creation. +Bases: `Generic`[`ArgsT`, `MethodT`], [`CommonAppCallParams`](#algokit_utils.applications.app_client.CommonAppCallParams) -* **Variables:** - * **extra_program_pages** – Optional number of extra program pages - * **schema** – Optional application creation schema +Base parameters for application method calls. -#### extra_program_pages *: int | None* *= None* +* **Variables:** + * **method** – Method to call + * **args** – Optional arguments to pass to method + * **on_complete** – Optional on complete action -#### schema *: [algokit_utils.transactions.transaction_composer.AppCreateSchema](../../transactions/transaction_composer/index.md#algokit_utils.transactions.transaction_composer.AppCreateSchema) | None* *= None* +#### method *: MethodT* -### *class* algokit_utils.applications.app_client.AppClientBareCallCreateParams +#### args *: ArgsT | None* *= None* -Bases: [`AppClientCreateSchema`](#algokit_utils.applications.app_client.AppClientCreateSchema), [`AppClientBareCallParams`](#algokit_utils.applications.app_client.AppClientBareCallParams) +### *class* algokit_utils.applications.app_client.AppClientMethodCallParams -Parameters for creating application with bare call. +Bases: [`BaseAppClientMethodCallParams`](#algokit_utils.applications.app_client.BaseAppClientMethodCallParams)[`collections.abc.Sequence`[`algokit_utils.applications.abi.ABIValue | algokit_utils.applications.abi.ABIStruct | algokit_utils.transactions.transaction_composer.AppMethodCallTransactionArgument | None`], `str`] -#### on_complete *: algosdk.transaction.OnComplete | None* *= None* +Parameters for application method calls. ### *class* algokit_utils.applications.app_client.AppClientMethodCallCreateParams Bases: [`AppClientCreateSchema`](#algokit_utils.applications.app_client.AppClientCreateSchema), [`AppClientMethodCallParams`](#algokit_utils.applications.app_client.AppClientMethodCallParams) -Parameters for creating application with method call. +Parameters for creating application with method call #### on_complete *: CreateOnComplete | None* *= None* diff --git a/docs/markdown/autoapi/algokit_utils/applications/app_factory/index.md b/docs/markdown/autoapi/algokit_utils/applications/app_factory/index.md index e0002b87..c5bf88a2 100644 --- a/docs/markdown/autoapi/algokit_utils/applications/app_factory/index.md +++ b/docs/markdown/autoapi/algokit_utils/applications/app_factory/index.md @@ -2,16 +2,16 @@ ## Classes -| [`AppFactoryParams`](#algokit_utils.applications.app_factory.AppFactoryParams) | | -|--------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------| -| [`AppFactoryCreateParams`](#algokit_utils.applications.app_factory.AppFactoryCreateParams) | Schema for application creation. | -| [`AppFactoryCreateMethodCallParams`](#algokit_utils.applications.app_factory.AppFactoryCreateMethodCallParams) | Schema for application creation. | -| [`AppFactoryCreateMethodCallResult`](#algokit_utils.applications.app_factory.AppFactoryCreateMethodCallResult) | Base class for transaction results. | -| [`SendAppFactoryTransactionResult`](#algokit_utils.applications.app_factory.SendAppFactoryTransactionResult) | Result of an application transaction. | -| [`SendAppUpdateFactoryTransactionResult`](#algokit_utils.applications.app_factory.SendAppUpdateFactoryTransactionResult) | Result of updating an application. | -| [`SendAppCreateFactoryTransactionResult`](#algokit_utils.applications.app_factory.SendAppCreateFactoryTransactionResult) | Result of creating a new application. | -| [`AppFactoryDeployResult`](#algokit_utils.applications.app_factory.AppFactoryDeployResult) | Result from deploying an application via AppFactory | -| [`AppFactory`](#algokit_utils.applications.app_factory.AppFactory) | | +| [`AppFactoryParams`](#algokit_utils.applications.app_factory.AppFactoryParams) | | +|--------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------| +| [`AppFactoryCreateParams`](#algokit_utils.applications.app_factory.AppFactoryCreateParams) | Parameters for creating application with bare call. | +| [`AppFactoryCreateMethodCallParams`](#algokit_utils.applications.app_factory.AppFactoryCreateMethodCallParams) | Parameters for creating application with method call | +| [`AppFactoryCreateMethodCallResult`](#algokit_utils.applications.app_factory.AppFactoryCreateMethodCallResult) | Base class for transaction results. | +| [`SendAppFactoryTransactionResult`](#algokit_utils.applications.app_factory.SendAppFactoryTransactionResult) | Result of an application transaction. | +| [`SendAppUpdateFactoryTransactionResult`](#algokit_utils.applications.app_factory.SendAppUpdateFactoryTransactionResult) | Result of updating an application. | +| [`SendAppCreateFactoryTransactionResult`](#algokit_utils.applications.app_factory.SendAppCreateFactoryTransactionResult) | Result of creating a new application. | +| [`AppFactoryDeployResult`](#algokit_utils.applications.app_factory.AppFactoryDeployResult) | Result from deploying an application via AppFactory | +| [`AppFactory`](#algokit_utils.applications.app_factory.AppFactory) | | ## Module Contents @@ -33,23 +33,17 @@ ### *class* algokit_utils.applications.app_factory.AppFactoryCreateParams -Bases: `_AppFactoryCreateBaseParams`, [`algokit_utils.applications.app_client.AppClientBareCallParams`](../app_client/index.md#algokit_utils.applications.app_client.AppClientBareCallParams) +Bases: [`algokit_utils.applications.app_client.AppClientBareCallCreateParams`](../app_client/index.md#algokit_utils.applications.app_client.AppClientBareCallCreateParams) -Schema for application creation. +Parameters for creating application with bare call. -* **Variables:** - * **extra_program_pages** – Optional number of extra program pages - * **schema** – Optional application creation schema +#### on_complete *: algokit_utils.applications.app_client.CreateOnComplete | None* *= None* ### *class* algokit_utils.applications.app_factory.AppFactoryCreateMethodCallParams -Bases: `_AppFactoryCreateBaseParams`, [`algokit_utils.applications.app_client.AppClientMethodCallParams`](../app_client/index.md#algokit_utils.applications.app_client.AppClientMethodCallParams) +Bases: [`algokit_utils.applications.app_client.AppClientMethodCallCreateParams`](../app_client/index.md#algokit_utils.applications.app_client.AppClientMethodCallCreateParams) -Schema for application creation. - -* **Variables:** - * **extra_program_pages** – Optional number of extra program pages - * **schema** – Optional application creation schema +Parameters for creating application with method call ### *class* algokit_utils.applications.app_factory.AppFactoryCreateMethodCallResult diff --git a/docs/markdown/autoapi/algokit_utils/models/amount/index.md b/docs/markdown/autoapi/algokit_utils/models/amount/index.md index 89160023..a1fde03c 100644 --- a/docs/markdown/autoapi/algokit_utils/models/amount/index.md +++ b/docs/markdown/autoapi/algokit_utils/models/amount/index.md @@ -1,26 +1,45 @@ # algokit_utils.models.amount +## Attributes + +| [`ALGORAND_MIN_TX_FEE`](#algokit_utils.models.amount.ALGORAND_MIN_TX_FEE) | | +|-----------------------------------------------------------------------------|----| + ## Classes | [`AlgoAmount`](#algokit_utils.models.amount.AlgoAmount) | Wrapper class to ensure safe, explicit conversion between µAlgo, Algo and numbers. | |-----------------------------------------------------------|--------------------------------------------------------------------------------------| +## Functions + +| [`algo`](#algokit_utils.models.amount.algo)(→ AlgoAmount) | Create an AlgoAmount object representing the given number of Algo. | +|-----------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| [`micro_algo`](#algokit_utils.models.amount.micro_algo)(→ AlgoAmount) | Create an AlgoAmount object representing the given number of µAlgo. | +| [`transaction_fees`](#algokit_utils.models.amount.transaction_fees)(→ AlgoAmount) | Calculate the total transaction fees for a given number of transactions. | + ## Module Contents -### *class* algokit_utils.models.amount.AlgoAmount(amount: dict[str, int | decimal.Decimal]) +### *class* algokit_utils.models.amount.AlgoAmount(\*, micro_algos: int) + +### *class* algokit_utils.models.amount.AlgoAmount(\*, micro_algo: int) + +### *class* algokit_utils.models.amount.AlgoAmount(\*, algos: int | decimal.Decimal) + +### *class* algokit_utils.models.amount.AlgoAmount(\*, algo: int | decimal.Decimal) Wrapper class to ensure safe, explicit conversion between µAlgo, Algo and numbers. -* **Parameters:** - **amount** – A dictionary containing either algos, algo, microAlgos, or microAlgo as key - and their corresponding value as an integer or Decimal. -* **Raises:** - **ValueError** – If an invalid amount format is provided. * **Example:** ```pycon ->>> amount = AlgoAmount({"algos": 1}) ->>> amount = AlgoAmount({"microAlgos": 1_000_000}) +>>> amount = AlgoAmount(algos=1) +>>> amount = AlgoAmount(algo=1) +>>> amount = AlgoAmount.from_algos(1) +>>> amount = AlgoAmount.from_algo(1) +>>> amount = AlgoAmount(micro_algos=1_000_000) +>>> amount = AlgoAmount(micro_algo=1_000_000) +>>> amount = AlgoAmount.from_micro_algos(1_000_000) +>>> amount = AlgoAmount.from_micro_algo(1_000_000) ``` #### *property* micro_algos *: int* @@ -106,3 +125,32 @@ Create an AlgoAmount object representing the given number of µAlgo. ```pycon >>> amount = AlgoAmount.from_micro_algo(1_000_000) ``` + +### algokit_utils.models.amount.algo(algos: int) → [AlgoAmount](#algokit_utils.models.amount.AlgoAmount) + +Create an AlgoAmount object representing the given number of Algo. + +* **Parameters:** + **algos** – The number of Algo to create an AlgoAmount object for. +* **Returns:** + An AlgoAmount object representing the given number of Algo. + +### algokit_utils.models.amount.micro_algo(microalgos: int) → [AlgoAmount](#algokit_utils.models.amount.AlgoAmount) + +Create an AlgoAmount object representing the given number of µAlgo. + +* **Parameters:** + **microalgos** – The number of µAlgo to create an AlgoAmount object for. +* **Returns:** + An AlgoAmount object representing the given number of µAlgo. + +### algokit_utils.models.amount.ALGORAND_MIN_TX_FEE + +### algokit_utils.models.amount.transaction_fees(number_of_transactions: int) → [AlgoAmount](#algokit_utils.models.amount.AlgoAmount) + +Calculate the total transaction fees for a given number of transactions. + +* **Parameters:** + **number_of_transactions** – The number of transactions to calculate the fees for. +* **Returns:** + The total transaction fees. diff --git a/docs/markdown/capabilities/amount.md b/docs/markdown/capabilities/amount.md index 5770ef43..41e2f683 100644 --- a/docs/markdown/capabilities/amount.md +++ b/docs/markdown/capabilities/amount.md @@ -21,10 +21,10 @@ from algokit_utils import AlgoAmount There are a few ways to create an `AlgoAmount`: - Algo - - Constructor: `AlgoAmount({"algo": 10})` or `AlgoAmount({"algos": 10})` + - Constructor: `AlgoAmount(algo=10)` or `AlgoAmount(algos=10)` - Static helper: `AlgoAmount.from_algo(10)` or `AlgoAmount.from_algos(10)` - microAlgo - - Constructor: `AlgoAmount({"microAlgo": 10_000})` or `AlgoAmount({"microAlgos": 10_000})` + - Constructor: `AlgoAmount(micro_algo=10_000)` or `AlgoAmount(micro_algos=10_000)` - Static helper: `AlgoAmount.from_micro_algo(10_000)` or `AlgoAmount.from_micro_algos(10_000)` ### Extracting a value from `AlgoAmount` @@ -49,7 +49,7 @@ The `AlgoAmount` class supports arithmetic operations: Example: ```python -amount1 = AlgoAmount({"algo": 1}) -amount2 = AlgoAmount({"microAlgo": 500_000}) +amount1 = AlgoAmount(algo=1) +amount2 = AlgoAmount(micro_algo=500_000) total = amount1 + amount2 # Results in 1.5 Algo ``` diff --git a/docs/source/capabilities/amount.md b/docs/source/capabilities/amount.md index f1d11807..e9b9bdcc 100644 --- a/docs/source/capabilities/amount.md +++ b/docs/source/capabilities/amount.md @@ -21,10 +21,10 @@ from algokit_utils import AlgoAmount There are a few ways to create an `AlgoAmount`: - Algo - - Constructor: `AlgoAmount({"algo": 10})` or `AlgoAmount({"algos": 10})` + - Constructor: `AlgoAmount(algo=10)` or `AlgoAmount(algos=10)` - Static helper: `AlgoAmount.from_algo(10)` or `AlgoAmount.from_algos(10)` - microAlgo - - Constructor: `AlgoAmount({"microAlgo": 10_000})` or `AlgoAmount({"microAlgos": 10_000})` + - Constructor: `AlgoAmount(micro_algo=10_000)` or `AlgoAmount(micro_algos=10_000)` - Static helper: `AlgoAmount.from_micro_algo(10_000)` or `AlgoAmount.from_micro_algos(10_000)` ### Extracting a value from `AlgoAmount` @@ -49,7 +49,7 @@ The `AlgoAmount` class supports arithmetic operations: Example: ```python -amount1 = AlgoAmount({"algo": 1}) -amount2 = AlgoAmount({"microAlgo": 500_000}) +amount1 = AlgoAmount(algo=1) +amount2 = AlgoAmount(micro_algo=500_000) total = amount1 + amount2 # Results in 1.5 Algo ``` diff --git a/src/algokit_utils/applications/app_client.py b/src/algokit_utils/applications/app_client.py index fb5d898a..73221651 100644 --- a/src/algokit_utils/applications/app_client.py +++ b/src/algokit_utils/applications/app_client.py @@ -76,7 +76,6 @@ "AppClient", "AppClientBareCallCreateParams", "AppClientBareCallParams", - "AppClientCallParams", "AppClientCompilationParams", "AppClientCompilationResult", "AppClientCreateSchema", @@ -85,6 +84,8 @@ "AppClientParams", "AppSourceMaps", "BaseAppClientMethodCallParams", + "CommonAppCallCreateParams", + "CommonAppCallParams", "CreateOnComplete", "FundAppAccountParams", "get_constant_block_offset", @@ -201,106 +202,35 @@ class AppClientCompilationParams(TypedDict, total=False): deletable: bool | None -@dataclass(kw_only=True) -class FundAppAccountParams: - """Parameters for funding an application's account. - - :ivar sender: Optional sender address - :ivar signer: Optional transaction signer - :ivar rekey_to: Optional address to rekey to - :ivar note: Optional transaction note - :ivar lease: Optional lease - :ivar static_fee: Optional static fee - :ivar extra_fee: Optional extra fee - :ivar max_fee: Optional maximum fee - :ivar validity_window: Optional validity window in rounds - :ivar first_valid_round: Optional first valid round - :ivar last_valid_round: Optional last valid round - :ivar amount: Amount to fund - :ivar close_remainder_to: Optional address to close remainder to - :ivar on_complete: Optional on complete action - """ - - sender: str | None = None - signer: TransactionSigner | None = None - rekey_to: str | None = None - note: bytes | None = None - lease: bytes | None = None - static_fee: AlgoAmount | None = None - extra_fee: AlgoAmount | None = None - max_fee: AlgoAmount | None = None - validity_window: int | None = None - first_valid_round: int | None = None - last_valid_round: int | None = None - amount: AlgoAmount - close_remainder_to: str | None = None - on_complete: algosdk.transaction.OnComplete | None = None - - -@dataclass(kw_only=True) -class AppClientCallParams: - """Parameters for calling an application. - - :ivar method: Optional ABI method name or signature - :ivar args: Optional arguments to pass to method - :ivar boxes: Optional box references to load - :ivar accounts: Optional account addresses to load - :ivar apps: Optional app IDs to load - :ivar assets: Optional asset IDs to load - :ivar lease: Optional lease - :ivar sender: Optional sender address - :ivar note: Optional transaction note - :ivar send_params: Optional parameters to control transaction sending - """ - - method: str | None = None - args: list | None = None - boxes: list | None = None - accounts: list[str] | None = None - apps: list[int] | None = None - assets: list[int] | None = None - lease: (str | bytes) | None = None - sender: str | None = None - note: (bytes | dict | str) | None = None - send_params: dict | None = None - - ArgsT = TypeVar("ArgsT") MethodT = TypeVar("MethodT") @dataclass(kw_only=True, frozen=True) -class BaseAppClientMethodCallParams(Generic[ArgsT, MethodT]): - """Base parameters for application method calls. +class CommonAppCallParams: + """Common configuration for app call transaction parameters + + :ivar account_references: List of account addresses to reference + :ivar app_references: List of app IDs to reference + :ivar asset_references: List of asset IDs to reference + :ivar box_references: List of box references to include + :ivar extra_fee: Additional fee to add to transaction + :ivar lease: Transaction lease value + :ivar max_fee: Maximum fee allowed for transaction + :ivar note: Arbitrary note for the transaction + :ivar rekey_to: Address to rekey account to + :ivar sender: Sender address override + :ivar signer: Custom transaction signer + :ivar static_fee: Fixed fee for transaction + :ivar validity_window: Number of rounds valid + :ivar first_valid_round: First valid round number + :ivar last_valid_round: Last valid round number""" - :ivar method: Method to call - :ivar args: Optional arguments to pass to method - :ivar account_references: Optional account references - :ivar app_references: Optional application references - :ivar asset_references: Optional asset references - :ivar box_references: Optional box references - :ivar extra_fee: Optional extra fee - :ivar first_valid_round: Optional first valid round - :ivar lease: Optional lease - :ivar max_fee: Optional maximum fee - :ivar note: Optional note - :ivar rekey_to: Optional rekey to address - :ivar sender: Optional sender address - :ivar signer: Optional transaction signer - :ivar static_fee: Optional static fee - :ivar validity_window: Optional validity window - :ivar last_valid_round: Optional last valid round - :ivar on_complete: Optional on complete action - """ - - method: MethodT - args: ArgsT | None = None account_references: list[str] | None = None app_references: list[int] | None = None asset_references: list[int] | None = None - box_references: Sequence[BoxReference | BoxIdentifier] | None = None + box_references: list[BoxReference | BoxIdentifier] | None = None extra_fee: AlgoAmount | None = None - first_valid_round: int | None = None lease: bytes | None = None max_fee: AlgoAmount | None = None note: bytes | None = None @@ -309,82 +239,85 @@ class BaseAppClientMethodCallParams(Generic[ArgsT, MethodT]): signer: TransactionSigner | None = None static_fee: AlgoAmount | None = None validity_window: int | None = None + first_valid_round: int | None = None last_valid_round: int | None = None - on_complete: algosdk.transaction.OnComplete | None = None + on_complete: OnComplete | None = None + + +@dataclass(frozen=True) +class AppClientCreateSchema: + """Schema for application creation. + + :ivar extra_program_pages: Optional number of extra program pages + :ivar schema: Optional application creation schema + """ + + extra_program_pages: int | None = None + schema: AppCreateSchema | None = None @dataclass(kw_only=True, frozen=True) -class AppClientMethodCallParams( - BaseAppClientMethodCallParams[ - Sequence[ABIValue | ABIStruct | AppMethodCallTransactionArgument | None], - str, - ] -): - """Parameters for application method calls.""" +class CommonAppCallCreateParams(AppClientCreateSchema, CommonAppCallParams): + """Common configuration for app create call transaction parameters.""" + + on_complete: CreateOnComplete | None = None @dataclass(kw_only=True, frozen=True) -class AppClientBareCallParams: +class FundAppAccountParams(CommonAppCallParams): + """Parameters for funding an application's account. + + :ivar amount: Amount to fund + :ivar close_remainder_to: Optional address to close remainder to + """ + + amount: AlgoAmount + close_remainder_to: str | None = None + + +@dataclass(kw_only=True, frozen=True) +class AppClientBareCallParams(CommonAppCallParams): """Parameters for bare application calls. - :ivar signer: Optional transaction signer - :ivar rekey_to: Optional rekey to address - :ivar lease: Optional lease - :ivar static_fee: Optional static fee - :ivar extra_fee: Optional extra fee - :ivar max_fee: Optional maximum fee - :ivar validity_window: Optional validity window - :ivar first_valid_round: Optional first valid round - :ivar last_valid_round: Optional last valid round - :ivar sender: Optional sender address - :ivar note: Optional note :ivar args: Optional arguments - :ivar account_references: Optional account references - :ivar app_references: Optional application references - :ivar asset_references: Optional asset references - :ivar box_references: Optional box references """ - signer: TransactionSigner | None = None - rekey_to: str | None = None - lease: bytes | None = None - static_fee: AlgoAmount | None = None - extra_fee: AlgoAmount | None = None - max_fee: AlgoAmount | None = None - validity_window: int | None = None - first_valid_round: int | None = None - last_valid_round: int | None = None - sender: str | None = None - note: bytes | None = None args: list[bytes] | None = None - account_references: list[str] | None = None - app_references: list[int] | None = None - asset_references: list[int] | None = None - box_references: list[BoxReference | BoxIdentifier] | None = None @dataclass(frozen=True) -class AppClientCreateSchema: - """Schema for application creation. +class AppClientBareCallCreateParams(CommonAppCallCreateParams): + """Parameters for creating application with bare call.""" - :ivar extra_program_pages: Optional number of extra program pages - :ivar schema: Optional application creation schema - """ + on_complete: CreateOnComplete | None = None - extra_program_pages: int | None = None - schema: AppCreateSchema | None = None +@dataclass(kw_only=True, frozen=True) +class BaseAppClientMethodCallParams(Generic[ArgsT, MethodT], CommonAppCallParams): + """Base parameters for application method calls. -@dataclass(frozen=True) -class AppClientBareCallCreateParams(AppClientCreateSchema, AppClientBareCallParams): - """Parameters for creating application with bare call.""" + :ivar method: Method to call + :ivar args: Optional arguments to pass to method + :ivar on_complete: Optional on complete action + """ - on_complete: OnComplete | None = None + method: MethodT + args: ArgsT | None = None + + +@dataclass(kw_only=True, frozen=True) +class AppClientMethodCallParams( + BaseAppClientMethodCallParams[ + Sequence[ABIValue | ABIStruct | AppMethodCallTransactionArgument | None], + str, + ] +): + """Parameters for application method calls.""" @dataclass(frozen=True) class AppClientMethodCallCreateParams(AppClientCreateSchema, AppClientMethodCallParams): - """Parameters for creating application with method call.""" + """Parameters for creating application with method call""" on_complete: CreateOnComplete | None = None diff --git a/src/algokit_utils/applications/app_factory.py b/src/algokit_utils/applications/app_factory.py index 3b0fb423..78857ab8 100644 --- a/src/algokit_utils/applications/app_factory.py +++ b/src/algokit_utils/applications/app_factory.py @@ -23,7 +23,6 @@ AppClientBareCallParams, AppClientCompilationParams, AppClientCompilationResult, - AppClientCreateSchema, AppClientMethodCallCreateParams, AppClientMethodCallParams, AppClientParams, @@ -88,17 +87,12 @@ class AppFactoryParams: @dataclass(kw_only=True, frozen=True) -class _AppFactoryCreateBaseParams(AppClientCreateSchema): +class AppFactoryCreateParams(AppClientBareCallCreateParams): on_complete: CreateOnComplete | None = None @dataclass(kw_only=True, frozen=True) -class AppFactoryCreateParams(_AppFactoryCreateBaseParams, AppClientBareCallParams): - pass - - -@dataclass(kw_only=True, frozen=True) -class AppFactoryCreateMethodCallParams(_AppFactoryCreateBaseParams, AppClientMethodCallParams): +class AppFactoryCreateMethodCallParams(AppClientMethodCallCreateParams): pass diff --git a/tests/models/test_algo_amount.py b/tests/models/test_algo_amount.py index 347a0cce..5161d37b 100644 --- a/tests/models/test_algo_amount.py +++ b/tests/models/test_algo_amount.py @@ -7,18 +7,14 @@ def test_initialization() -> None: # Test valid initialization formats - assert AlgoAmount({"microAlgos": 1_000_000}).micro_algos == 1_000_000 - assert AlgoAmount({"microAlgo": 500_000}).micro_algos == 500_000 - assert AlgoAmount({"algos": 1}).micro_algos == 1_000_000 - assert AlgoAmount({"algo": Decimal("0.5")}).micro_algos == 500_000 + assert AlgoAmount(micro_algos=1_000_000).micro_algos == 1_000_000 + assert AlgoAmount(micro_algo=500_000).micro_algos == 500_000 + assert AlgoAmount(algos=1).micro_algos == 1_000_000 + assert AlgoAmount(algo=Decimal("0.5")).micro_algos == 500_000 # Test decimal precision - assert AlgoAmount({"algos": Decimal("0.000001")}).micro_algos == 1 - assert AlgoAmount({"algo": Decimal("123.456789")}).micro_algos == 123_456_789 - - # Test invalid initialization - with pytest.raises(ValueError, match="Invalid amount provided"): - AlgoAmount({"invalid": 100}) + assert AlgoAmount(algos=Decimal("0.000001")).micro_algos == 1 + assert AlgoAmount(algo=Decimal("123.456789")).micro_algos == 123_456_789 def test_from_methods() -> None: @@ -84,7 +80,7 @@ def test_edge_cases() -> None: assert large.micro_algos == 1e9 * 1e6 # Decimal precision limits - precise = AlgoAmount({"algos": Decimal("0.123456789")}) + precise = AlgoAmount(algos=Decimal("0.123456789")) assert precise.micro_algos == 123_456 From f37ddc8549af5fdc0a8fd73a8885bf818e245923 Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Mon, 3 Feb 2025 10:34:46 +0100 Subject: [PATCH 8/8] chore: addressing pr comment for logic error; tweaking AppClient section in migration guide --- docs/markdown/v3-migration-guide.md | 86 +++++++------------- docs/source/v3-migration-guide.md | 86 +++++++------------- src/algokit_utils/applications/app_client.py | 10 +-- 3 files changed, 61 insertions(+), 121 deletions(-) diff --git a/docs/markdown/v3-migration-guide.md b/docs/markdown/v3-migration-guide.md index 710d6850..27a48053 100644 --- a/docs/markdown/v3-migration-guide.md +++ b/docs/markdown/v3-migration-guide.md @@ -149,71 +149,41 @@ arc56_app_spec = Arc56Contract.from_arc32(testing_app_arc32_app_spec) > Despite auto conversion of ARC-32 to ARC-56, we recommend recompiling your contract to a fully compliant ARC-56 specification given that auto conversion would skip populating information that can’t be parsed from raw ARC-32. -### Step 5 - Update `ApplicationClient` usage +### Step 5 - Replace `ApplicationClient` usage -The application client has been in v2 has been responsible for instantiation, deployment and calling of the application. In v3, this has been split into `AppClient`, `AppDeployer` and `AppFactory` to better reflect the different responsibilities: +The existing `ApplicationClient` (untyped app client) class is still present until at least v4, but it’s worthwhile migrating to the new [`AppClient` and `AppFactory` classes](capabilities/app-client.md). These new clients are [ARC-56](https://github.com/algorandfoundation/ARCs/pull/258) compatible, but also support [ARC-32](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0032.md) app specs and will continue to support this indefinitely until such time the community deems they are deprecated. -```python -"""Before (v2 deployment)""" -from algokit_utils import ApplicationClient, OnUpdate, OnSchemaBreak - -# Initialize client with manual configuration -app_client = ApplicationClient( - algod_client=algod, - app_spec=app_spec, - creator=creator, - app_name="MyApp" -) - -# Deployment with versioning and update policies -deploy_result = app_client.deploy( - version="1.0", - allow_update=True, - allow_delete=False, - on_update=OnUpdate.UpdateApp, - on_schema_break=OnSchemaBreak.Fail -) +All of the functionality in `ApplicationClient` is available within the new classes, but their interface is slightly different to make it easier to use and more consistent with the new `AlgorandClient` functionality. The key existing methods that have changed all have `@deprecation` notices to help guide you on this, but broadly the changes are: -# Post-deployment calls -response = app_client.call("initialize", args=["config"]) +- The app resolution semantics, now have static methods that determine different ways of constructing a client and the constructor itself is very simple (requiring `app_id`) +- If you want to call `create` or `deploy` then you need an `AppFactory` to do that, and then it will in turn give you an `AppClient` instance that is connected to the app you just created / deployed. This significantly simplifies the app client because now the app client has a clear operating purpose: allow for calls and state management for an *instance* of an app, whereas the app factory handles all of the calls when you don’t have an instance yet (or may or may not have an instance in the case of `deploy`). +- This means that you can simply access `client.app_id` and `client.app_address` on `AppClient` since these values are known statically and won’t change (previously associated calls to `app_address`, `app_id` properties potentially required extra API calls as the values weren’t always available). +- Adding `fund_app_account` which serves as a convenience method to top up the balance of address associated with application. +- All of the methods that return or execute a transaction (`update`, `call`, `opt_in`, etc.) are now exposed in an interface similar to the one in [`AlgorandClient`](capabilities/algorand-client.md#creating-and-issuing-transactions), namely (where `{call_type}` is one of: `update` / `delete` / `opt_in` / `close_out` / `clear_state` / `call`): + - `appClient.create_transaction.{callType}` to get a transaction for an ABI method call + - `appClient.send.{call_type}` to sign and send a transaction for an ABI method call + - `appClient.params.{call_type}` to get a [params object](capabilities/algorand-client.md#transaction-parameters) for an ABI method call + - `appClient.create_transaction.bare.{call_type}` to get a transaction for a bare app call + - `appClient.send.bare.{call_type}` to sign and send a transaction for a bare app call + - `appClient.params.bare.{call_type}` to get a [params object](capabilities/algorand-client.md#transaction-parameters) for a bare app call +- The semantics to resolve the application is now available via [simpler entrypoints within `algorand.client`](capabilities/app-client.md#appclient) +- When making an ABI method call, the method arguments property is are now passed via explicit `args` field in a parameters dataclass applicable to the method call. +- The foreign reference arrays have been renamed to align with typed parameters on `ts` and related core `algosdk`: + - `boxes` -> `box_references` + - `apps` -> `app_references` + - `assets` -> `asset_references` + - `accounts` -> `account_references` +- The return value for methods that send a transaction will have any ABI return value directly in the `abi_return` property rather than the low level algosdk `ABIResult` type while also automatically decoding values based on provided ARC56 spec. +### Step 6 - Replace typed app client usage -"""After (v3 factory-based deployment)""" -from algokit_utils import AppFactory, OnUpdate, OnSchemaBreak - -# Factory-based deployment with compiled parameters -app_factory = AppFactory( - AppFactoryParams( - algorand=algorand, - app_spec=app_spec, - app_name="MyApp", - compilation_params=AppClientCompilationParams( - deploy_time_params={"VERSION": 1}, - updatable=True, # Replaces allow_update - deletable=False # Replaces allow_delete - ) - ) -) - -app_client, deploy_result = app_factory.deploy( - version="1.0", - on_update=OnUpdate.UpdateApp, - on_schema_break=OnSchemaBreak.Fail, -) # Returns a tuple of (app_client, deploy_result) - -# Type-safe post-deployment calls -response = app_client.send.call("setup", args=[{"max_users": 100}]) -``` +Version 2 of the Python typed app client generator introduces breaking changes to the generated client that support the new `AppFactory` and `AppClient` functionality along with adding ARC-56 support. The generated client has better typing support for things like state commensurate with the new capabilities within ARC-56. -Notable changes: +It’s worth noting that because we have maintained backwards compatibility with the pre v2 `algokit-utils-py` stateless functions, older typed clients generated using version 1 of the Python typed client generator will work against v3 of utils, however you won’t have access to the new features or ARC-56 support. -- Split between `AppClient`, `AppDeployer` (for raw creation/deployment) and `AppFactory` (for creation/deployment using factory patterns). In majority of cases, you will only need `AppFactory` as it provides convenience methods for instantiation of `AppClient` and mediates calls to `AppDeployer`. -- More structured transaction building with `.params`, `.create_transaction`, and `.send` -- Consistent parameter naming (`args` instead of `method_args`, `box_references` instead of `boxes`) -- ARC-56 support for state management -- Improved error handling and debugging support +If you want to convert from an older typed client to a new one you will need to make certain changes. Refer to [client generator v2 migration guide](https://github.com/algorandfoundation/algokit-client-generator-py/blob/main/docs/v2-migration.md). -### Step 6 - Update `AppClient` State Management +### Step 7 - Update `AppClient` State Management State management is now more structured and type-safe: @@ -240,7 +210,7 @@ boxes = app_client.state.box.get_all() map_value = app_client.state.box.get_map_value("map_name", "key") ``` -### Step 7 - Update Asset Management +### Step 8 - Update Asset Management Asset management is now more consistent: diff --git a/docs/source/v3-migration-guide.md b/docs/source/v3-migration-guide.md index 9d64cc16..674ae165 100644 --- a/docs/source/v3-migration-guide.md +++ b/docs/source/v3-migration-guide.md @@ -149,71 +149,41 @@ arc56_app_spec = Arc56Contract.from_arc32(testing_app_arc32_app_spec) > Despite auto conversion of ARC-32 to ARC-56, we recommend recompiling your contract to a fully compliant ARC-56 specification given that auto conversion would skip populating information that can't be parsed from raw ARC-32. -### Step 5 - Update `ApplicationClient` usage +### Step 5 - Replace `ApplicationClient` usage -The application client has been in v2 has been responsible for instantiation, deployment and calling of the application. In v3, this has been split into `AppClient`, `AppDeployer` and `AppFactory` to better reflect the different responsibilities: +The existing `ApplicationClient` (untyped app client) class is still present until at least v4, but it's worthwhile migrating to the new [`AppClient` and `AppFactory` classes](./capabilities/app-client.md). These new clients are [ARC-56](https://github.com/algorandfoundation/ARCs/pull/258) compatible, but also support [ARC-32](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0032.md) app specs and will continue to support this indefinitely until such time the community deems they are deprecated. -```python -"""Before (v2 deployment)""" -from algokit_utils import ApplicationClient, OnUpdate, OnSchemaBreak - -# Initialize client with manual configuration -app_client = ApplicationClient( - algod_client=algod, - app_spec=app_spec, - creator=creator, - app_name="MyApp" -) - -# Deployment with versioning and update policies -deploy_result = app_client.deploy( - version="1.0", - allow_update=True, - allow_delete=False, - on_update=OnUpdate.UpdateApp, - on_schema_break=OnSchemaBreak.Fail -) +All of the functionality in `ApplicationClient` is available within the new classes, but their interface is slightly different to make it easier to use and more consistent with the new `AlgorandClient` functionality. The key existing methods that have changed all have `@deprecation` notices to help guide you on this, but broadly the changes are: -# Post-deployment calls -response = app_client.call("initialize", args=["config"]) +- The app resolution semantics, now have static methods that determine different ways of constructing a client and the constructor itself is very simple (requiring `app_id`) +- If you want to call `create` or `deploy` then you need an `AppFactory` to do that, and then it will in turn give you an `AppClient` instance that is connected to the app you just created / deployed. This significantly simplifies the app client because now the app client has a clear operating purpose: allow for calls and state management for an _instance_ of an app, whereas the app factory handles all of the calls when you don't have an instance yet (or may or may not have an instance in the case of `deploy`). +- This means that you can simply access `client.app_id` and `client.app_address` on `AppClient` since these values are known statically and won't change (previously associated calls to `app_address`, `app_id` properties potentially required extra API calls as the values weren't always available). +- Adding `fund_app_account` which serves as a convenience method to top up the balance of address associated with application. +- All of the methods that return or execute a transaction (`update`, `call`, `opt_in`, etc.) are now exposed in an interface similar to the one in [`AlgorandClient`](./capabilities/algorand-client.md#creating-and-issuing-transactions), namely (where `{call_type}` is one of: `update` / `delete` / `opt_in` / `close_out` / `clear_state` / `call`): + - `appClient.create_transaction.{callType}` to get a transaction for an ABI method call + - `appClient.send.{call_type}` to sign and send a transaction for an ABI method call + - `appClient.params.{call_type}` to get a [params object](./capabilities/algorand-client.md#transaction-parameters) for an ABI method call + - `appClient.create_transaction.bare.{call_type}` to get a transaction for a bare app call + - `appClient.send.bare.{call_type}` to sign and send a transaction for a bare app call + - `appClient.params.bare.{call_type}` to get a [params object](./capabilities/algorand-client.md#transaction-parameters) for a bare app call +- The semantics to resolve the application is now available via [simpler entrypoints within `algorand.client`](./capabilities/app-client.md#appclient) +- When making an ABI method call, the method arguments property is are now passed via explicit `args` field in a parameters dataclass applicable to the method call. +- The foreign reference arrays have been renamed to align with typed parameters on `ts` and related core `algosdk`: + - `boxes` -> `box_references` + - `apps` -> `app_references` + - `assets` -> `asset_references` + - `accounts` -> `account_references` +- The return value for methods that send a transaction will have any ABI return value directly in the `abi_return` property rather than the low level algosdk `ABIResult` type while also automatically decoding values based on provided ARC56 spec. +### Step 6 - Replace typed app client usage -"""After (v3 factory-based deployment)""" -from algokit_utils import AppFactory, OnUpdate, OnSchemaBreak - -# Factory-based deployment with compiled parameters -app_factory = AppFactory( - AppFactoryParams( - algorand=algorand, - app_spec=app_spec, - app_name="MyApp", - compilation_params=AppClientCompilationParams( - deploy_time_params={"VERSION": 1}, - updatable=True, # Replaces allow_update - deletable=False # Replaces allow_delete - ) - ) -) - -app_client, deploy_result = app_factory.deploy( - version="1.0", - on_update=OnUpdate.UpdateApp, - on_schema_break=OnSchemaBreak.Fail, -) # Returns a tuple of (app_client, deploy_result) - -# Type-safe post-deployment calls -response = app_client.send.call("setup", args=[{"max_users": 100}]) -``` +Version 2 of the Python typed app client generator introduces breaking changes to the generated client that support the new `AppFactory` and `AppClient` functionality along with adding ARC-56 support. The generated client has better typing support for things like state commensurate with the new capabilities within ARC-56. -Notable changes: +It's worth noting that because we have maintained backwards compatibility with the pre v2 `algokit-utils-py` stateless functions, older typed clients generated using version 1 of the Python typed client generator will work against v3 of utils, however you won't have access to the new features or ARC-56 support. -- Split between `AppClient`, `AppDeployer` (for raw creation/deployment) and `AppFactory` (for creation/deployment using factory patterns). In majority of cases, you will only need `AppFactory` as it provides convenience methods for instantiation of `AppClient` and mediates calls to `AppDeployer`. -- More structured transaction building with `.params`, `.create_transaction`, and `.send` -- Consistent parameter naming (`args` instead of `method_args`, `box_references` instead of `boxes`) -- ARC-56 support for state management -- Improved error handling and debugging support +If you want to convert from an older typed client to a new one you will need to make certain changes. Refer to [client generator v2 migration guide](https://github.com/algorandfoundation/algokit-client-generator-py/blob/main/docs/v2-migration.md). -### Step 6 - Update `AppClient` State Management +### Step 7 - Update `AppClient` State Management State management is now more structured and type-safe: @@ -240,7 +210,7 @@ boxes = app_client.state.box.get_all() map_value = app_client.state.box.get_map_value("map_name", "key") ``` -### Step 7 - Update Asset Management +### Step 8 - Update Asset Management Asset management is now more consistent: diff --git a/src/algokit_utils/applications/app_client.py b/src/algokit_utils/applications/app_client.py index 73221651..6e67f2c7 100644 --- a/src/algokit_utils/applications/app_client.py +++ b/src/algokit_utils/applications/app_client.py @@ -1549,7 +1549,7 @@ def _expose_logic_error_static( # noqa: C901 program: bytes | None = None, approval_source_info: ProgramSourceInfo | None = None, clear_source_info: ProgramSourceInfo | None = None, - ) -> Exception: + ) -> LogicError | Exception: source_map = clear_source_map if is_clear_state_program else approval_source_map error_details = parse_logic_error(str(e)) @@ -1628,11 +1628,11 @@ def get_line_for_pc(input_pc: int) -> int | None: ) if isinstance(e, LogicError): e.message = runtime_error_message - error = e + return e else: - e = Exception(runtime_error_message) - error.__cause__ = e - return error + error = Exception(runtime_error_message) + error.__cause__ = e + return error return e