algorand-python-testing 0.0.0b1__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 (52) hide show
  1. algopy/__init__.py +58 -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 +55 -0
  8. algopy_testing/arc4.py +1533 -0
  9. algopy_testing/constants.py +22 -0
  10. algopy_testing/context.py +1194 -0
  11. algopy_testing/decorators/__init__.py +0 -0
  12. algopy_testing/decorators/abimethod.py +204 -0
  13. algopy_testing/decorators/baremethod.py +83 -0
  14. algopy_testing/decorators/subroutine.py +9 -0
  15. algopy_testing/enums.py +42 -0
  16. algopy_testing/gtxn.py +261 -0
  17. algopy_testing/itxn.py +665 -0
  18. algopy_testing/models/__init__.py +31 -0
  19. algopy_testing/models/account.py +128 -0
  20. algopy_testing/models/application.py +72 -0
  21. algopy_testing/models/asset.py +109 -0
  22. algopy_testing/models/block.py +34 -0
  23. algopy_testing/models/box.py +158 -0
  24. algopy_testing/models/contract.py +82 -0
  25. algopy_testing/models/gitxn.py +42 -0
  26. algopy_testing/models/global_values.py +72 -0
  27. algopy_testing/models/gtxn.py +56 -0
  28. algopy_testing/models/itxn.py +85 -0
  29. algopy_testing/models/logicsig.py +44 -0
  30. algopy_testing/models/template_variable.py +23 -0
  31. algopy_testing/models/transactions.py +158 -0
  32. algopy_testing/models/txn.py +113 -0
  33. algopy_testing/models/unsigned_builtins.py +36 -0
  34. algopy_testing/op.py +1098 -0
  35. algopy_testing/primitives/__init__.py +6 -0
  36. algopy_testing/primitives/biguint.py +148 -0
  37. algopy_testing/primitives/bytes.py +174 -0
  38. algopy_testing/primitives/string.py +68 -0
  39. algopy_testing/primitives/uint64.py +213 -0
  40. algopy_testing/protocols.py +18 -0
  41. algopy_testing/py.typed +0 -0
  42. algopy_testing/state/__init__.py +4 -0
  43. algopy_testing/state/global_state.py +73 -0
  44. algopy_testing/state/local_state.py +54 -0
  45. algopy_testing/utilities/__init__.py +3 -0
  46. algopy_testing/utilities/budget.py +23 -0
  47. algopy_testing/utilities/log.py +55 -0
  48. algopy_testing/utils.py +249 -0
  49. algorand_python_testing-0.0.0b1.dist-info/METADATA +81 -0
  50. algorand_python_testing-0.0.0b1.dist-info/RECORD +52 -0
  51. algorand_python_testing-0.0.0b1.dist-info/WHEEL +4 -0
  52. algorand_python_testing-0.0.0b1.dist-info/licenses/LICENSE +14 -0
