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
File without changes
@@ -0,0 +1,204 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import typing
5
+
6
+ import algosdk
7
+
8
+ from algopy_testing.constants import UINT64_SIZE
9
+ from algopy_testing.utils import (
10
+ abi_return_type_annotation_for_arg,
11
+ abi_type_name_for_arg,
12
+ int_to_bytes,
13
+ )
14
+
15
+ if typing.TYPE_CHECKING:
16
+ import algopy
17
+
18
+ from algopy_testing.context import AlgopyTestContext
19
+
20
+
21
+ if typing.TYPE_CHECKING:
22
+ import algopy
23
+
24
+
25
+ _P = typing.ParamSpec("_P")
26
+ _R = typing.TypeVar("_R")
27
+
28
+
29
+ def _generate_arc4_signature(
30
+ fn: typing.Callable[_P, _R], args: tuple[typing.Any, ...]
31
+ ) -> algopy.Bytes:
32
+ import algopy
33
+
34
+ args_without_txns = [
35
+ arg
36
+ for arg in args
37
+ if not isinstance(arg, algopy.gtxn.TransactionBase) # type: ignore[arg-type, unused-ignore]
38
+ ]
39
+ arg_types = [algosdk.abi.Argument(abi_type_name_for_arg(arg=arg)) for arg in args_without_txns]
40
+ return_type = algosdk.abi.Returns(
41
+ abi_return_type_annotation_for_arg(fn.__annotations__.get("return"))
42
+ )
43
+ method = algosdk.abi.Method(name=fn.__name__, args=arg_types, returns=return_type)
44
+ return algopy.Bytes(method.get_selector())
45
+
46
+
47
+ def _extract_refs_from_args(
48
+ args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any], ref_type: type
49
+ ) -> list[typing.Any]:
50
+ import algopy
51
+
52
+ refs: list[typing.Any] = []
53
+ for arg in list(args) + list(kwargs.values()):
54
+ match arg:
55
+ case algopy.gtxn.TransactionBase() if ref_type is algopy.gtxn.TransactionBase:
56
+ refs.append(arg)
57
+ case algopy.Account() if ref_type is algopy.Account:
58
+ refs.append(arg)
59
+ case algopy.Asset() if ref_type is algopy.Asset:
60
+ refs.append(arg)
61
+ case algopy.Application() if ref_type is algopy.Application:
62
+ refs.append(arg)
63
+ case (
64
+ algopy.String()
65
+ | algopy.BigUInt()
66
+ | algopy.UInt64()
67
+ | algopy.BigUInt()
68
+ | algopy.arc4.Bool()
69
+ | algopy.arc4.BigUIntN()
70
+ | algopy.arc4.BigUFixedNxM()
71
+ | algopy.arc4.StaticArray()
72
+ | algopy.arc4.DynamicArray()
73
+ | algopy.arc4.DynamicBytes()
74
+ | algopy.arc4.Address()
75
+ | algopy.arc4.String()
76
+ | algopy.arc4.Byte()
77
+ | algopy.arc4.UIntN()
78
+ | int()
79
+ ) if ref_type is algopy.Bytes:
80
+ refs.append(_extract_bytes(arg))
81
+ case _:
82
+ continue
83
+ return refs
84
+
85
+
86
+ def _extract_bytes(value: object) -> algopy.Bytes:
87
+ import algopy
88
+
89
+ if isinstance(value, algopy.Bytes):
90
+ return value
91
+ if isinstance(
92
+ value,
93
+ (
94
+ algopy.String
95
+ | algopy.BigUInt
96
+ | algopy.arc4.Bool
97
+ | algopy.arc4.BigUIntN
98
+ | algopy.arc4.BigUFixedNxM
99
+ | algopy.arc4.StaticArray
100
+ | algopy.arc4.DynamicArray
101
+ | algopy.arc4.DynamicBytes
102
+ | algopy.arc4.Address
103
+ | algopy.arc4.String
104
+ | algopy.arc4.Byte
105
+ | algopy.arc4.UIntN
106
+ ),
107
+ ):
108
+ return value.bytes # type: ignore[union-attr, unused-ignore]
109
+ if isinstance(value, (algopy.UInt64 | int)):
110
+ return algopy.Bytes(int_to_bytes(int(value), UINT64_SIZE))
111
+ raise ValueError(f"Unsupported type: {type(value).__name__}")
112
+
113
+
114
+ def _extract_and_append_txn_to_context(
115
+ context: AlgopyTestContext,
116
+ args: tuple[typing.Any, ...],
117
+ kwargs: dict[str, typing.Any],
118
+ fn: typing.Callable[_P, _R],
119
+ ) -> None:
120
+ import algopy
121
+
122
+ context.add_transactions(_extract_refs_from_args(args, kwargs, algopy.gtxn.TransactionBase))
123
+
124
+ context.add_transactions(
125
+ [
126
+ context.any_application_call_transaction(
127
+ sender=context.default_creator,
128
+ app_id=context.default_application,
129
+ accounts=_extract_refs_from_args(args, kwargs, algopy.Account),
130
+ assets=_extract_refs_from_args(args, kwargs, algopy.Asset),
131
+ apps=_extract_refs_from_args(args, kwargs, algopy.Application),
132
+ app_args=[
133
+ _generate_arc4_signature(fn, args),
134
+ *_extract_refs_from_args(args, kwargs, algopy.Bytes),
135
+ ],
136
+ approval_program_pages=_extract_refs_from_args(args, kwargs, algopy.Bytes),
137
+ clear_state_program_pages=_extract_refs_from_args(args, kwargs, algopy.Bytes),
138
+ ),
139
+ ]
140
+ )
141
+ new_active_txn_index = len(context.get_transaction_group()) - 1
142
+ context.set_active_transaction_index(new_active_txn_index if new_active_txn_index > 0 else 0)
143
+
144
+
145
+ @typing.overload
146
+ def abimethod(fn: typing.Callable[_P, _R], /) -> typing.Callable[_P, _R]: ...
147
+
148
+
149
+ @typing.overload
150
+ def abimethod(
151
+ *,
152
+ name: str | None = None,
153
+ create: typing.Literal["allow", "require", "disallow"] = "disallow",
154
+ allow_actions: typing.Sequence[
155
+ algopy.OnCompleteAction
156
+ | typing.Literal[
157
+ "NoOp",
158
+ "OptIn",
159
+ "CloseOut",
160
+ # ClearState has its own program, so is not considered as part of ARC4 routing
161
+ "UpdateApplication",
162
+ "DeleteApplication",
163
+ ]
164
+ ] = ("NoOp",),
165
+ readonly: bool = False,
166
+ default_args: typing.Mapping[str, str | object] = {},
167
+ ) -> typing.Callable[[typing.Callable[_P, _R]], typing.Callable[_P, _R]]: ...
168
+
169
+
170
+ def abimethod( # noqa: PLR0913
171
+ fn: typing.Callable[_P, _R] | None = None,
172
+ *,
173
+ name: str | None = None, # noqa: ARG001
174
+ create: typing.Literal["allow", "require", "disallow"] = "disallow", # noqa: ARG001
175
+ allow_actions: typing.Sequence[ # noqa: ARG001
176
+ algopy.OnCompleteAction
177
+ | typing.Literal[
178
+ "NoOp",
179
+ "OptIn",
180
+ "CloseOut",
181
+ "UpdateApplication",
182
+ "DeleteApplication",
183
+ ]
184
+ ] = ("NoOp",),
185
+ readonly: bool = False, # noqa: ARG001
186
+ default_args: typing.Mapping[str, str | object] | None = None, # noqa: ARG001
187
+ ) -> typing.Callable[[typing.Callable[_P, _R]], typing.Callable[_P, _R]] | typing.Callable[_P, _R]:
188
+ def decorator(fn: typing.Callable[_P, _R]) -> typing.Callable[_P, _R]:
189
+ @functools.wraps(fn)
190
+ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
191
+ from algopy_testing import get_test_context
192
+
193
+ context = get_test_context()
194
+ if context is None or context._active_transaction_index is not None:
195
+ return fn(*args, **kwargs)
196
+
197
+ _extract_and_append_txn_to_context(context, args[1:], kwargs, fn)
198
+ return fn(*args, **kwargs)
199
+
200
+ return wrapper
201
+
202
+ if fn is not None:
203
+ return decorator(fn)
204
+ return decorator
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import typing
5
+
6
+ if typing.TYPE_CHECKING:
7
+ import algopy
8
+
9
+ from algopy_testing.context import AlgopyTestContext
10
+
11
+
12
+ _P = typing.ParamSpec("_P")
13
+ _R = typing.TypeVar("_R")
14
+
15
+
16
+ def _append_bare_txn_to_context(
17
+ context: AlgopyTestContext,
18
+ ) -> None:
19
+ context.add_transactions(
20
+ [
21
+ context.any_application_call_transaction(
22
+ sender=context.default_creator,
23
+ app_id=context.default_application,
24
+ ),
25
+ ]
26
+ )
27
+ context.set_active_transaction_index(len(context.get_transaction_group()) - 1)
28
+
29
+
30
+ @typing.overload
31
+ def baremethod(fn: typing.Callable[_P, _R], /) -> typing.Callable[_P, _R]: ...
32
+
33
+
34
+ @typing.overload
35
+ def baremethod(
36
+ *,
37
+ create: typing.Literal["allow", "require", "disallow"] = "disallow",
38
+ allow_actions: typing.Sequence[
39
+ algopy.OnCompleteAction
40
+ | typing.Literal[
41
+ "NoOp",
42
+ "OptIn",
43
+ "CloseOut",
44
+ "UpdateApplication",
45
+ "DeleteApplication",
46
+ ]
47
+ ] = ("NoOp",),
48
+ ) -> typing.Callable[[typing.Callable[_P, _R]], typing.Callable[_P, _R]]: ...
49
+
50
+
51
+ def baremethod(
52
+ fn: typing.Callable[_P, _R] | None = None,
53
+ *,
54
+ create: typing.Literal["allow", "require", "disallow"] = "disallow", # noqa: ARG001
55
+ allow_actions: typing.Sequence[ # noqa: ARG001
56
+ algopy.OnCompleteAction
57
+ | typing.Literal[
58
+ "NoOp",
59
+ "OptIn",
60
+ "CloseOut",
61
+ "UpdateApplication",
62
+ "DeleteApplication",
63
+ ]
64
+ ] = ("NoOp",),
65
+ ) -> typing.Callable[[typing.Callable[_P, _R]], typing.Callable[_P, _R]] | typing.Callable[_P, _R]:
66
+ def decorator(fn: typing.Callable[_P, _R]) -> typing.Callable[_P, _R]:
67
+ @functools.wraps(fn)
68
+ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
69
+ from algopy_testing import get_test_context
70
+
71
+ context = get_test_context()
72
+ if context is None or context._active_transaction_index is not None:
73
+ return fn(*args, **kwargs)
74
+
75
+ # Custom logic for baremethod can be added here
76
+ _append_bare_txn_to_context(context)
77
+ return fn(*args, **kwargs)
78
+
79
+ return wrapper
80
+
81
+ if fn is not None:
82
+ return decorator(fn)
83
+ 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,42 @@
1
+ from enum import Enum, IntEnum, StrEnum
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
+ class EC(StrEnum):
37
+ """Available values for the `EC` enum"""
38
+
39
+ BN254g1 = "BN254g1"
40
+ BN254g2 = "BN254g2"
41
+ BLS12_381g1 = "BLS12_381g1"
42
+ BLS12_381g2 = "BLS12_381g2"
algopy_testing/gtxn.py ADDED
@@ -0,0 +1,261 @@
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
+ # NOTE: The actual access by group_index is not used, its there to comply with stubs
28
+ # but user has no need to deal with that as we can reason on the index based
29
+ # on position in gtxn group
30
+ NULL_GTXN_GROUP_INDEX = -1
31
+
32
+
33
+ @dataclass
34
+ class _GroupTransaction:
35
+ _fields: dict[str, typing.Any] = field(default_factory=dict)
36
+
37
+ group_index: algopy.UInt64 | int = field(default=NULL_GTXN_GROUP_INDEX)
38
+
39
+ def __init__(self, group_index: algopy.UInt64 | int) -> None:
40
+ self.group_index = group_index
41
+ self._fields = {}
42
+
43
+
44
+ class TransactionFields(
45
+ _TransactionBaseFields,
46
+ _AssetTransferBaseFields,
47
+ _PaymentBaseFields,
48
+ _ApplicationCallBaseFields,
49
+ _KeyRegistrationBaseFields,
50
+ _AssetConfigBaseFields,
51
+ _AssetFreezeBaseFields,
52
+ total=False,
53
+ ):
54
+ pass
55
+
56
+
57
+ @dataclass
58
+ class TransactionBase(_GroupTransaction):
59
+ def __init__(
60
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[_TransactionBaseFields]
61
+ ):
62
+ from algopy_testing import get_test_context
63
+
64
+ context = get_test_context()
65
+ try:
66
+ existing_txn = context._gtxns[group_index]
67
+ except IndexError:
68
+ existing_txn = None
69
+
70
+ if (
71
+ existing_txn
72
+ and isinstance(existing_txn, self.__class__)
73
+ or self.__class__ == Transaction
74
+ ):
75
+ self.__dict__.update(existing_txn.__dict__)
76
+ else:
77
+ super().__init__(group_index=group_index)
78
+ self._fields.update(kwargs)
79
+
80
+ def set(self, **kwargs: typing.Unpack[_TransactionBaseFields]) -> None:
81
+ """Updates inner transaction parameter values"""
82
+ self._fields.update(kwargs)
83
+
84
+ def __getattr__(self, name: str) -> object:
85
+ if name not in _TransactionBaseFields.__annotations__:
86
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
87
+
88
+ return self._fields.get(name)
89
+
90
+
91
+ @dataclass
92
+ class AssetTransferTransaction(TransactionBase):
93
+ def __init__(
94
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[AssetTransferFields]
95
+ ):
96
+ super().__init__(group_index=group_index)
97
+ self._fields.update(kwargs)
98
+
99
+ def set(self, **kwargs: typing.Unpack[AssetTransferFields]) -> None:
100
+ """Updates inner transaction parameter values"""
101
+ self._fields.update(kwargs)
102
+
103
+ def __getattr__(self, name: str) -> typing.Any:
104
+ if name in AssetTransferFields.__annotations__:
105
+ return self._fields.get(name)
106
+
107
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
108
+
109
+
110
+ @dataclass
111
+ class PaymentTransaction(TransactionBase):
112
+ def __init__(self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[PaymentFields]):
113
+ import algopy
114
+
115
+ super().__init__(group_index=group_index)
116
+ self._fields.update(
117
+ {
118
+ "type": algopy.TransactionType.Payment,
119
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.Payment)),
120
+ **kwargs,
121
+ }
122
+ )
123
+
124
+ def __getattr__(self, name: str) -> typing.Any:
125
+ if name in PaymentFields.__annotations__:
126
+ return self._fields.get(name)
127
+
128
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
129
+
130
+
131
+ @dataclass
132
+ class ApplicationCallTransaction(TransactionBase):
133
+ def __init__(
134
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[ApplicationCallFields]
135
+ ):
136
+ import algopy
137
+
138
+ super().__init__(group_index=group_index)
139
+ self._fields.update(
140
+ {
141
+ "type": algopy.TransactionType.ApplicationCall,
142
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.ApplicationCall)),
143
+ **kwargs,
144
+ }
145
+ )
146
+
147
+ def __getattr__(self, name: str) -> typing.Any:
148
+ if name in ApplicationCallFields.__annotations__:
149
+ return self._fields.get(name)
150
+
151
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
152
+
153
+
154
+ class KeyRegistrationTransaction(TransactionBase):
155
+ def __init__(
156
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[KeyRegistrationFields]
157
+ ):
158
+ import algopy
159
+
160
+ super().__init__(group_index=group_index)
161
+ self._fields.update(
162
+ {
163
+ "type": algopy.TransactionType.KeyRegistration,
164
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.KeyRegistration)),
165
+ **kwargs,
166
+ }
167
+ )
168
+
169
+ def __getattr__(self, name: str) -> typing.Any:
170
+ if name in KeyRegistrationFields.__annotations__:
171
+ return self._fields.get(name)
172
+
173
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
174
+
175
+
176
+ @dataclass
177
+ class AssetConfigTransaction(TransactionBase):
178
+ def __init__(
179
+ self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[AssetConfigFields]
180
+ ):
181
+ import algopy
182
+
183
+ super().__init__(group_index=group_index)
184
+ self._fields.update(
185
+ {
186
+ "type": algopy.TransactionType.AssetConfig,
187
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.AssetConfig)),
188
+ **kwargs,
189
+ }
190
+ )
191
+
192
+ def __getattr__(self, name: str) -> typing.Any:
193
+ if name in AssetConfigFields.__annotations__:
194
+ return self._fields.get(name)
195
+
196
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
197
+
198
+
199
+ @dataclass
200
+ class AssetFreezeTransaction(TransactionBase):
201
+ def __init__(
202
+ self,
203
+ group_index: algopy.UInt64 | int,
204
+ **kwargs: typing.Unpack[AssetFreezeFields],
205
+ ):
206
+ import algopy
207
+
208
+ super().__init__(group_index=group_index)
209
+ self._fields.update(
210
+ {
211
+ "type": algopy.TransactionType.AssetFreeze,
212
+ "type_bytes": txn_type_to_bytes(int(algopy.TransactionType.AssetFreeze)),
213
+ **kwargs,
214
+ }
215
+ )
216
+
217
+ def __getattr__(self, name: str) -> typing.Any:
218
+ if name in AssetFreezeFields.__annotations__:
219
+ return self._fields.get(name)
220
+
221
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
222
+
223
+
224
+ @dataclass
225
+ class Transaction(TransactionBase):
226
+ def __init__(
227
+ self,
228
+ group_index: algopy.UInt64 | int,
229
+ **kwargs: typing.Unpack[TransactionFields],
230
+ ):
231
+ super().__init__(group_index=group_index)
232
+ self._fields.update(kwargs)
233
+ # in case we still got no type after super attempts to load
234
+ # existing txn fro _gtxns then fail
235
+ if "type" not in self._fields:
236
+ raise ValueError("Transaction 'type' field is required")
237
+
238
+ def __getattr__(self, name: str) -> typing.Any:
239
+ if name in TransactionFields.__annotations__:
240
+ return self._fields.get(name)
241
+
242
+ raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
243
+
244
+
245
+ __all__ = [
246
+ "ApplicationCallFields",
247
+ "ApplicationCallTransaction",
248
+ "AssetConfigFields",
249
+ "AssetConfigTransaction",
250
+ "AssetFreezeFields",
251
+ "AssetFreezeTransaction",
252
+ "AssetTransferFields",
253
+ "AssetTransferTransaction",
254
+ "KeyRegistrationFields",
255
+ "KeyRegistrationTransaction",
256
+ "NULL_GTXN_GROUP_INDEX",
257
+ "PaymentFields",
258
+ "PaymentTransaction",
259
+ "Transaction",
260
+ "TransactionBase",
261
+ ]