algokit-utils 3.0.0b1__py3-none-any.whl → 3.0.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of algokit-utils might be problematic. Click here for more details.

Files changed (70) hide show
  1. algokit_utils/__init__.py +23 -183
  2. algokit_utils/_debugging.py +123 -97
  3. algokit_utils/_legacy_v2/__init__.py +177 -0
  4. algokit_utils/{_ensure_funded.py → _legacy_v2/_ensure_funded.py} +19 -18
  5. algokit_utils/{_transfer.py → _legacy_v2/_transfer.py} +24 -23
  6. algokit_utils/_legacy_v2/account.py +203 -0
  7. algokit_utils/_legacy_v2/application_client.py +1471 -0
  8. algokit_utils/_legacy_v2/application_specification.py +21 -0
  9. algokit_utils/_legacy_v2/asset.py +168 -0
  10. algokit_utils/_legacy_v2/common.py +28 -0
  11. algokit_utils/_legacy_v2/deploy.py +822 -0
  12. algokit_utils/_legacy_v2/logic_error.py +14 -0
  13. algokit_utils/{models.py → _legacy_v2/models.py} +19 -142
  14. algokit_utils/_legacy_v2/network_clients.py +140 -0
  15. algokit_utils/account.py +12 -183
  16. algokit_utils/accounts/__init__.py +2 -0
  17. algokit_utils/accounts/account_manager.py +909 -0
  18. algokit_utils/accounts/kmd_account_manager.py +159 -0
  19. algokit_utils/algorand.py +265 -0
  20. algokit_utils/application_client.py +9 -1453
  21. algokit_utils/application_specification.py +39 -197
  22. algokit_utils/applications/__init__.py +7 -0
  23. algokit_utils/applications/abi.py +276 -0
  24. algokit_utils/applications/app_client.py +2056 -0
  25. algokit_utils/applications/app_deployer.py +600 -0
  26. algokit_utils/applications/app_factory.py +826 -0
  27. algokit_utils/applications/app_manager.py +470 -0
  28. algokit_utils/applications/app_spec/__init__.py +2 -0
  29. algokit_utils/applications/app_spec/arc32.py +207 -0
  30. algokit_utils/applications/app_spec/arc56.py +1023 -0
  31. algokit_utils/applications/enums.py +40 -0
  32. algokit_utils/asset.py +32 -168
  33. algokit_utils/assets/__init__.py +1 -0
  34. algokit_utils/assets/asset_manager.py +320 -0
  35. algokit_utils/beta/_utils.py +36 -0
  36. algokit_utils/beta/account_manager.py +4 -195
  37. algokit_utils/beta/algorand_client.py +4 -314
  38. algokit_utils/beta/client_manager.py +5 -74
  39. algokit_utils/beta/composer.py +5 -712
  40. algokit_utils/clients/__init__.py +2 -0
  41. algokit_utils/clients/client_manager.py +656 -0
  42. algokit_utils/clients/dispenser_api_client.py +192 -0
  43. algokit_utils/common.py +8 -26
  44. algokit_utils/config.py +71 -18
  45. algokit_utils/deploy.py +7 -892
  46. algokit_utils/dispenser_api.py +8 -176
  47. algokit_utils/errors/__init__.py +1 -0
  48. algokit_utils/errors/logic_error.py +121 -0
  49. algokit_utils/logic_error.py +7 -80
  50. algokit_utils/models/__init__.py +8 -0
  51. algokit_utils/models/account.py +193 -0
  52. algokit_utils/models/amount.py +198 -0
  53. algokit_utils/models/application.py +61 -0
  54. algokit_utils/models/network.py +25 -0
  55. algokit_utils/models/simulate.py +11 -0
  56. algokit_utils/models/state.py +59 -0
  57. algokit_utils/models/transaction.py +100 -0
  58. algokit_utils/network_clients.py +7 -152
  59. algokit_utils/protocols/__init__.py +2 -0
  60. algokit_utils/protocols/account.py +22 -0
  61. algokit_utils/protocols/typed_clients.py +108 -0
  62. algokit_utils/transactions/__init__.py +3 -0
  63. algokit_utils/transactions/transaction_composer.py +2293 -0
  64. algokit_utils/transactions/transaction_creator.py +156 -0
  65. algokit_utils/transactions/transaction_sender.py +574 -0
  66. {algokit_utils-3.0.0b1.dist-info → algokit_utils-3.0.0b2.dist-info}/METADATA +12 -7
  67. algokit_utils-3.0.0b2.dist-info/RECORD +70 -0
  68. {algokit_utils-3.0.0b1.dist-info → algokit_utils-3.0.0b2.dist-info}/WHEEL +1 -1
  69. algokit_utils-3.0.0b1.dist-info/RECORD +0 -24
  70. {algokit_utils-3.0.0b1.dist-info → algokit_utils-3.0.0b2.dist-info}/LICENSE +0 -0
