algorand-python-testing 0.1.0__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.
Files changed (41) hide show
  1. algopy/__init__.py +42 -0
  2. algopy/arc4.py +1 -0
  3. algopy/gtxn.py +1 -0
  4. algopy/itxn.py +1 -0
  5. algopy/op.py +1 -0
  6. algopy/py.typed +0 -0
  7. algopy_testing/__init__.py +47 -0
  8. algopy_testing/arc4.py +1222 -0
  9. algopy_testing/constants.py +17 -0
  10. algopy_testing/context.py +769 -0
  11. algopy_testing/decorators/__init__.py +0 -0
  12. algopy_testing/decorators/abimethod.py +146 -0
  13. algopy_testing/decorators/subroutine.py +9 -0
  14. algopy_testing/enums.py +39 -0
  15. algopy_testing/gtxn.py +239 -0
  16. algopy_testing/itxn.py +353 -0
  17. algopy_testing/models/__init__.py +23 -0
  18. algopy_testing/models/account.py +128 -0
  19. algopy_testing/models/application.py +72 -0
  20. algopy_testing/models/asset.py +109 -0
  21. algopy_testing/models/contract.py +69 -0
  22. algopy_testing/models/global_values.py +67 -0
  23. algopy_testing/models/gtxn.py +40 -0
  24. algopy_testing/models/itxn.py +34 -0
  25. algopy_testing/models/transactions.py +158 -0
  26. algopy_testing/models/txn.py +111 -0
  27. algopy_testing/models/unsigned_builtins.py +15 -0
  28. algopy_testing/op.py +639 -0
  29. algopy_testing/primitives/__init__.py +6 -0
  30. algopy_testing/primitives/biguint.py +147 -0
  31. algopy_testing/primitives/bytes.py +173 -0
  32. algopy_testing/primitives/string.py +67 -0
  33. algopy_testing/primitives/uint64.py +210 -0
  34. algopy_testing/py.typed +0 -0
  35. algopy_testing/state/__init__.py +4 -0
  36. algopy_testing/state/global_state.py +73 -0
  37. algopy_testing/state/local_state.py +54 -0
  38. algopy_testing/utils.py +156 -0
  39. algorand_python_testing-0.1.0.dist-info/METADATA +29 -0
  40. algorand_python_testing-0.1.0.dist-info/RECORD +41 -0
  41. algorand_python_testing-0.1.0.dist-info/WHEEL +4 -0
