Skip to content

Commit 9eb670b

Browse files
authored
Merge pull request #36 from algorandfoundation/feat/size-of
feat: add stub implementation of `algopy.size_of` function
2 parents ff2c3ec + 2c73646 commit 9eb670b

File tree

6 files changed

+110
-4
lines changed

6 files changed

+110
-4
lines changed

docs/coverage.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
See which `algorand-python` stubs are implemented by the `algorand-python-testing` library. See the [Concepts](testing-guide/concepts.md#types-of-algopy-stub-implementations) section for more details on the implementation categories. Refer to the [`algorand-python` stubs API](api.md) for the full list of the stubs for which the `algorand-python-testing` library provides implementations referenced in the table below.
44

55
| Name | Implementation type |
6-
| ------------------------------------------- | ------------------- |
6+
|---------------------------------------------|---------------------|
77
| algopy.Account | Emulated |
88
| algopy.Application | Emulated |
99
| algopy.Asset | Emulated |
@@ -33,6 +33,7 @@ See which `algorand-python` stubs are implemented by the `algorand-python-testin
3333
| algopy.ensure_budget | Emulated |
3434
| algopy.log | Emulated |
3535
| algopy.logicsig | Emulated |
36+
| algopy.size_of | Emulated |
3637
| algopy.subroutine | Native |
3738
| algopy.uenumerate | Native |
3839
| algopy.urange | Native |

src/_algopy_testing/arc4.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1256,10 +1256,20 @@ def _compress_multiple_bool(value_list: list[Bool]) -> int:
12561256
return result
12571257

12581258

1259+
def get_max_bytes_static_len(type_info: _TypeInfo) -> int | None:
1260+
return _get_max_bytes_len_impl(type_info, static_size_only=True)
1261+
1262+
12591263
def _get_max_bytes_len(type_info: _TypeInfo) -> int:
1264+
return _get_max_bytes_len_impl(type_info) or 0
1265+
1266+
1267+
def _get_max_bytes_len_impl(type_info: _TypeInfo, *, static_size_only: bool = False) -> int | None:
12601268
size = 0
12611269
if isinstance(type_info, _DynamicArrayTypeInfo):
12621270
size += _ABI_LENGTH_SIZE
1271+
if static_size_only:
1272+
return None
12631273
elif isinstance(type_info, _TupleTypeInfo | _StructTypeInfo | _StaticArrayTypeInfo):
12641274
i = 0
12651275
if isinstance(type_info, _TupleTypeInfo | _StructTypeInfo):
@@ -1276,12 +1286,18 @@ def _get_max_bytes_len(type_info: _TypeInfo) -> int:
12761286
if bool_num % 8 != 0:
12771287
size += 1
12781288
else:
1279-
child_byte_size = _get_max_bytes_len(child_types[i])
1289+
child_byte_size = _get_max_bytes_len_impl(
1290+
child_types[i], static_size_only=static_size_only
1291+
)
1292+
if child_byte_size is None:
1293+
return None
12801294
size += child_byte_size
12811295
i += 1
12821296
elif isinstance(type_info, _UIntTypeInfo):
12831297
size = type_info.max_bytes_len
12841298

1299+
elif static_size_only:
1300+
return None
12851301
return size
12861302

12871303

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from _algopy_testing.utilities.budget import OpUpFeeSource, ensure_budget
22
from _algopy_testing.utilities.log import log
3+
from _algopy_testing.utilities.size_of import size_of
34

4-
__all__ = ["OpUpFeeSource", "ensure_budget", "log"]
5+
__all__ = ["OpUpFeeSource", "ensure_budget", "log", "size_of"]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import types
2+
3+
from _algopy_testing.primitives.uint64 import UInt64
4+
5+
6+
def size_of(typ: type | object, /) -> UInt64:
7+
from _algopy_testing.arc4 import get_max_bytes_static_len
8+
from _algopy_testing.serialize import get_native_to_arc4_serializer
9+
10+
if isinstance(typ, types.GenericAlias):
11+
pass
12+
elif not isinstance(typ, type):
13+
typ = type(typ)
14+
15+
if typ is bool: # treat bool on its own as a uint64
16+
typ = UInt64
17+
serializer = get_native_to_arc4_serializer(typ) # type: ignore[arg-type]
18+
type_info = serializer.arc4_type._type_info
19+
size = get_max_bytes_static_len(type_info)
20+
if size is None:
21+
raise ValueError(f"{typ} is dynamically sized")
22+
return UInt64(size)

src/algopy/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from _algopy_testing.primitives import Array, BigUInt, Bytes, ImmutableArray, String, UInt64
1818
from _algopy_testing.protocols import BytesBacked
1919
from _algopy_testing.state import Box, BoxMap, BoxRef, GlobalState, LocalState
20-
from _algopy_testing.utilities import OpUpFeeSource, ensure_budget, log
20+
from _algopy_testing.utilities import OpUpFeeSource, ensure_budget, log, size_of
2121

2222
from . import arc4, gtxn, itxn, op
2323

@@ -59,6 +59,7 @@
5959
"logicsig",
6060
"op",
6161
"subroutine",
62+
"size_of",
6263
"uenumerate",
6364
"urange",
6465
]

tests/utilities/test_size_of.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import typing
2+
3+
import pytest
4+
from algopy import Account, Application, Asset, Bytes, String, UInt64, arc4, size_of
5+
6+
7+
class Swapped(arc4.Struct):
8+
a: arc4.UInt64
9+
b: arc4.Bool
10+
c: arc4.Tuple[arc4.UInt64, arc4.Bool, arc4.Bool]
11+
d: arc4.StaticArray[arc4.Bool, typing.Literal[10]]
12+
e: arc4.Tuple[arc4.UInt64, arc4.StaticArray[arc4.UInt64, typing.Literal[3]]]
13+
14+
15+
class WhatsMySize(typing.NamedTuple):
16+
foo: UInt64
17+
bar: bool
18+
baz: Swapped
19+
20+
21+
class MyTuple(typing.NamedTuple):
22+
foo: UInt64
23+
bar: bool
24+
baz: bool
25+
26+
27+
class MyDynamicSizedTuple(typing.NamedTuple):
28+
foo: UInt64
29+
bar: String
30+
31+
32+
def test_size_of() -> None:
33+
x = arc4.UInt64(0)
34+
assert size_of(x) == 8
35+
assert size_of(arc4.UInt64) == 8
36+
assert size_of(UInt64) == 8
37+
assert size_of(arc4.Address) == 32
38+
assert size_of(Account) == 32
39+
assert size_of(Application) == 8
40+
assert size_of(Asset) == 8
41+
assert size_of(bool) == 8
42+
assert size_of(tuple[bool]) == 1
43+
assert size_of(tuple[bool, bool, bool, bool, bool, bool, bool, bool]) == 1
44+
assert size_of(tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool]) == 2
45+
assert size_of(tuple[arc4.UInt64, UInt64, bool, arc4.Bool]) == 17
46+
assert size_of(arc4.Tuple[arc4.UInt64, arc4.Bool, arc4.Bool] == 9)
47+
assert size_of(MyTuple) == 9
48+
assert size_of(WhatsMySize) == 61
49+
assert size_of(arc4.StaticArray[arc4.Byte, typing.Literal[7]]) == 7
50+
assert size_of(arc4.StaticArray(arc4.Byte(), arc4.Byte())) == 2
51+
assert size_of(Swapped) == 52
52+
53+
54+
@pytest.mark.parametrize(
55+
"typ",
56+
[
57+
arc4.StaticArray[arc4.DynamicBytes, typing.Literal[7]],
58+
tuple[arc4.DynamicBytes, Bytes],
59+
arc4.Tuple[arc4.UInt64, arc4.String],
60+
MyDynamicSizedTuple,
61+
],
62+
)
63+
def test_size_of_dynamic(typ: type) -> None:
64+
with pytest.raises(ValueError, match="is dynamically sized"):
65+
size_of(typ)

0 commit comments

Comments
 (0)