@@ -0,0 +1,192 @@
1
+ import contextlib
2
+ import enum
3
+ import os
4
+ from dataclasses import dataclass
5
+
6
+ import httpx
7
+
8
+ from algokit_utils.config import config
9
+
10
+ __all__ = [
11
+ "DISPENSER_ACCESS_TOKEN_KEY",
12
+ "DISPENSER_ASSETS",
13
+ "DISPENSER_REQUEST_TIMEOUT",
14
+ "DispenserApiConfig",
15
+ "DispenserAsset",
16
+ "DispenserAssetName",
17
+ "DispenserFundResponse",
18
+ "DispenserLimitResponse",
19
+ "TestNetDispenserApiClient",
20
+ ]
21
+
22
+
23
+ logger = config.logger
24
+
25
+
26
+ class DispenserApiConfig:
27
+ BASE_URL = "https://api.dispenser.algorandfoundation.tools"
28
+
29
+
30
+ class DispenserAssetName(enum.IntEnum):
31
+ ALGO = 0
32
+
33
+
34
+ @dataclass
35
+ class DispenserAsset:
36
+ asset_id: int
37
+ decimals: int
38
+ description: str
39
+
40
+
41
+ @dataclass
42
+ class DispenserFundResponse:
43
+ tx_id: str
44
+ amount: int
45
+
46
+
47
+ @dataclass
48
+ class DispenserLimitResponse:
49
+ amount: int
50
+
51
+
52
+ DISPENSER_ASSETS = {
53
+ DispenserAssetName.ALGO: DispenserAsset(
54
+ asset_id=0,
55
+ decimals=6,
56
+ description="Algo",
57
+ ),
58
+ }
59
+ DISPENSER_REQUEST_TIMEOUT = 15
60
+ DISPENSER_ACCESS_TOKEN_KEY = "ALGOKIT_DISPENSER_ACCESS_TOKEN"
61
+
62
+
63
+ class TestNetDispenserApiClient:
64
+ """
65
+ Client for interacting with the [AlgoKit TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md).
66
+ To get started create a new access token via `algokit dispenser login --ci`
67
+ and pass it to the client constructor as `auth_token`.
68
+ Alternatively set the access token as environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN`,
69
+ and it will be auto loaded. If both are set, the constructor argument takes precedence.
70
+
71
+ Default request timeout is 15 seconds. Modify by passing `request_timeout` to the constructor.
72
+ """
73
+
74
+ auth_token: str
75
+ request_timeout = DISPENSER_REQUEST_TIMEOUT
76
+
77
+ def __init__(self, auth_token: str | None = None, request_timeout: int = DISPENSER_REQUEST_TIMEOUT):
78
+ auth_token_from_env = os.getenv(DISPENSER_ACCESS_TOKEN_KEY)
79
+
80
+ if auth_token:
81
+ self.auth_token = auth_token
82
+ elif auth_token_from_env:
83
+ self.auth_token = auth_token_from_env
84
+ else:
85
+ raise Exception(
86
+ f"Can't init AlgoKit TestNet Dispenser API client "
87
+ f"because neither environment variable {DISPENSER_ACCESS_TOKEN_KEY} or "
88
+ "the auth_token were provided."
89
+ )
90
+
91
+ self.request_timeout = request_timeout
92
+
93
+ def _process_dispenser_request(
94
+ self, *, auth_token: str, url_suffix: str, data: dict | None = None, method: str = "POST"
95
+ ) -> httpx.Response:
96
+ """
97
+ Generalized method to process http requests to dispenser API
98
+ """
99
+
100
+ headers = {"Authorization": f"Bearer {(auth_token)}"}
101
+
102
+ # Set request arguments
103
+ request_args = {
104
+ "url": f"{DispenserApiConfig.BASE_URL}/{url_suffix}",
105
+ "headers": headers,
106
+ "timeout": self.request_timeout,
107
+ }
108
+
109
+ if method.upper() != "GET" and data is not None:
110
+ request_args["json"] = data
111
+
112
+ try:
113
+ response: httpx.Response = getattr(httpx, method.lower())(**request_args)
114
+ response.raise_for_status()
115
+ return response
116
+
117
+ except httpx.HTTPStatusError as err:
118
+ error_message = f"Error processing dispenser API request: {err.response.status_code}"
119
+ error_response = None
120
+ with contextlib.suppress(Exception):
121
+ error_response = err.response.json()
122
+
123
+ if error_response and error_response.get("code"):
124
+ error_message = error_response.get("code")
125
+
126
+ elif err.response.status_code == httpx.codes.BAD_REQUEST:
127
+ error_message = err.response.json()["message"]
128
+
129
+ raise Exception(error_message) from err
130
+
131
+ except Exception as err:
132
+ error_message = "Error processing dispenser API request"
133
+ logger.debug(f"{error_message}: {err}", exc_info=True)
134
+ raise err
135
+
136
+ def fund(self, address: str, amount: int, asset_id: int) -> DispenserFundResponse:
137
+ """
138
+ Fund an account with Algos from the dispenser API
139
+ """
140
+
141
+ try:
142
+ response = self._process_dispenser_request(
143
+ auth_token=self.auth_token,
144
+ url_suffix=f"fund/{asset_id}",
145
+ data={"receiver": address, "amount": amount, "assetID": asset_id},
146
+ method="POST",
147
+ )
148
+
149
+ content = response.json()
150
+ return DispenserFundResponse(tx_id=content["txID"], amount=content["amount"])
151
+
152
+ except Exception as err:
153
+ logger.exception(f"Error funding account {address}: {err}")
154
+ raise err
155
+
156
+ def refund(self, refund_txn_id: str) -> None:
157
+ """
158
+ Register a refund for a transaction with the dispenser API
159
+ """
160
+
161
+ try:
162
+ self._process_dispenser_request(
163
+ auth_token=self.auth_token,
164
+ url_suffix="refund",
165
+ data={"refundTransactionID": refund_txn_id},
166
+ method="POST",
167
+ )
168
+
169
+ except Exception as err:
170
+ logger.exception(f"Error issuing refund for txn_id {refund_txn_id}: {err}")
171
+ raise err
172
+
173
+ def get_limit(
174
+ self,
175
+ address: str,
176
+ ) -> DispenserLimitResponse:
177
+ """
178
+ Get current limit for an account with Algos from the dispenser API
179
+ """
180
+
181
+ try:
182
+ response = self._process_dispenser_request(
183
+ auth_token=self.auth_token,
184
+ url_suffix=f"fund/{DISPENSER_ASSETS[DispenserAssetName.ALGO].asset_id}/limit",
185
+ method="GET",
186
+ )
187
+ content = response.json()
188
+
189
+ return DispenserLimitResponse(amount=content["amount"])
190
+ except Exception as err:
191
+ logger.exception(f"Error setting limit for account {address}: {err}")
192
+ raise err
algokit_utils/common.py CHANGED
@@ -1,28 +1,10 @@
1
- """
2
- This module contains common classes and methods that are reused in more than one file.
3
- """
1
+ import warnings
4
2
 
