tsrkit-types 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.
tsrkit_types/enum.py ADDED
@@ -0,0 +1,128 @@
1
+ from enum import EnumMeta
2
+ from typing import Tuple, Type, Union, Any, TypeVar, cast
3
+ from tsrkit_types.integers import Uint
4
+
5
+ T = TypeVar("T", bound="Enum")
6
+
7
+
8
+ class Enum(metaclass=EnumMeta):
9
+ """Decodable Enum type - Extending the built-in Enum type to add encoding and decoding methods
10
+
11
+ How to use it:
12
+ >>> class MyEnum(Enum):
13
+ >>> A = 1
14
+ >>> B = 2
15
+ >>> C = 3
16
+ >>>
17
+ >>> value = MyEnum.A
18
+ >>> encoded = value.encode()
19
+ >>> decoded, bytes_read = MyEnum.decode_from(encoded)
20
+ >>> assert decoded == value
21
+ >>> assert bytes_read == 1
22
+ >>>
23
+ >>> assert MyEnum.from_json(1,) == MyEnum.A
24
+ >>> assert MyEnum.from_json("A",) == MyEnum.A
25
+ """
26
+
27
+ @property
28
+ def value(self) -> Any:
29
+ return self._value_
30
+
31
+ @classmethod
32
+ def _missing_(cls, value: Any) -> T:
33
+ raise ValueError(f"Invalid value: {value}")
34
+
35
+ # ---------------------------------------------------------------------------- #
36
+ # Serialization #
37
+ # ---------------------------------------------------------------------------- #
38
+
39
+ def encode_size(self) -> int:
40
+ """Return the size in bytes needed to encode this enum value"""
41
+ return 1
42
+
43
+ def encode(self) -> bytes:
44
+ """Encode the value into a new bytes object."""
45
+ size = self.encode_size()
46
+ buffer = bytearray(size)
47
+ written = self.encode_into(buffer)
48
+ return bytes(buffer[:written])
49
+
50
+ def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
51
+ """Encode this enum value into the given buffer at the given offset
52
+
53
+ Args:
54
+ buffer: The buffer to encode into
55
+ offset: The offset to start encoding at
56
+
57
+ Returns:
58
+ The number of bytes written
59
+
60
+ Raises:
61
+ ValueError: If the enum has too many variants to encode in a byte
62
+ """
63
+ # Get the index of the enum value in all enums
64
+ # Encode the index as a byte
65
+ all_enums = self.__class__._member_names_
66
+ index = all_enums.index(self._name_)
67
+ if index > 255:
68
+ raise ValueError("Enum index is too large to encode into a single byte")
69
+ return Uint(index).encode_into(buffer, offset)
70
+
71
+ @classmethod
72
+ def decode_from(
73
+ cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0
74
+ ) -> Tuple[T, int]:
75
+ """Decode an enum value from the given buffer at the given offset
76
+
77
+ Args:
78
+ buffer: The buffer to decode from
79
+ offset: The offset to start decoding at
80
+
81
+ Returns:
82
+ A tuple of (decoded enum value, number of bytes read)
83
+
84
+ Raises:
85
+ ValueError: If the encoded index is invalid
86
+ """
87
+ # Decode the byte (index of enum) into an Enum
88
+ # Return the enum value
89
+ index, bytes_read = Uint.decode_from(buffer, offset)
90
+ value = cast(T, cls._member_map_[cls._member_names_[index]])
91
+ return value, bytes_read
92
+
93
+
94
+ # ---------------------------------------------------------------------------- #
95
+ # JSON Serde #
96
+ # ---------------------------------------------------------------------------- #
97
+
98
+ @classmethod
99
+ def from_json(cls: Type[T], data: Any) -> T:
100
+ """Convert a JSON value to an enum value
101
+
102
+ Args:
103
+ data: The JSON value (either the enum value or name)
104
+
105
+ Returns:
106
+ The corresponding enum value
107
+
108
+ Raises:
109
+ JsonDeserializationError: If the value is invalid
110
+ """
111
+ for v in cls.__members__.values():
112
+ if v._value_ == data or v._name_ == data:
113
+ return cast(T, v)
114
+ raise ValueError(f"Invalid value: {data}")
115
+
116
+ def to_json(self) -> Any:
117
+ """Convert this enum value to a JSON value
118
+
119
+ Returns:
120
+ The enum's value for JSON serialization
121
+ """
122
+ return self._value_
123
+
124
+ @classmethod
125
+ def decode(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> T:
126
+ """Decode a value from the provided buffer starting at the specified offset."""
127
+ value, bytes_read = cls.decode_from(buffer, offset)
128
+ return value
@@ -0,0 +1,203 @@
1
+ import abc
2
+ from decimal import Decimal
3
+ import math
4
+ from typing import Any, Tuple, Union, Callable
5
+ from tsrkit_types.itf.codable import Codable
6
+
7
+
8
+ class IntCheckMeta(abc.ABCMeta):
9
+ """Meta class to check if the instance is an integer with the same byte size"""
10
+ def __instancecheck__(cls, instance):
11
+ return isinstance(instance, int) and getattr(instance, "byte_size", 0) == cls.byte_size
12
+
13
+
14
+ class Uint(int, Codable, metaclass=IntCheckMeta):
15
+ """
16
+ Unsigned integer type.
17
+
18
+
19
+ Usage:
20
+ >>> # Fixed Integer with defined class
21
+ >>> U8 = Uint[8]
22
+ >>> U8(10)
23
+ U8(10)
24
+ >>> U8.encode(10)
25
+ b'\n'
26
+ >>> U8.decode(b'\n')
27
+ U8(10)
28
+
29
+ >>> # Fixed Integer w dynamic usage
30
+ >>> num = Uint[8](10)
31
+ >>> num
32
+ U8(10)
33
+ >>> num.encode()
34
+ b'\n'
35
+ >>> Uint[8].decode(b'\n')
36
+ U8(10)
37
+
38
+
39
+ >>> # If you want to use the General Integer (supports up to 2**64 - 1),
40
+ >>> # you can use the Uint class without specifying the byte size.
41
+ >>>
42
+ >>> num = Uint(10)
43
+ >>> num
44
+ Uint(10)
45
+ >>> num.encode()
46
+ b'\n'
47
+ >>> Uint.decode(b'\n')
48
+ Uint(10)
49
+
50
+ """
51
+
52
+ # If the byte_size is set, the integer is fixed size.
53
+ # Otherwise, the integer is General Integer (supports up to 2**64 - 1)
54
+ byte_size: int = 0
55
+
56
+ def __class_getitem__(cls, size: int):
57
+ return type(f"U{size}" if size else "Int", (cls,), {"byte_size": size // 8})
58
+
59
+ def __new__(cls, value: Any):
60
+ value = int(value)
61
+ if cls.byte_size > 0:
62
+ bits = 8 * cls.byte_size
63
+ min_v = 0
64
+ max_v = (1 << bits) - 1
65
+ if not (min_v <= value <= max_v):
66
+ raise ValueError(f"Fixed Int: {cls.__name__} out of range: {value!r} "
67
+ f"not in [{min_v}, {max_v}]")
68
+ else:
69
+ if not 0 <= value < 2 ** 64:
70
+ raise ValueError(f"General Int: Value must be between 0 and 2**64 - 1, got {value}")
71
+
72
+ return super().__new__(cls, value)
73
+
74
+ def __repr__(self):
75
+ return f"{self.__class__.__name__}({int(self)})"
76
+
77
+ def _wrap_op(self, other: Any, op: Callable[[int, int], int]):
78
+ res = op(int(self), int(other))
79
+ return type(self)(res)
80
+
81
+ # ---------------------------------------------------------------------------- #
82
+ # Arithmetic #
83
+ # ---------------------------------------------------------------------------- #
84
+ def __add__(self, other):
85
+ return self._wrap_op(other, int.__add__)
86
+
87
+ def __sub__(self, other):
88
+ return self._wrap_op(other, int.__sub__)
89
+
90
+ def __mul__(self, other):
91
+ return self._wrap_op(other, int.__mul__)
92
+
93
+ def __floordiv__(self, other):
94
+ return self._wrap_op(other, int.__floordiv__)
95
+
96
+ def __and__(self, other):
97
+ return self._wrap_op(other, int.__and__)
98
+
99
+ def __or__(self, other):
100
+ return self._wrap_op(other, int.__or__)
101
+
102
+ def __xor__(self, other):
103
+ return self._wrap_op(other, int.__xor__)
104
+
105
+ # ---------------------------------------------------------------------------- #
106
+ # JSON Serde #
107
+ # ---------------------------------------------------------------------------- #
108
+ def to_json(self) -> str:
109
+ return int(self)
110
+
111
+ @classmethod
112
+ def from_json(cls, json_str: str) -> "Uint":
113
+ return cls(int(json_str))
114
+
115
+ # ---------------------------------------------------------------------------- #
116
+ # Serialization #
117
+ # ---------------------------------------------------------------------------- #
118
+ @staticmethod
119
+ def l(x):
120
+ return math.floor(Decimal(x).ln() / (Decimal(7) * Decimal(2).ln()))
121
+
122
+
123
+ def encode_size(self) -> int:
124
+ if self.byte_size > 0:
125
+ return self.byte_size
126
+ else:
127
+ if self < 2**7:
128
+ return 1
129
+ elif self < 2 ** (7 * 9):
130
+ return 1 + self.l(self)
131
+ elif self < 2**64:
132
+ return 9
133
+ else:
134
+ raise ValueError("Value too large for encoding. General Uint support up to 2**64 - 1")
135
+
136
+ def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
137
+ if self.byte_size > 0:
138
+ buffer[offset:offset+self.byte_size] = self.to_bytes(self.byte_size, "little")
139
+ return self.byte_size
140
+ else:
141
+ if self < 2**7:
142
+ buffer[offset:offset+1] = self.to_bytes(1, "little")
143
+ return 1
144
+
145
+ size = self.encode_size()
146
+ self._check_buffer_size(buffer, size, offset)
147
+ if self < 2 ** (7 * 8):
148
+ _l = self.l(self)
149
+ # Create temporary U8 for encoding the prefix
150
+ prefix_value = (2**8 - 2 ** (8 - _l) +
151
+ math.floor(Decimal(self) / (Decimal(2) ** (_l * 8))))
152
+ buffer[offset] = int(prefix_value)
153
+ offset += 1
154
+ # Encode the remaining bytes
155
+ remaining = self % (2 ** (_l * 8))
156
+ remaining_bytes = remaining.to_bytes(_l, "little")
157
+ buffer[offset : offset + _l] = remaining_bytes
158
+ elif self < 2**64:
159
+ buffer[offset] = 2**8 - 1 # Full 64-bit marker
160
+ offset += 1
161
+ buffer[offset : offset + 8] = self.to_bytes(8, "little")
162
+ else:
163
+ raise ValueError(
164
+ f"Value too large for encoding. General Uint support up to 2**64 - 1, got {self}"
165
+ )
166
+ return size
167
+
168
+ @classmethod
169
+ def decode_from(
170
+ cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0
171
+ ) -> Tuple[Any, int]:
172
+ if cls.byte_size > 0:
173
+ value, size = int.from_bytes(buffer[offset : offset + cls.byte_size], "little"), cls.byte_size
174
+ return cls.__new__(cls, value), size
175
+ else:
176
+ tag = int.from_bytes(buffer[offset:offset+1], "little")
177
+
178
+ if tag < 2**7:
179
+ return cls(tag), 1
180
+
181
+ if tag == 2**8 - 1:
182
+ # Full 64-bit encoding
183
+ if len(buffer) - offset < 9:
184
+ raise ValueError("Buffer too small to decode 64-bit integer")
185
+ value = int.from_bytes(buffer[offset + 1 : offset + 9], "little")
186
+ return cls(value), 9
187
+ else:
188
+ # Variable length encoding
189
+ _l = math.floor(
190
+ Decimal(8) - (Decimal(2**8) - Decimal(tag)).ln() / Decimal(2).ln()
191
+ )
192
+ if len(buffer) - offset < _l + 1:
193
+ raise ValueError("Buffer too small to decode variable-length integer")
194
+ alpha = tag + 2 ** (8 - _l) - 2**8
195
+ beta = int.from_bytes(buffer[offset + 1 : offset + 1 + _l], "little")
196
+ value = alpha * 2 ** (_l * 8) + beta
197
+ return cls(value), _l + 1
198
+
199
+
200
+ U8 = Uint[8]
201
+ U16 = Uint[16]
202
+ U32 = Uint[32]
203
+ U64 = Uint[64]
@@ -0,0 +1,82 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TypeVar, Generic, Tuple, Union
3
+
4
+ T = TypeVar("T")
5
+
6
+
7
+ class Codable(ABC, Generic[T]):
8
+ """Abstract base class defining the interface for encoding and decoding data."""
9
+
10
+ @abstractmethod
11
+ def encode_size(self) -> int:
12
+ """
13
+ Calculate the number of bytes needed to encode the value.
14
+
15
+ Returns:
16
+ The number of bytes needed to encode the value.
17
+ """
18
+ raise NotImplementedError("Subclasses must implement this method")
19
+
20
+ @abstractmethod
21
+ def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
22
+ """
23
+ Encode the value into the provided buffer at the specified offset.
24
+
25
+ Args:
26
+ buffer: The buffer to encode the value into.
27
+ offset: The offset at which to start encoding the value.
28
+
29
+ Returns:
30
+ The number of bytes written to the buffer.
31
+ """
32
+ raise NotImplementedError("Subclasses must implement this method")
33
+
34
+ def encode(self) -> bytes:
35
+ """
36
+ Encode the value into a new bytes object.
37
+
38
+ Returns:
39
+ The encoded value as a bytes object.
40
+ """
41
+ size = self.encode_size()
42
+ buffer = bytearray(size)
43
+ written = self.encode_into(buffer)
44
+ return bytes(buffer[:written])
45
+
46
+ @classmethod
47
+ def decode_from(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple[T, int]:
48
+ """
49
+ Decode a value from the provided buffer starting at the specified offset.
50
+
51
+ Args:
52
+ buffer: The buffer to decode the value from.
53
+ offset: The offset at which to start decoding the value.
54
+
55
+ Returns:
56
+ A tuple containing the decoded value and the number of bytes read from the buffer.
57
+ """
58
+ raise NotImplementedError("Subclasses must implement this method")
59
+
60
+ @classmethod
61
+ def decode(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> T:
62
+ """
63
+ Decode a value from the provided buffer starting at the specified offset.
64
+
65
+ Args:
66
+ buffer: The buffer to decode the value from.
67
+ offset: The offset at which to start decoding the value.
68
+ """
69
+ value, bytes_read = cls.decode_from(buffer, offset)
70
+ return value
71
+
72
+ def _check_buffer_size(self, buffer: bytearray, size: int, offset: int) -> None:
73
+ """
74
+ Check if the buffer has enough space to encode the value.
75
+
76
+ Args:
77
+ buffer: The buffer to check the size of.
78
+ size: The size of the value to encode.
79
+ offset: The offset at which to start encoding the value.
80
+ """
81
+ if len(buffer) - offset < size:
82
+ raise ValueError("Buffer too small to encode value")
tsrkit_types/null.py ADDED
@@ -0,0 +1,43 @@
1
+ from typing import Optional, Self, Tuple, Union
2
+
3
+
4
+ class NullType:
5
+ def __repr__(self):
6
+ return "Null"
7
+
8
+ def __eq__(self, other):
9
+ return not other
10
+
11
+ def __bool__(self):
12
+ return False
13
+
14
+ # ---------------------------------------------------------------------------- #
15
+ # Serialization #
16
+ # ---------------------------------------------------------------------------- #
17
+
18
+ def encode_size(self) -> int:
19
+ return 0
20
+
21
+ def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
22
+ return 0
23
+
24
+ @classmethod
25
+ def decode_from(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple[Self, int]:
26
+ return cls(), 0
27
+
28
+ # ---------------------------------------------------------------------------- #
29
+ # JSON Parse #
30
+ # ---------------------------------------------------------------------------- #
31
+
32
+ def to_json(self) -> str:
33
+ return None
34
+
35
+ @classmethod
36
+ def from_json(cls, json_str: Optional[str]) -> Self:
37
+ if json_str is None:
38
+ return cls()
39
+ raise ValueError("Invalid JSON string for NullType")
40
+
41
+
42
+
43
+ Null = NullType()
tsrkit_types/option.py ADDED
@@ -0,0 +1,30 @@
1
+ from typing import Generic, Optional, TypeVar
2
+ from tsrkit_types.choice import Choice
3
+ from tsrkit_types.null import Null, NullType
4
+
5
+
6
+ T = TypeVar("T")
7
+
8
+ class Option(Choice, Generic[T]):
9
+ """
10
+ Option[T] wraps either no value (None) or a T.
11
+ """
12
+
13
+ def __class_getitem__(cls, opt_t: T):
14
+ if not isinstance(opt_t, type):
15
+ raise TypeError("Option[...] only accepts a single type")
16
+ name = f"Option[{opt_t.__class__.__name__}]"
17
+ return type(name,
18
+ (Option,),
19
+ {"_opt_types": ((None, opt_t), (None, NullType))})
20
+
21
+ def __init__(self, val: T|NullType = Null):
22
+ super().__init__(val)
23
+
24
+ def set(self, value: T|NullType = Null, key: Optional[str] = None):
25
+ if value is None:
26
+ value = Null
27
+ super().set(value, key)
28
+
29
+ def __bool__(self):
30
+ return self._value != Null
@@ -0,0 +1,212 @@
1
+ import abc
2
+ from typing import TypeVar, Type, ClassVar, Tuple, Generic
3
+ from tsrkit_types.integers import Uint
4
+ from tsrkit_types.itf.codable import Codable
5
+
6
+ T = TypeVar("T")
7
+
8
+ class SeqCheckMeta(abc.ABCMeta):
9
+ """Meta class to check if the instance is an integer with the same byte size"""
10
+ def __instancecheck__(cls, instance):
11
+ # String comparison is used to avoid identity comparison issues - like Uint[8] and Uint[8]
12
+ # TODO - This needs more false positive testing
13
+ _matches_element_type = str(getattr(cls, "_element_type", None)) == str(getattr(instance, "_element_type", None))
14
+ _matches_min_length = getattr(cls, "_min_length", 0) == getattr(instance, "_min_length", 0)
15
+ _matches_max_length = getattr(cls, "_max_length", 2**64) == getattr(instance, "_max_length", 2**64)
16
+ return isinstance(instance, list) and _matches_element_type and _matches_min_length and _matches_max_length
17
+
18
+
19
+ class Seq(list, Codable, Generic[T], metaclass=SeqCheckMeta):
20
+ """
21
+ Sequence Type
22
+
23
+ Usage:
24
+ >>> # Create a reusable type
25
+ >>> class Eta(Seq[bytes, 4]): ...
26
+ >>>
27
+ >>> # (or) Disposable
28
+ >>> val_indexes = Seq[int, 1023]([0] * 1028)
29
+ >>>
30
+ >>> # Supports codec [both variable and fixed length] given that the element type must support codec
31
+ >>> Seq[U16, 1023]([0] * 1028).encode()
32
+ """
33
+ _element_type: ClassVar[Type[T]]
34
+ _min_length: ClassVar[int] = 0
35
+ _max_length: ClassVar[int] = 2 ** 64
36
+
37
+ def __class_getitem__(cls, params):
38
+ # To overwrite previous cls values
39
+ min_l, max_l, elem_t = 0, 2**64, None
40
+
41
+ if isinstance(params, int) or isinstance(params, (type, TypeVar)):
42
+ if isinstance(params, (type, TypeVar)):
43
+ elem_t = params
44
+ elif isinstance(params, int):
45
+ max_l, min_l = params, params
46
+ else:
47
+ raise TypeError(f"Invalid param to define {__class__.__name__}: {params}")
48
+ elif len(params) == 2:
49
+ if isinstance(params[0], type) and isinstance(params[1], int):
50
+ elem_t, min_l, max_l = params[0], params[1], params[1]
51
+ elif isinstance(params[0], int) and isinstance(params[1], int):
52
+ min_l, max_l = params[0], params[1]
53
+ else:
54
+ raise TypeError(f"Invalid param to define {cls.__class__.__name__}: {params}")
55
+ elif len(params) == 3 and isinstance(params[0], (type, TypeVar)) and isinstance(params[1], int) and isinstance(params[2], int):
56
+ elem_t, min_l, max_l = params
57
+ else:
58
+ raise TypeError(f"Invalid param to define {cls.__class__.__name__}: {params}")
59
+
60
+ # build a nice name
61
+ parts = []
62
+ if elem_t:
63
+ parts.append(elem_t.__name__)
64
+ if min_l == max_l:
65
+ parts.append(f"N={min_l}")
66
+ else:
67
+ if min_l:
68
+ parts.append(f"min={min_l}")
69
+ if max_l != 2 ** 64:
70
+ parts.append(f"max={max_l}")
71
+
72
+ name = f"{cls.__name__}[{','.join(parts)}]"
73
+
74
+ return type(name, (cls,), {
75
+ "_element_type": elem_t,
76
+ "_min_length": min_l,
77
+ "_max_length": max_l,
78
+ })
79
+
80
+ def _validate(self, value):
81
+ """For TypeChecks - added to fns that alter elements"""
82
+ if getattr(self, "_element_type", None) is not None:
83
+ if not isinstance(value, self._element_type):
84
+ raise TypeError(f"{value!r} is not an instance of {self._element_type!r}")
85
+
86
+ def _validate_self(self):
87
+ """For Resultant Self check - added to fns that alter size"""
88
+ if len(self) < self._min_length:
89
+ raise ValueError(f"Vector: Expected sequence size to be >= {self._min_length}, resultant size {len(self)}")
90
+ elif len(self) > self._max_length:
91
+ raise ValueError(f"Vector: Expected sequence size to be <= {self._max_length}, resultant size {len(self)}")
92
+
93
+ def __init__(self, initial: list[T]):
94
+ super().__init__()
95
+ self.extend(initial)
96
+
97
+ def append(self, v: T):
98
+ self._validate(v)
99
+ super().append(v)
100
+ self._validate_self()
101
+
102
+ def insert(self, i, v: T):
103
+ self._validate(v)
104
+ super().insert(i, v)
105
+ self._validate_self()
106
+
107
+ def extend(self, seq: list[T]):
108
+ for val in seq:
109
+ self._validate(val)
110
+ super().extend(seq)
111
+ self._validate_self()
112
+
113
+ def __setitem__(self, i, v: T):
114
+ self._validate(v)
115
+ super().__setitem__(i, v)
116
+
117
+ def __repr__(self):
118
+ return f"{self.__class__.__name__}({list(self)})"
119
+
120
+ @property
121
+ def _length(self):
122
+ if self._min_length == self._max_length:
123
+ return self._min_length
124
+ return None
125
+
126
+ # ---------------------------------------------------------------------------- #
127
+ # Serialization #
128
+ # ---------------------------------------------------------------------------- #
129
+ def encode_size(self):
130
+ size = 0
131
+
132
+ # If length is not defined
133
+ if self._length is None:
134
+ size += Uint(len(self)).encode_size()
135
+
136
+ for item in self:
137
+ if not isinstance(item, Codable):
138
+ raise TypeError(0, 0, f"Expected Codable, got {type(item)}")
139
+ size += item.encode_size()
140
+
141
+ return size
142
+
143
+ def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
144
+ current_offset = offset
145
+ # If length is not defined
146
+ if(self._min_length != len(self)) and (self._max_length != len(self)):
147
+ current_offset += Uint(len(self)).encode_into(buffer, current_offset)
148
+
149
+ for item in self:
150
+ written = item.encode_into(buffer, current_offset)
151
+ current_offset += written
152
+
153
+ return current_offset - offset
154
+
155
+ @classmethod
156
+ def decode_from(cls, buffer: bytes, offset: int = 0) -> Tuple["Seq", int]:
157
+ current_offset = offset
158
+
159
+ # Determine if this is variable length
160
+ if cls._min_length == cls._max_length:
161
+ # Fixed length
162
+ _len = cls._min_length
163
+ else:
164
+ # Variable length - decode length from buffer
165
+ _len, _inc_offset = Uint.decode_from(buffer, current_offset)
166
+ current_offset += _inc_offset
167
+
168
+ items = []
169
+ for _ in range(_len):
170
+ item, _inc_offset = cls._element_type.decode_from(buffer, current_offset)
171
+ current_offset += _inc_offset
172
+ items.append(item)
173
+
174
+ return cls(items), current_offset - offset
175
+
176
+ # ---------------------------------------------------------------------------- #
177
+ # JSON Serde #
178
+ # ---------------------------------------------------------------------------- #
179
+ def to_json(self):
180
+ """Convert to JSON representation."""
181
+ return [item.to_json() for item in self]
182
+
183
+ @classmethod
184
+ def from_json(cls, data):
185
+ """Create instance from JSON representation."""
186
+ if cls._element_type:
187
+ items = [cls._element_type.from_json(item) for item in data]
188
+ else:
189
+ items = data
190
+ return cls(items)
191
+
192
+
193
+ # All params supported-
194
+ # Union[Type, int, Tuple[Type, int], Tuple[int, int], Tuple[Type, int, int]]
195
+
196
+ class Vector(Seq):
197
+ def __class_getitem__(cls, params: None): return super().__class_getitem__(params)
198
+
199
+ class Array(Seq):
200
+ def __class_getitem__(cls, params: int): return super().__class_getitem__(params)
201
+
202
+ class TypedArray(Seq):
203
+ def __class_getitem__(cls, params: Tuple[Type, int]): return super().__class_getitem__(params)
204
+
205
+ class TypedVector(Seq):
206
+ def __class_getitem__(cls, params: Type): return super().__class_getitem__(params)
207
+
208
+ class BoundedVector(Seq):
209
+ def __class_getitem__(cls, params: Tuple[int, int]): return super().__class_getitem__(params)
210
+
211
+ class TypedBoundedVector(Seq):
212
+ def __class_getitem__(cls, params: Tuple[Type, int, int]): return super().__class_getitem__(params)