|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import copy |
| 4 | +import typing |
| 5 | + |
| 6 | +if typing.TYPE_CHECKING: |
| 7 | + from collections.abc import Callable |
| 8 | + |
| 9 | +TKey = typing.TypeVar("TKey") |
| 10 | +TItem = typing.TypeVar("TItem") |
| 11 | + |
| 12 | + |
| 13 | +class MutableBytes: |
| 14 | + """Helper class to ensure mutable algopy types (currently ARC4 array, tuple and |
| 15 | + structs) cascade their changes to parent container types (global/local state, boxes, |
| 16 | + ARC4 array, tuple and structs)""" |
| 17 | + |
| 18 | + def __init__(self) -> None: |
| 19 | + # callback to call once when _value is modified |
| 20 | + self._on_mutate: Callable[[typing.Any], None] | None = None |
| 21 | + |
| 22 | + @property |
| 23 | + def _value(self) -> bytes: |
| 24 | + return self.__value |
| 25 | + |
| 26 | + @_value.setter |
| 27 | + def _value(self, value: bytes) -> None: |
| 28 | + self.__value = value |
| 29 | + if self._on_mutate: |
| 30 | + self._on_mutate(self) |
| 31 | + self._on_mutate = None |
| 32 | + |
| 33 | + def copy(self) -> typing.Self: |
| 34 | + # when copying a value discard the _on_mutate callback |
| 35 | + clone = copy.deepcopy(self) |
| 36 | + clone._on_mutate = None |
| 37 | + return clone |
| 38 | + |
| 39 | + |
| 40 | +def set_item_on_mutate(container: object, index: object, value: TItem) -> TItem: |
| 41 | + """Used to update a container-like type when an item is modified.""" |
| 42 | + |
| 43 | + def callback(new_value: TItem) -> None: |
| 44 | + container[index] = new_value # type: ignore[index] |
| 45 | + |
| 46 | + return add_mutable_callback(callback, value) |
| 47 | + |
| 48 | + |
| 49 | +def set_attr_on_mutate(parent: object, name: str, value: TItem) -> TItem: |
| 50 | + """Used to update an object-like type when an attr is modified.""" |
| 51 | + |
| 52 | + def callback(new_value: TItem) -> None: |
| 53 | + setattr(parent, name, new_value) |
| 54 | + |
| 55 | + return add_mutable_callback(callback, value) |
| 56 | + |
| 57 | + |
| 58 | +def add_mutable_callback(on_mutate: Callable[[typing.Any], None], value: TItem) -> TItem: |
| 59 | + """Add on_mutate callback to value, if value is mutable. |
| 60 | +
|
| 61 | + Returns value to simplify usage |
| 62 | + """ |
| 63 | + if isinstance(value, MutableBytes): |
| 64 | + value._on_mutate = on_mutate |
| 65 | + return value |
0 commit comments