5
- import base64
6
- import typing
3
+ warnings.warn(
4
+ "The legacy v2 common module is deprecated and will be removed in a future version. "
5
+ "Refer to `CompiledTeal` class from `algokit_utils` instead.",
6
+ DeprecationWarning,
7
+ stacklevel=2,
8
+ )
7
9
 
8
- from algosdk.source_map import SourceMap
9
-
10
- from algokit_utils import deploy
11
-
12
- if typing.TYPE_CHECKING:
13
- from algosdk.v2client.algod import AlgodClient
14
-
15
-
16
- class Program:
17
- """A compiled TEAL program"""
18
-
19
- def __init__(self, program: str, client: "AlgodClient"):
20
- """
21
- Fully compile the program source to binary and generate a
22
- source map for matching pc to line number
23
- """
24
- self.teal = program
25
- result: dict = client.compile(deploy.strip_comments(self.teal), source_map=True)
26
- self.raw_binary = base64.b64decode(result["result"])
27
- self.binary_hash: str = result["hash"]
28
- self.source_map = SourceMap(result["sourcemap"])
10
+ from algokit_utils._legacy_v2.common import * # noqa: F403, E402
algokit_utils/config.py CHANGED
@@ -2,14 +2,57 @@ import logging
2
2
  import os
3
3
  from collections.abc import Callable