@@ -0,0 +1,56 @@
1
+ import typing
2
+ from collections.abc import Callable
3
+
4
+
5
+ class _GTxn:
6
+ def __getattr__(self, name: str) -> Callable[[int], typing.Any]:
7
+ from algopy_testing.context import get_test_context
8
+
9
+ context = get_test_context()
10
+ if not context:
11
+ raise ValueError(
12
+ "Test context is not initialized! Use `with algopy_testing_context()` to access "
13
+ "the context manager."
14
+ )
15
+
16
+ txn_group = context.get_transaction_group()
17
+ if not txn_group:
18
+ raise ValueError(
19
+ "No group transactions found in the context! Use `with algopy_testing_context()` "
20
+ "to access the context manager."
21
+ )
22
+
23
+ return lambda *args: self._handle_field(txn_group, name, *args)
24
+
25
+ def _handle_field(
26
+ self, txn_group: list[typing.Any], name: str, *args: typing.Any
27
+ ) -> typing.Any:
28
+ if len(args) == 1:
29
+ return self._get_value(txn_group, name, args[0])
30
+ elif len(args) == 2:
31
+ return self._get_value(txn_group, name, args[0], args[1])
32
+ else:
33
+ raise ValueError(f"Invalid number of arguments for field '{name}'")
34
+
35
+ # TODO: refine mapping
36
+ def _map_fields(self, name: str) -> str:
37
+ field_mapping = {"type": "type_bytes", "type_enum": "type", "application_args": "app_args"}
38
+ return field_mapping.get(name, name)
39
+
40
+ def _get_value(
41
+ self, txn_group: list[typing.Any], name: str, index: int, second_arg: typing.Any = None
42
+ ) -> object:
43
+ if index >= len(txn_group):
44
+ raise IndexError("Transaction index out of range")
45
+ gtxn = txn_group[index]
46
+ mapped_name = self._map_fields(name)
47
+ value = getattr(gtxn, mapped_name)
48
+
49
+ if callable(value) and second_arg is not None:
50
+ return value(second_arg)
51
+ elif value is None:
52
+ raise ValueError(f"'{name}' is not defined for {type(gtxn).__name__}")
53
+ return value
54
+
55
+
56
+ GTxn = _GTxn()
@@ -0,0 +1,85 @@
1
+ import typing
2
+ from collections.abc import Callable
3
+
4
+
5
+ class _ITxn:
6
+ def __getattr__(self, name: str) -> Callable[[], typing.Any]:
7
+ from algopy_testing.context import get_test_context
8
+
9
+ context = get_test_context()
10
+ if not context:
11
+ raise ValueError(
12
+ "Test context is not initialized! Use `with algopy_testing_context()` to access "
13
+ "the context manager."
14
+ )
15
+ if not context._inner_transaction_groups:
16
+ raise ValueError(
17
+ "No inner transaction found in the context! Use `with algopy_testing_context()` "
18
+ "to access the context manager."
19
+ )
20
+ last_itxn_group = context._inner_transaction_groups[-1]
21
+
22
+ if not last_itxn_group:
23
+ raise ValueError("No inner transaction found in the testing context!")
24
+
25
+ last_itxn = last_itxn_group[-1]
26
+
27
+ value = getattr(last_itxn, name)
28
+ if value is None:
29
+ raise ValueError(f"'{name}' is not defined for {type(last_itxn).__name__} ")
30
+ # mimic the static functions on ITxn with a lambda
31
+ return lambda: value
32
+
33
+
34
+ ITxn = _ITxn()
35
+
36
+
37
+ class _ITxnCreate:
38
+ @classmethod
39
+ def begin(cls) -> None:
40
+ from algopy_testing.context import get_test_context
41
+
42
+ context = get_test_context()
43
+ context._constructing_inner_transaction_group = []
44
+
45
+ @classmethod
46
+ def next(cls) -> None:
47
+ from algopy_testing.context import get_test_context
48
+
49
+ context = get_test_context()
50
+ if context._constructing_inner_transaction:
51
+ context._constructing_inner_transaction_group.append(
52
+ context._constructing_inner_transaction
53
+ )
54
+ context._constructing_inner_transaction = None
55
+
56
+ @classmethod
57
+ def submit(cls) -> None:
58
+ from algopy_testing.context import get_test_context
59
+
60
+ context = get_test_context()
61
+ if context._constructing_inner_transaction:
62
+ context._constructing_inner_transaction_group.append(
63
+ context._constructing_inner_transaction
64
+ )
65
+ context._constructing_inner_transaction = None
66
+ context._inner_transaction_groups.append(context._constructing_inner_transaction_group)
67
+ context._constructing_inner_transaction_group = []
68
+
69
+ def __setattr__(self, name: str, value: typing.Any) -> None:
70
+ if name.startswith("set_"):
71
+ field = name[4:] # Remove 'set_' prefix
72
+ self._set_field(field, value)
73
+ else:
74
+ super().__setattr__(name, value)
75
+
76
+ def _set_field(self, field: str, value: typing.Any) -> None:
77
+ from algopy_testing.context import get_test_context
78
+
79
+ context = get_test_context()
80
+ if not context._constructing_inner_transaction:
81
+ raise ValueError("No active inner transaction. Call ITxnCreate.begin() first.")
82
+ setattr(context._constructing_inner_transaction, field, value)
83
+
84
+
85
+ ITxnCreate = _ITxnCreate()
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from functools import wraps
5
+
6
+ if typing.TYPE_CHECKING:
7
+ from collections.abc import Callable
8
+
9
+ import algopy
10
+
11
+
12
+ class LogicSig:
13
+ """A logic signature"""
14
+
15
+ def __init__(self, func: Callable[[], bool | algopy.UInt64], name: str | None = None):
16
+ self.func = func
17
+ self.name = name or func.__name__
18
+
19
+
20
+ @typing.overload
21
+ def logicsig(sub: Callable[[], bool | algopy.UInt64], /) -> LogicSig: ...
22
+
23
+
24
+ @typing.overload
25
+ def logicsig(*, name: str) -> Callable[[Callable[[], bool | algopy.UInt64]], LogicSig]: ...
26
+
27
+
28
+ def logicsig(
29
+ sub: Callable[[], bool | algopy.UInt64] | None = None, *, name: str | None = None
30
+ ) -> algopy.LogicSig | Callable[[Callable[[], bool | algopy.UInt64]], LogicSig]:
31
+ """Decorator to indicate a function is a logic signature"""
32
+ import algopy
33
+
34
+ def decorator(func: Callable[[], bool | algopy.UInt64]) -> algopy.LogicSig:
35
+ @wraps(func)
36
+ def wrapper() -> bool | algopy.UInt64:
37
+ return func()
38
+
39
+ return algopy.LogicSig(wrapper, name=name)
40
+
41
+ if sub is None:
42
+ return decorator
43
+ else:
44
+ return decorator(sub)
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ _T = typing.TypeVar("_T")
6
+
7
+
8
+ class TemplateVarGeneric:
9
+ def __getitem__(self, type_: type[_T]) -> typing.Callable[[str], typing.Any]:
10
+ from algopy_testing.context import get_test_context
11
+
12
+ context = get_test_context()
13
+
14
+ def create_template_var(variable_name: str) -> typing.Any:
15
+ if variable_name not in context._template_vars:
16
+ raise ValueError(f"Template variable {variable_name} not found in test context!")
17
+ return context._template_vars[variable_name]
18
+
19
+ return create_template_var
20
+
21
+
22
+ TemplateVar: TemplateVarGeneric = TemplateVarGeneric()
23
+ """Template variables can be used to represent a placeholder for a deploy-time provided value."""
@@ -0,0 +1,158 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from typing import TYPE_CHECKING, TypedDict
5
+
6
+ if TYPE_CHECKING:
7
+ import algopy
8
+
9
+
10
+ class _TransactionCoreFields(TypedDict, total=False):
11
+ sender: algopy.Account
12
+ fee: algopy.UInt64
13
+ first_valid: algopy.UInt64
14
+ first_valid_time: algopy.UInt64
15
+ last_valid: algopy.UInt64
16
+ note: algopy.Bytes
17
+ lease: algopy.Bytes
18
+ txn_id: algopy.Bytes
19
+ rekey_to: algopy.Account
20
+
21
+
22
+ class _TransactionBaseFields(_TransactionCoreFields, total=False):
23
+ type: algopy.TransactionType
24
+ type_bytes: algopy.Bytes
25
+
26
+
27
+ class _AssetTransferBaseFields(TypedDict, total=False):
28
+ xfer_asset: algopy.Asset
29
+ asset_amount: algopy.UInt64
30
+ asset_sender: algopy.Account
31
+ asset_receiver: algopy.Account
32
+ asset_close_to: algopy.Account
33
+
34
+
35
+ class _PaymentBaseFields(TypedDict, total=False):
36
+ receiver: algopy.Account
37
+ amount: algopy.UInt64
38
+ close_remainder_to: algopy.Account
39
+
40
+
41
+ class _AssetFreezeBaseFields(TypedDict, total=False):
42
+ freeze_asset: algopy.Asset
43
+ freeze_account: algopy.Account
44
+ frozen: bool
45
+
46
+
47
+ class _AssetConfigBaseFields(TypedDict, total=False):
48
+ config_asset: algopy.Asset
49
+ total: algopy.UInt64
50
+ decimals: algopy.UInt64
51
+ default_frozen: bool
52
+ unit_name: algopy.Bytes
53
+ asset_name: algopy.Bytes
54
+ url: algopy.Bytes
55
+ metadata_hash: algopy.Bytes
56
+ manager: algopy.Account
57
+ reserve: algopy.Account
58
+ freeze: algopy.Account
59
+ clawback: algopy.Account
60
+
61
+
62
+ class _ApplicationCallCoreFields(TypedDict, total=False):
63
+ app_id: algopy.Application
64
+ on_completion: algopy.OnCompleteAction
65
+ num_app_args: algopy.UInt64
66
+ num_accounts: algopy.UInt64
67
+ approval_program: algopy.Bytes
68
+ clear_state_program: algopy.Bytes
69
+ num_assets: algopy.UInt64
70
+ num_apps: algopy.UInt64
71
+ global_num_uint: algopy.UInt64
72
+ global_num_bytes: algopy.UInt64
73
+ local_num_uint: algopy.UInt64
74
+ local_num_bytes: algopy.UInt64
75
+ extra_program_pages: algopy.UInt64
76
+ last_log: algopy.Bytes
77
+ num_approval_program_pages: algopy.UInt64
78
+ num_clear_state_program_pages: algopy.UInt64
79
+
80
+
81
+ class _ApplicationCallBaseFields(_ApplicationCallCoreFields, total=False):
82
+ app_args: typing.Callable[[algopy.UInt64 | int], algopy.Bytes]
83
+ accounts: typing.Callable[[algopy.UInt64 | int], algopy.Account]
84
+ assets: typing.Callable[[algopy.UInt64 | int], algopy.Asset]
85
+ apps: typing.Callable[[algopy.UInt64 | int], algopy.Application]
86
+ approval_program_pages: typing.Callable[[algopy.UInt64 | int], algopy.Bytes]
87
+ clear_state_program_pages: typing.Callable[[algopy.UInt64 | int], algopy.Bytes]
88
+
89
+
90
+ class _ApplicationCallFields(_TransactionBaseFields, _ApplicationCallCoreFields, total=False):
91
+ pass
92
+
93
+
94
+ class _KeyRegistrationBaseFields(TypedDict, total=False):
95
+ vote_key: algopy.Bytes
96
+ selection_key: algopy.Bytes
97
+ vote_first: algopy.UInt64
98
+ vote_last: algopy.UInt64
99
+ vote_key_dilution: algopy.UInt64
100
+ non_participation: bool
101
+ state_proof_key: algopy.Bytes
102
+
103
+
104
+ class _TransactionFields(
105
+ _TransactionBaseFields,
106
+ _PaymentBaseFields,
107
+ _KeyRegistrationBaseFields,
108
+ _AssetConfigBaseFields,
109
+ _AssetTransferBaseFields,
110
+ _AssetFreezeBaseFields,
111
+ _ApplicationCallBaseFields,
112
+ total=False,
113
+ ):
114
+ pass
115
+
116
+
117
+ class AssetTransferFields(_TransactionBaseFields, _AssetTransferBaseFields, total=False):
118
+ pass
119
+
120
+
121
+ class PaymentFields(_TransactionBaseFields, _PaymentBaseFields, total=False):
122
+ pass
123
+
124
+
125
+ class AssetFreezeFields(_TransactionBaseFields, _AssetFreezeBaseFields, total=False):
126
+ pass
127
+
128
+
129
+ class AssetConfigFields(_TransactionBaseFields, _AssetConfigBaseFields, total=False):
130
+ pass
131
+
132
+
133
+ class ApplicationCallFields(_TransactionBaseFields, _ApplicationCallBaseFields, total=False):
134
+ pass
135
+
136
+
137
+ class KeyRegistrationFields(_TransactionBaseFields, _KeyRegistrationBaseFields, total=False):
138
+ pass
139
+
140
+
141
+ __all__ = [
142
+ "ApplicationCallFields",
143
+ "AssetConfigFields",
144
+ "AssetFreezeFields",
145
+ "AssetTransferFields",
146
+ "KeyRegistrationFields",
147
+ "PaymentFields",
148
+ "_ApplicationCallBaseFields",
149
+ "_ApplicationCallFields",
150
+ "_AssetConfigBaseFields",
151
+ "_AssetFreezeBaseFields",
152
+ "_AssetTransferBaseFields",
153
+ "_KeyRegistrationBaseFields",
154
+ "_PaymentBaseFields",
155
+ "_TransactionBaseFields",
156
+ "_TransactionCoreFields",
157
+ "_TransactionFields",
158
+ ]
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from typing import TYPE_CHECKING, TypedDict, TypeVar
5
+
6
+ if TYPE_CHECKING:
7
+ import algopy
8
+
9
+
10
+ T = TypeVar("T")
11
+
12
+
13
+ class TxnFields(TypedDict, total=False):
14
+ sender: algopy.Account
15
+ fee: algopy.UInt64
16
+ first_valid: algopy.UInt64
17
+ first_valid_time: algopy.UInt64
18
+ last_valid: algopy.UInt64
19
+ note: algopy.Bytes
20
+ lease: algopy.Bytes
21
+ receiver: algopy.Account
22
+ amount: algopy.UInt64
23
+ close_remainder_to: algopy.Account
24
+ vote_pk: algopy.Bytes
25
+ selection_pk: algopy.Bytes
26
+ vote_first: algopy.UInt64
27
+ vote_last: algopy.UInt64
28
+ vote_key_dilution: algopy.UInt64
29
+ type: algopy.Bytes
30
+ type_enum: algopy.UInt64
31
+ xfer_asset: algopy.Asset
32
+ asset_amount: algopy.UInt64
33
+ asset_sender: algopy.Account
34
+ asset_receiver: algopy.Account
35
+ asset_close_to: algopy.Account
36
+ group_index: algopy.UInt64
37
+ tx_id: algopy.Bytes
38
+ application_id: algopy.Application
39
+ on_completion: algopy.UInt64
40
+ num_app_args: algopy.UInt64
41
+ num_accounts: algopy.UInt64
42
+ approval_program: algopy.Bytes
43
+ clear_state_program: algopy.Bytes
44
+ rekey_to: algopy.Account
45
+ config_asset: algopy.Asset
46
+ config_asset_total: algopy.UInt64
47
+ config_asset_decimals: algopy.UInt64
48
+ config_asset_default_frozen: bool
49
+ config_asset_unit_name: algopy.Bytes
50
+ config_asset_name: algopy.Bytes
51
+ config_asset_url: algopy.Bytes
52
+ config_asset_metadata_hash: algopy.Bytes
53
+ config_asset_manager: algopy.Account
54
+ config_asset_reserve: algopy.Account
55
+ config_asset_freeze: algopy.Account
56
+ config_asset_clawback: algopy.Account
57
+ freeze_asset: algopy.Asset
58
+ freeze_asset_account: algopy.Account
59
+ freeze_asset_frozen: bool
60
+ num_assets: algopy.UInt64
61
+ num_applications: algopy.UInt64
62
+ global_num_uint: algopy.UInt64
63
+ global_num_byte_slice: algopy.UInt64
64
+ local_num_uint: algopy.UInt64
65
+ local_num_byte_slice: algopy.UInt64
66
+ extra_program_pages: algopy.UInt64
67
+ nonparticipation: bool
68
+ num_logs: algopy.UInt64
69
+ created_asset_id: algopy.Asset
70
+ created_application_id: algopy.Application
71
+ last_log: algopy.Bytes
72
+ state_proof_pk: algopy.Bytes
73
+ num_approval_program_pages: algopy.UInt64
74
+ num_clear_state_program_pages: algopy.UInt64
75
+ application_args: tuple[algopy.Bytes, ...]
76
+ accounts: tuple[algopy.Account, ...]
77
+ assets: tuple[algopy.Asset, ...]
78
+ applications: tuple[algopy.Application, ...]
79
+ logs: tuple[algopy.Bytes, ...]
80
+ approval_program_pages: tuple[algopy.Bytes, ...]
81
+ clear_state_program_pages: tuple[algopy.Bytes, ...]
82
+
83
+
84
+ class _Txn:
85
+ def _map_fields(self, name: str) -> str:
86
+ field_mapping = {"type": "type_bytes", "type_enum": "type", "application_args": "app_args"}
87
+ return field_mapping.get(name, name)
88
+
89
+ def __getattr__(self, name: str) -> typing.Any:
90
+ from algopy_testing.context import get_test_context
91
+
92
+ context = get_test_context()
93
+ if not context:
94
+ raise ValueError(
95
+ "Test context is not initialized! Use `with algopy_testing_context()` to access "
96
+ "the context manager."
97
+ )
98
+ active_txn = context.get_active_transaction()
99
+ if name in context._txn_fields and context._txn_fields[name] is not None: # type: ignore[literal-required]
100
+ return context._txn_fields[name] # type: ignore[literal-required]
101
+ elif active_txn:
102
+ if context._active_transaction_index is not None and name == "group_index":
103
+ return context._active_transaction_index
104
+ return getattr(active_txn, self._map_fields(name))
105
+ else:
106
+ raise AttributeError(
107
+ f"'Txn' object has no value set for attribute named '{name}'. "
108
+ f"Use `context.patch_txn_fields({name}=your_value)` to set the value "
109
+ "in your test setup."
110
+ )
111
+
112
+
113
+ Txn = _Txn()
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from algopy_testing.utils import as_int64
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from collections.abc import Iterable, Iterator, Reversible
9
+
10
+ import algopy
11
+
12
+
13
+ class urange: # noqa: N801
14
+ _value: range
15
+
16
+ def __init__(self, *args: int | algopy.UInt64) -> None:
17
+ self._value = range(*[as_int64(arg) for arg in args])
18
+
19
+ def __iter__(self) -> Iterator[algopy.UInt64]:
20
+ import algopy
21
+
22
+ return map(algopy.UInt64, self._value)
23
+
24
+ def __reversed__(self) -> Iterator[algopy.UInt64]:
25
+ import algopy
26
+
27
+ return map(algopy.UInt64, reversed(self._value))
28
+
29
+
30
+ _T = typing.TypeVar("_T")
31
+
32
+
33
+ def uenumerate(iterable: Iterable[_T]) -> Reversible[tuple[algopy.UInt64, _T]]:
34
+ import algopy
35
+
36
+ return [(algopy.UInt64(i), v) for i, v in enumerate(iterable)]