File without changes
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import typing
5
+
6
+ from algopy_testing.constants import UINT64_SIZE
7
+ from algopy_testing.utils import int_to_bytes
8
+
9
+ if typing.TYPE_CHECKING:
10
+ import algopy
11
+
12
+ from algopy_testing.context import AlgopyTestContext
13
+
14
+
15
+ if typing.TYPE_CHECKING:
16
+ import algopy
17
+
18
+
19
+ _P = typing.ParamSpec("_P")
20
+ _R = typing.TypeVar("_R")
21
+
22
+
23
+ def _extract_refs_from_args(
24
+ args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any], ref_type: type
25
+ ) -> list[typing.Any]:
26
+ import algopy
27
+
28
+ refs: list[typing.Any] = []
29
+ for arg in list(args) + list(kwargs.values()):
30
+ match arg:
31
+ case algopy.gtxn.TransactionBase() if ref_type is algopy.gtxn.TransactionBase:
32
+ refs.append(arg)
33
+ case algopy.Account() if ref_type is algopy.Account:
34
+ refs.append(arg)
35
+ case algopy.Asset() if ref_type is algopy.Asset:
36
+ refs.append(arg)
37
+ case algopy.Application() if ref_type is algopy.Application:
38
+ refs.append(arg)
39
+ case (
40
+ algopy.Bytes() | algopy.String() | algopy.BigUInt() | algopy.UInt64() | int()
41
+ ) if ref_type is algopy.Bytes:
42
+ refs.append(_extract_bytes(arg))
43
+ case _:
44
+ continue
45
+ return refs
46
+
47
+
48
+ def _extract_bytes(value: object) -> algopy.Bytes:
49
+ import algopy
50
+
51
+ if isinstance(value, algopy.Bytes):
52
+ return value
53
+ if isinstance(value, (algopy.String | algopy.BigUInt)):
54
+ return value.bytes
55
+ if isinstance(value, (algopy.UInt64 | int)):
56
+ return algopy.Bytes(int_to_bytes(int(value), UINT64_SIZE))
57
+ raise ValueError(f"Unsupported type: {type(value).__name__}")
58
+
59
+
60
+ def _extract_and_append_txn_to_context(
61
+ context: AlgopyTestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
62
+ ) -> None:
63
+ import algopy
64
+
65
+ context.add_transactions(_extract_refs_from_args(args, kwargs, algopy.gtxn.TransactionBase))
66
+ existing_indexes = {int(txn.group_index) for txn in context.get_transaction_group()}
67
+ new_group_txn_index = max(existing_indexes, default=-1) + 1
68
+
69
+ context.add_transactions(
70
+ [
71
+ context.any_app_call_txn(
72
+ group_index=1,
73
+ sender=context.default_creator,
74
+ app_id=context.default_application,
75
+ accounts=_extract_refs_from_args(args, kwargs, algopy.Account),
76
+ assets=_extract_refs_from_args(args, kwargs, algopy.Asset),
77
+ apps=_extract_refs_from_args(args, kwargs, algopy.Application),
78
+ app_args=_extract_refs_from_args(args, kwargs, algopy.Bytes),
79
+ approval_program_pages=_extract_refs_from_args(args, kwargs, algopy.Bytes),
80
+ clear_state_program_pages=_extract_refs_from_args(args, kwargs, algopy.Bytes),
81
+ ),
82
+ ]
83
+ )
84
+ context.set_active_transaction_index(new_group_txn_index)
85
+
86
+
87
+ @typing.overload
88
+ def abimethod(fn: typing.Callable[_P, _R], /) -> typing.Callable[_P, _R]: ...
89
+
90
+
91
+ @typing.overload
92
+ def abimethod(
93
+ *,
94
+ name: str | None = None,
95
+ create: typing.Literal["allow", "require", "disallow"] = "disallow",
96
+ allow_actions: typing.Sequence[
97
+ algopy.OnCompleteAction
98
+ | typing.Literal[
99
+ "NoOp",
100
+ "OptIn",
101
+ "CloseOut",
102
+ # ClearState has its own program, so is not considered as part of ARC4 routing
103
+ "UpdateApplication",
104
+ "DeleteApplication",
105
+ ]
106
+ ] = ("NoOp",),
107
+ readonly: bool = False,
108
+ default_args: typing.Mapping[str, str | object] = {},
109
+ ) -> typing.Callable[[typing.Callable[_P, _R]], typing.Callable[_P, _R]]: ...
110
+
111
+
112
+ def abimethod( # noqa: PLR0913
113
+ fn: typing.Callable[_P, _R] | None = None,
114
+ *,
115
+ name: str | None = None, # noqa: ARG001
116
+ create: typing.Literal["allow", "require", "disallow"] = "disallow", # noqa: ARG001
117
+ allow_actions: typing.Sequence[ # noqa: ARG001
118
+ algopy.OnCompleteAction
119
+ | typing.Literal[
120
+ "NoOp",
121
+ "OptIn",
122
+ "CloseOut",
123
+ "UpdateApplication",
124
+ "DeleteApplication",
125
+ ]
126
+ ] = ("NoOp",),
127
+ readonly: bool = False, # noqa: ARG001
128
+ default_args: typing.Mapping[str, str | object] | None = None, # noqa: ARG001
129
+ ) -> typing.Callable[[typing.Callable[_P, _R]], typing.Callable[_P, _R]] | typing.Callable[_P, _R]:
130
+ def decorator(fn: typing.Callable[_P, _R]) -> typing.Callable[_P, _R]:
131
+ @functools.wraps(fn)
132
+ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
133
+ from algopy_testing import get_test_context
134
+
135
+ context = get_test_context()
136
+ if context is None or context._active_transaction_index is not None:
137
+ return fn(*args, **kwargs)
138
+
139
+ _extract_and_append_txn_to_context(context, args, kwargs)
140
+ return fn(*args, **kwargs)
141
+
142
+ return wrapper
143
+
144
+ if fn is not None:
145
+ return decorator(fn)
146
+ return decorator
@@ -0,0 +1,9 @@
1
+ from collections.abc import Callable
2
+ from typing import ParamSpec, TypeVar
3
+
4
+ _P = ParamSpec("_P")
5
+ _R = TypeVar("_R")
6
+
7
+
8
+ def subroutine(sub: Callable[_P, _R]) -> Callable[_P, _R]:
9
+ return sub
@@ -0,0 +1,39 @@
1
+ from enum import Enum, IntEnum
2
+
3
+
4
+ class OnCompleteAction(Enum):
5
+ NoOp = 0
6
+ OptIn = 1
7
+ CloseOut = 2
8
+ ClearState = 3
9
+ UpdateApplication = 4
10
+ DeleteApplication = 5
11
+
12
+
13
+ class TransactionType(IntEnum):
14
+ Payment = 0
15
+ KeyRegistration = 1
16
+ AssetConfig = 2
17
+ AssetTransfer = 3
18
+ AssetFreeze = 4
19
+ ApplicationCall = 5
20
+
21
+
22
+ class ECDSA(Enum):
23
+ Secp256k1 = 0
24
+ Secp256r1 = 1
25
+
26
+
27
+ class VrfVerify(Enum):
28
+ VrfAlgorand = 0
29
+
30
+
31
+ class Base64(Enum):
32
+ URLEncoding = 0
33
+ StdEncoding = 1
34
+
35
+
36
+ __all__ = [
37
+ "OnCompleteAction",
38
+ "TransactionType",
39
+ ]
algopy_testing/gtxn.py ADDED
@@ -0,0 +1,239 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from dataclasses import dataclass, field
5
+ from typing import TYPE_CHECKING
6
+
7
+ from algopy_testing.models.transactions import (
8
+ ApplicationCallFields,
9
+ AssetConfigFields,
10
+ AssetFreezeFields,
11
+ AssetTransferFields,
12
+ KeyRegistrationFields,
13
+ PaymentFields,
14
+ _ApplicationCallBaseFields,
15
+ _AssetConfigBaseFields,
16
+ _AssetFreezeBaseFields,
17
+ _AssetTransferBaseFields,
18
+ _KeyRegistrationBaseFields,
19
+ _PaymentBaseFields,
20
+ _TransactionBaseFields,
21
+ )
22
+ from algopy_testing.utils import txn_type_to_bytes
23
+
24
+ if TYPE_CHECKING:
25
+ import algopy
26
+
27
+
28
+ @dataclass
29
+ class _GroupTransaction:
30
+ _fields: dict[str, typing.Any] = field(default_factory=dict)
31
+
32
+ group_index: algopy.UInt64 | int = field(default=0)
33
+
34
+ def __init__(self, group_index: algopy.UInt64 | int) -> None:
35
+ self.group_index = group_index
36
+ self._fields = {}
37
+
38
+
39
+ class TransactionFields(
40
+ _TransactionBaseFields,
41
+ _AssetTransferBaseFields,
42
+ _PaymentBaseFields,
43
+ _ApplicationCallBaseFields,
44
+ _KeyRegistrationBaseFields,
45
+ _AssetConfigBaseFields,
46
+ _AssetFreezeBaseFields,
47
+ total=False,
48
+ ):
49
+ pass
50
+
51
+
52
+ @dataclass
53
+ class TransactionBase(_GroupTransaction):
54
+ def __init__(
55
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[_TransactionBaseFields]
56
+ ):
57
+ super().__init__(group_index=group_index)
58
+ self._fields.update(kwargs)
59
+
60
+ def set(self, **kwargs: typing.Unpack[_TransactionBaseFields]) -> None:
61
+ """Updates inner transaction parameter values"""
62
+ self._fields.update(kwargs)
63
+
64
+ def __getattr__(self, name: str) -> object:
65
+ if name in _TransactionBaseFields.__annotations__:
66
+ return self._fields.get(name)
67
+
68
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
69
+
70
+
71
+ @dataclass
72
+ class AssetTransferTransaction(TransactionBase):
73
+ def __init__(
74
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[AssetTransferFields]
75
+ ):
76
+ super().__init__(group_index=group_index)
77
+ self._fields.update(kwargs)
78
+
79
+ def set(self, **kwargs: typing.Unpack[AssetTransferFields]) -> None:
80
+ """Updates inner transaction parameter values"""
81
+ self._fields.update(kwargs)
82
+
83
+ def __getattr__(self, name: str) -> typing.Any:
84
+ if name in AssetTransferFields.__annotations__:
85
+ return self._fields.get(name)
86
+
87
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
88
+
89
+
90
+ @dataclass
91
+ class PaymentTransaction(TransactionBase):
92
+ def __init__(self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[PaymentFields]):
93
+ import algopy
94
+
95
+ super().__init__(group_index=group_index)
96
+ self._fields.update(
97
+ {
98
+ "type": algopy.TransactionType.Payment,
99
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.Payment)),
100
+ **kwargs,
101
+ }
102
+ )
103
+
104
+ def __getattr__(self, name: str) -> typing.Any:
105
+ if name in PaymentFields.__annotations__:
106
+ return self._fields.get(name)
107
+
108
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
109
+
110
+
111
+ @dataclass
112
+ class ApplicationCallTransaction(TransactionBase):
113
+ def __init__(
114
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[ApplicationCallFields]
115
+ ):
116
+ import algopy
117
+
118
+ super().__init__(group_index=group_index)
119
+ self._fields.update(
120
+ {
121
+ "type": algopy.TransactionType.ApplicationCall,
122
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.ApplicationCall)),
123
+ **kwargs,
124
+ }
125
+ )
126
+
127
+ def __getattr__(self, name: str) -> typing.Any:
128
+ if name in ApplicationCallFields.__annotations__:
129
+ return self._fields.get(name)
130
+
131
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
132
+
133
+
134
+ class KeyRegistrationTransaction(TransactionBase):
135
+ def __init__(
136
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[KeyRegistrationFields]
137
+ ):
138
+ import algopy
139
+
140
+ super().__init__(group_index=group_index)
141
+ self._fields.update(
142
+ {
143
+ "type": algopy.TransactionType.KeyRegistration,
144
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.KeyRegistration)),
145
+ **kwargs,
146
+ }
147
+ )
148
+
149
+ def __getattr__(self, name: str) -> typing.Any:
150
+ if name in KeyRegistrationFields.__annotations__:
151
+ return self._fields.get(name)
152
+
153
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
154
+
155
+
156
+ @dataclass
157
+ class AssetConfigTransaction(TransactionBase):
158
+ def __init__(
159
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[AssetConfigFields]
160
+ ):
161
+ import algopy
162
+
163
+ super().__init__(group_index=group_index)
164
+ self._fields.update(
165
+ {
166
+ "type": algopy.TransactionType.AssetConfig,
167
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.AssetConfig)),
168
+ **kwargs,
169
+ }
170
+ )
171
+
172
+ def __getattr__(self, name: str) -> typing.Any:
173
+ if name in AssetConfigFields.__annotations__:
174
+ return self._fields.get(name)
175
+
176
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
177
+
178
+
179
+ @dataclass
180
+ class AssetFreezeTransaction(TransactionBase):
181
+ def __init__(
182
+ self,
183
+ group_index: algopy.UInt64 | int,
184
+ **kwargs: typing.Unpack[AssetFreezeFields],
185
+ ):
186
+ import algopy
187
+
188
+ super().__init__(group_index=group_index)
189
+ self._fields.update(
190
+ {
191
+ "type": algopy.TransactionType.AssetFreeze,
192
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.AssetFreeze)),
193
+ **kwargs,
194
+ }
195
+ )
196
+
197
+ def __getattr__(self, name: str) -> typing.Any:
198
+ if name in AssetFreezeFields.__annotations__:
199
+ return self._fields.get(name)
200
+
201
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
202
+
203
+
204
+ @dataclass
205
+ class Transaction(TransactionBase):
206
+ def __init__(
207
+ self,
208
+ group_index: algopy.UInt64 | int,
209
+ **kwargs: typing.Unpack[TransactionFields],
210
+ ):
211
+ if "type" not in kwargs:
212
+ raise ValueError("Transaction 'type' field is required")
213
+
214
+ super().__init__(group_index=group_index)
215
+ self._fields.update(kwargs)
216
+
217
+ def __getattr__(self, name: str) -> typing.Any:
218
+ if name in TransactionFields.__annotations__:
219
+ return self._fields.get(name)
220
+
221
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
222
+
223
+
224
+ __all__ = [
225
+ "TransactionBase",
226
+ "AssetTransferTransaction",
227
+ "PaymentTransaction",
228
+ "ApplicationCallTransaction",
229
+ "KeyRegistrationTransaction",
230
+ "AssetConfigTransaction",
231
+ "AssetFreezeTransaction",
232
+ "Transaction",
233
+ "PaymentFields",
234
+ "AssetTransferFields",
235
+ "ApplicationCallFields",
236
+ "KeyRegistrationFields",
237
+ "AssetConfigFields",
238
+ "AssetFreezeFields",
239
+ ]