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
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, final
5
+
6
+ if TYPE_CHECKING:
7
+ import algopy
8
+
9
+
10
+ @dataclass
11
+ class StateTotals:
12
+ global_uints: int | None = None
13
+ global_bytes: int | None = None
14
+ local_uints: int | None = None
15
+ local_bytes: int | None = None
16
+
17
+
18
+ class _ContractMeta(type):
19
+ def __call__(cls, *args: Any, **kwargs: dict[str, Any]) -> object:
20
+ from algopy import Contract
21
+
22
+ from algopy_testing.context import get_test_context
23
+
24
+ context = get_test_context()
25
+ instance = super().__call__(*args, **kwargs)
26
+
27
+ if context and isinstance(instance, Contract):
28
+ context._add_contract(instance)
29
+
30
+ return instance
31
+
32
+
33
+ class Contract(metaclass=_ContractMeta):
34
+ """Base class for an Algorand Smart Contract"""
35
+
36
+ _name: str
37
+ _scratch_slots: Any | None
38
+ _state_totals: StateTotals | None
39
+
40
+ def __init_subclass__(
41
+ cls,
42
+ *,
43
+ name: str | None = None,
44
+ scratch_slots: (
45
+ algopy.UInt64 | tuple[int | algopy.UInt64, ...] | list[int | algopy.UInt64] | None
46
+ ) = None,
47
+ state_totals: StateTotals | None = None,
48
+ ):
49
+ cls._name = name or cls.__name__
50
+ cls._scratch_slots = scratch_slots
51
+ cls._state_totals = state_totals
52
+
53
+ def approval_program(self) -> algopy.UInt64 | bool:
54
+ raise NotImplementedError("`approval_program` is not implemented.")
55
+
56
+ def clear_state_program(self) -> algopy.UInt64 | bool:
57
+ raise NotImplementedError("`clear_state_program` is not implemented.")
58
+
59
+
60
+ class ARC4Contract(Contract):
61
+ @final
62
+ def approval_program(self) -> algopy.UInt64 | bool:
63
+ raise NotImplementedError(
64
+ "`approval_program` is not implemented. To test ARC4 specific logic, "
65
+ "refer to direct calls to ARC4 methods."
66
+ )
67
+
68
+ def clear_state_program(self) -> algopy.UInt64 | bool:
69
+ return True
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ import typing
5
+ from dataclasses import dataclass
6
+ from typing import TypedDict, TypeVar
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from collections.abc import Callable
10
+
11
+ import algopy
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ class GlobalFields(TypedDict, total=False):
17
+ min_txn_fee: algopy.UInt64
18
+ min_balance: algopy.UInt64
19
+ max_txn_life: algopy.UInt64
20
+ zero_address: algopy.Account
21
+ group_size: algopy.UInt64
22
+ logic_sig_version: algopy.UInt64
23
+ round: algopy.UInt64
24
+ latest_timestamp: algopy.UInt64
25
+ current_application_id: algopy.UInt64
26
+ creator_address: algopy.Account
27
+ current_application_address: algopy.Account
28
+ group_id: algopy.Bytes
29
+ caller_application_id: algopy.Application
30
+ caller_application_address: algopy.Account
31
+ asset_create_min_balance: algopy.UInt64
32
+ asset_opt_in_min_balance: algopy.UInt64
33
+ genesis_hash: algopy.Bytes
34
+ opcode_budget: Callable[[], int]
35
+
36
+
37
+ @dataclass
38
+ class _Global:
39
+ def __getattr__(self, name: str) -> typing.Any:
40
+ from algopy import UInt64
41
+
42
+ from algopy_testing.context import get_test_context
43
+
44
+ context = get_test_context()
45
+ if not context:
46
+ raise ValueError(
47
+ "Test context is not initialized! Use `with algopy_testing_context()` to access "
48
+ "the context manager."
49
+ )
50
+
51
+ if name == "latest_timestamp" and context._global_fields.get(name) is None:
52
+ return UInt64(int(time.time()))
53
+
54
+ if name == "group_size" and context._global_fields.get(name) is None:
55
+ return UInt64(len(context.get_transaction_group()))
56
+
57
+ if name not in context._global_fields:
58
+ raise AttributeError(
59
+ f"'algopy.Global' object has no value set for attribute named '{name}'. "
60
+ f"Use `context.patch_global_fields({name}=your_value)` to set the value "
61
+ "in your test setup."
62
+ )
63
+
64
+ return context._global_fields[name] # type: ignore[literal-required]
65
+
66
+
67
+ Global = _Global()
@@ -0,0 +1,40 @@
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 index: self._get_value(txn_group, name, index)
24
+
25
+ # TODO: refine mapping
26
+ def _map_fields(self, name: str) -> str:
27
+ field_mapping = {"type": "type_bytes", "type_enum": "type", "application_args": "app_args"}
28
+ return field_mapping.get(name, name)
29
+
30
+ def _get_value(self, txn_group: list[typing.Any], name: str, index: int) -> object:
31
+ if index >= len(txn_group):
32
+ raise IndexError("Transaction index out of range")
33
+ gtxn = txn_group[index]
34
+ value = getattr(gtxn, self._map_fields(name))
35
+ if value is None:
36
+ raise ValueError(f"'{name}' is not defined for {type(gtxn).__name__}")
37
+ return value
38
+
39
+
40
+ GTxn = _GTxn()
@@ -0,0 +1,34 @@
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()
@@ -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
+ "_ApplicationCallBaseFields",
143
+ "_ApplicationCallFields",
144
+ "_AssetConfigBaseFields",
145
+ "_AssetFreezeBaseFields",
146
+ "_AssetTransferBaseFields",
147
+ "_KeyRegistrationBaseFields",
148
+ "_PaymentBaseFields",
149
+ "_TransactionBaseFields",
150
+ "_TransactionCoreFields",
151
+ "_TransactionFields",
152
+ "ApplicationCallFields",
153
+ "AssetConfigFields",
154
+ "AssetFreezeFields",
155
+ "AssetTransferFields",
156
+ "KeyRegistrationFields",
157
+ "PaymentFields",
158
+ ]
@@ -0,0 +1,111 @@
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
+ return getattr(active_txn, self._map_fields(name))
103
+ else:
104
+ raise AttributeError(
105
+ f"'Txn' object has no value set for attribute named '{name}'. "
106
+ f"Use `context.patch_txn_fields({name}=your_value)` to set the value "
107
+ "in your test setup."
108
+ )
109
+
110
+
111
+ Txn = _Txn()
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ if typing.TYPE_CHECKING:
6
+ import algopy
7
+
8
+
9
+ class urange: # noqa: N801
10
+ def __init__(self, *args: int | algopy.UInt64) -> None:
11
+ pass
12
+
13
+
14
+ def uenumerate(_iterable: typing.Any) -> typing.Any:
15
+ pass