4
4
  from pathlib import Path
5
-
6
- logger = logging.getLogger(__name__)
5
+ from typing import Any
7
6
 
8
7
  # Environment variable to override the project root
9
8
  ALGOKIT_PROJECT_ROOT = os.getenv("ALGOKIT_PROJECT_ROOT")
10
9
  ALGOKIT_CONFIG_FILENAME = ".algokit.toml"
11
10
 
12
11
 
12
+ class AlgoKitLogger:
13
+ def __init__(self) -> None:
14
+ self._logger = logging.getLogger("algokit")
15
+ self._setup_logger()
16
+
17
+ def _setup_logger(self) -> None:
18
+ formatter = logging.Formatter("%(levelname)s: %(message)s")
19
+ handler = logging.StreamHandler()
20
+ handler.setFormatter(formatter)
21
+ self._logger.addHandler(handler)
22
+ self._logger.setLevel(logging.INFO)
23
+
24
+ def _get_logger(self, *, suppress_log: bool = False) -> logging.Logger:
25
+ if suppress_log:
26
+ null_logger = logging.getLogger("null")
27
+ null_logger.addHandler(logging.NullHandler())
28
+ return null_logger
29
+ return self._logger
30
+
31
+ def error(self, message: str, *args: Any, suppress_log: bool = False, **kwargs: Any) -> None:
32
+ """Log an error message, optionally suppressing output"""
33
+ self._get_logger(suppress_log=suppress_log).error(message, *args, **kwargs)
34
+
35
+ def exception(self, message: str, *args: Any, suppress_log: bool = False, **kwargs: Any) -> None:
36
+ """Log an exception message, optionally suppressing output"""
37
+ self._get_logger(suppress_log=suppress_log).exception(message, *args, **kwargs)
38
+
39
+ def warning(self, message: str, *args: Any, suppress_log: bool = False, **kwargs: Any) -> None:
40
+ """Log a warning message, optionally suppressing output"""
41
+ self._get_logger(suppress_log=suppress_log).warning(message, *args, **kwargs)
42
+
43
+ def info(self, message: str, *args: Any, suppress_log: bool = False, **kwargs: Any) -> None:
44
+ """Log an info message, optionally suppressing output"""
45
+ self._get_logger(suppress_log=suppress_log).info(message, *args, **kwargs)
46
+
47
+ def debug(self, message: str, *args: Any, suppress_log: bool = False, **kwargs: Any) -> None:
48
+ """Log a debug message, optionally suppressing output"""
49
+ self._get_logger(suppress_log=suppress_log).debug(message, *args, **kwargs)
50
+
51
+ def verbose(self, message: str, *args: Any, suppress_log: bool = False, **kwargs: Any) -> None:
52
+ """Log a verbose message (maps to debug), optionally suppressing output"""
53
+ self._get_logger(suppress_log=suppress_log).debug(message, *args, **kwargs)
54
+
55
+
13
56
  class UpdatableConfig:
14
57
  """Class to manage and update configuration settings for the AlgoKit project.
15
58
 
@@ -19,26 +62,33 @@ class UpdatableConfig:
19
62
  trace_all (bool): Indicates whether to trace all operations.
20
63
  trace_buffer_size_mb (int): The size of the trace buffer in megabytes.
21
64
  max_search_depth (int): The maximum depth to search for a specific file.
65
+ populate_app_call_resources (bool): Indicates whether to populate app call resources.
22
66
  """
23
67
 
24
68
  def __init__(self) -> None:
69
+ self._logger = AlgoKitLogger()
25
70
  self._debug: bool = False
26
71
  self._project_root: Path | None = None
27
72
  self._trace_all: bool = False
28
73
  self._trace_buffer_size_mb: int | float = 256 # megabytes
29
74
  self._max_search_depth: int = 10
75
+ self._populate_app_call_resources: bool = False
30
76
  self._configure_project_root()
31
77
 
32
78
  def _configure_project_root(self) -> None:
33
79
  """Configures the project root by searching for a specific file within a depth limit."""
34
80
  current_path = Path(__file__).resolve()
35
81
  for _ in range(self._max_search_depth):
36
- logger.debug(f"Searching in: {current_path}")
82
+ self.logger.debug(f"Searching in: {current_path}")
37
83
  if (current_path / ALGOKIT_CONFIG_FILENAME).exists():
38
84
  self._project_root = current_path
39
85
  break
40
86
  current_path = current_path.parent
41
87
 
88
+ @property
89
+ def logger(self) -> AlgoKitLogger:
90
+ return self._logger
91
+
42
92
  @property
43
93
  def debug(self) -> bool:
44
94
  """Returns the debug status."""
@@ -59,6 +109,10 @@ class UpdatableConfig:
59
109
  """Returns the size of the trace buffer in megabytes."""
60
110
  return self._trace_buffer_size_mb
61
111
 
112
+ @property
113
+ def populate_app_call_resource(self) -> bool:
114
+ return self._populate_app_call_resources
115
+
62
116
  def with_debug(self, func: Callable[[], str | None]) -> None:
63
117
  """Executes a function with debug mode temporarily enabled."""
64
118
  original_debug = self._debug
@@ -68,14 +122,15 @@ class UpdatableConfig:
68
122
  finally:
69
123
  self._debug = original_debug
70
124
 
71
- def configure( # noqa: PLR0913
125
+ def configure(
72
126
  self,
73
127
  *,
74
- debug: bool,
128
+ debug: bool | None = None,
75
129
  project_root: Path | None = None,
76
130
  trace_all: bool = False,
77
131
  trace_buffer_size_mb: float = 256,
78
132
  max_search_depth: int = 10,
133
+ populate_app_call_resources: bool = False,
79
134
  ) -> None:
80
135
  """
81
136
  Configures various settings for the application.
@@ -85,28 +140,26 @@ class UpdatableConfig:
85
140
  If you are executing the config from an algokit compliant project, you can simply call
86
141
  `config.configure(debug=True)`.
87
142
 
88
- Args:
89
- debug (bool): Indicates whether debug mode is enabled.
90
- project_root (Path | None, optional): The path to the project root directory. Defaults to None.
91
- trace_all (bool, optional): Indicates whether to trace all operations. Defaults to False. Which implies that
143
+ :param debug: Indicates whether debug mode is enabled.
144
+ :param project_root: The path to the project root directory. Defaults to None.
145
+ :param trace_all: Indicates whether to trace all operations. Defaults to False. Which implies that
92
146
  only the operations that are failed will be traced by default.
93
- trace_buffer_size_mb (float, optional): The size of the trace buffer in megabytes. Defaults to 512mb.
94
- max_search_depth (int, optional): The maximum depth to search for a specific file. Defaults to 10.
95
-
96
- Returns:
97
- None
147
+ :param trace_buffer_size_mb: The size of the trace buffer in megabytes. Defaults to 256
148
+ :param max_search_depth: The maximum depth to search for a specific file. Defaults to 10
149
+ :param populate_app_call_resources: Indicates whether to populate app call resources. Defaults to False
98
150
  """
99
151
 
100
- self._debug = debug
101
-
102
- if project_root:
152
+ if debug is not None:
153
+ self._debug = debug
154
+ if project_root is not None:
103
155
  self._project_root = project_root.resolve(strict=True)
104
- elif debug and ALGOKIT_PROJECT_ROOT:
156
+ elif debug is not None and ALGOKIT_PROJECT_ROOT:
105
157
  self._project_root = Path(ALGOKIT_PROJECT_ROOT).resolve(strict=True)
106
158
 
107
159
  self._trace_all = trace_all
108
160
  self._trace_buffer_size_mb = trace_buffer_size_mb
109
161
  self._max_search_depth = max_search_depth
162
+ self._populate_app_call_resources = populate_app_call_resources
110
163
 
111
164
 
112
165
  config = UpdatableConfig()