tsrkit-types 0.1.2__py3-none-any.whl → 0.1.3__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/__init__.py CHANGED
@@ -1,76 +1,95 @@
1
- # from jam.types.base.integers import (
2
- # U8,
3
- # U16,
4
- # U32,
5
- # U64,
6
- # U128,
7
- # U256,
8
- # Int,
9
- # )
10
- # from jam.types.base.string import String
11
- # from jam.types.base.null import Null, Nullable
12
- # from jam.types.base.dictionary import Dictionary, decodable_dictionary
13
- # from jam.types.base.boolean import Boolean
14
- # from jam.types.base.bit import Bit
15
- # from jam.types.base.composite import Choice, Option, decodable_choice, decodable_option
16
- # from jam.types.base.sequences.array import Array, decodable_array
17
- # from jam.types.base.sequences.vector import Vector, decodable_vector
18
- # from jam.types.base.bytes.bytes import Bytes
19
- # # from jam.types.base.sequences.bytes import BitArray, decodable_bit_array, Byte
20
- # # from jam.types.base.bytes.byte_array import (
21
- # # ByteArray8,
22
- # # ByteArray16,
23
- # # ByteArray32,
24
- # # ByteArray64,
25
- # # ByteArray96,
26
- # # ByteArray128,
27
- # # ByteArray144,
28
- # # ByteArray256,
29
- # # ByteArray784,
30
- # # )
31
- #
32
- # __all__ = [
33
- # # Integer types
34
- # "Int",
35
- # "U8",
36
- # "U16",
37
- # "U32",
38
- # "U64",
39
- # "U128",
40
- # "U256",
41
- # # Choice and Null types
42
- # "Choice",
43
- # "Option",
44
- # "Null",
45
- # "Nullable",
46
- # # Dictionary type
47
- # "Dictionary",
48
- # # Boolean and Bit types
49
- # "Boolean",
50
- # "Bit",
51
- # # String type
52
- # "String",
53
- # # Sequence types
54
- # # "Array",
55
- # # "Vector",
56
- # # # Byte types
57
- # # "ByteArray8",
58
- # # "ByteArray16",
59
- # # "ByteArray32",
60
- # # "ByteArray64",
61
- # # "ByteArray96",
62
- # # "ByteArray128",
63
- # # "ByteArray144",
64
- # # "ByteArray256",
65
- # # "ByteArray784",
66
- # # "BitArray",
67
- # # "Byte",
68
- # "Bytes",
69
- # # Decodable types
70
- # "decodable_array",
71
- # "decodable_bit_array",
72
- # "decodable_vector",
73
- # "decodable_dictionary",
74
- # "decodable_choice",
75
- # "decodable_option",
76
- # ]
1
+ """
2
+ TSRKit Types - Performant Python types for binary serialization and JSON encoding.
3
+
4
+ This module provides a comprehensive set of typed data structures with built-in
5
+ serialization capabilities, including integers, strings, containers, and more.
6
+ """
7
+
8
+ # Core interfaces
9
+ from .itf.codable import Codable
10
+
11
+ # Integer types
12
+ from .integers import Uint, U8, U16, U32, U64
13
+
14
+ # String types
15
+ from .string import String
16
+
17
+ # Boolean types
18
+ from .bool import Bool
19
+
20
+ # Null types
21
+ from .null import Null, NullType
22
+
23
+ # Choice and Option types
24
+ from .choice import Choice
25
+ from .option import Option
26
+
27
+ # Container types
28
+ from .sequences import (
29
+ Seq, Vector, Array,
30
+ TypedVector, TypedArray,
31
+ BoundedVector, TypedBoundedVector
32
+ )
33
+
34
+ # Dictionary types
35
+ from .dictionary import Dictionary
36
+
37
+ # Bytes types
38
+ from .bytes import Bytes, ByteArray16, ByteArray32, ByteArray64, ByteArray128, ByteArray256, ByteArray512, ByteArray1024
39
+ from .bytearray import ByteArray
40
+
41
+ # Bit types
42
+ from .bits import Bits
43
+
44
+ # Enum types
45
+ from .enum import Enum
46
+
47
+ # Structure decorator
48
+ from .struct import structure, struct
49
+
50
+ # Export all public types
51
+ __all__ = [
52
+ # Core interfaces
53
+ "Codable",
54
+
55
+ # Integer types
56
+ "Uint", "U8", "U16", "U32", "U64",
57
+
58
+ # String types
59
+ "String",
60
+
61
+ # Boolean types
62
+ "Bool",
63
+
64
+ # Null types
65
+ "Null", "NullType",
66
+
67
+ # Choice and Option types
68
+ "Choice", "Option",
69
+
70
+ # Container types
71
+ "Seq", "Vector", "Array",
72
+ "TypedVector", "TypedArray",
73
+ "BoundedVector", "TypedBoundedVector",
74
+
75
+ # Dictionary types
76
+ "Dictionary",
77
+
78
+ # Bytes types
79
+ "Bytes", "ByteArray16", "ByteArray32", "ByteArray64", "ByteArray128", "ByteArray256", "ByteArray512", "ByteArray1024",
80
+ "ByteArray",
81
+
82
+ # Bit types
83
+ "Bits",
84
+
85
+ # Enum types
86
+ "Enum",
87
+
88
+ # Structure decorator
89
+ "structure", "struct",
90
+ ]
91
+
92
+ # Version information
93
+ __version__ = "1.0.0"
94
+ __author__ = "TSRKit Team"
95
+ __license__ = "MIT"
tsrkit_types/bits.py CHANGED
@@ -34,7 +34,13 @@ class Bits(Seq):
34
34
 
35
35
  @classmethod
36
36
  def from_json(cls, json_str: str) -> "Bits":
37
- return cls(Bytes.from_hex(json_str).to_bits(bit_order=cls._order))
37
+ bits = Bytes.from_json(json_str).to_bits(bit_order=cls._order)
38
+
39
+ # For fixed-length types, trim to exact size
40
+ if cls._min_length == cls._max_length and cls._min_length > 0:
41
+ bits = bits[:cls._min_length]
42
+
43
+ return cls(bits)
38
44
 
39
45
  # ---------------------------------------------------------------------------- #
40
46
  # Serialization #
@@ -43,7 +49,9 @@ class Bits(Seq):
43
49
  def encode_size(self) -> int:
44
50
  # Calculate the number of bytes needed
45
51
  bit_enc = 0
46
- if self._length is None:
52
+ # Check if this is a variable-length type (needs length prefix)
53
+ is_fixed_length = (self._min_length == self._max_length and self._min_length > 0)
54
+ if not is_fixed_length:
47
55
  bit_enc = Uint(len(self)).encode_size()
48
56
 
49
57
  return bit_enc + ((len(self) + 7) // 8)
@@ -54,17 +62,18 @@ class Bits(Seq):
54
62
  total_size = self.encode_size()
55
63
  self._check_buffer_size(buffer, total_size, offset)
56
64
 
57
- # Initialize all bytes to 0
58
- for i in range(0, total_size):
59
- buffer[offset + i] = 0
60
-
61
- if self._length is None:
65
+ current_offset = offset
66
+
67
+ # Check if this is a variable-length type (needs length prefix)
68
+ is_fixed_length = (self._min_length == self._max_length and self._min_length > 0)
69
+
70
+ if not is_fixed_length:
62
71
  # Encode the bit length first
63
- offset += Uint(len(self)).encode_into(buffer, offset)
72
+ current_offset += Uint(len(self)).encode_into(buffer, current_offset)
64
73
  else:
65
- # Ensure bit length is size of value
66
- if len(self) != self._length:
67
- raise ValueError("Bit sequence length mismatch")
74
+ # Ensure bit length matches expected size for fixed-length types
75
+ if len(self) != self._min_length:
76
+ raise ValueError(f"Bit sequence length mismatch: expected {self._min_length}, got {len(self)}")
68
77
 
69
78
  if not all(
70
79
  isinstance(bit, (bool, int)) and bit in (0, 1, True, False)
@@ -72,9 +81,9 @@ class Bits(Seq):
72
81
  ):
73
82
  raise ValueError(f"Bit sequence must contain only 0s and 1s, got an sequence of {self}")
74
83
 
75
- buffer[offset : offset + total_size] = Bytes.from_bits(
76
- self, bit_order=self._order
77
- )
84
+ # Convert bits to bytes and write to buffer
85
+ bit_bytes = Bytes.from_bits(self, bit_order=self._order)
86
+ buffer[current_offset : current_offset + len(bit_bytes)] = bit_bytes
78
87
 
79
88
  return total_size
80
89
 
@@ -98,18 +107,28 @@ class Bits(Seq):
98
107
  Raises:
99
108
  DecodeError: If buffer too small or bit_length not specified
100
109
  """
101
- _len = cls._length
102
- if _len is None:
103
- # Assume first byte is the bit length
110
+ # Check if this is a fixed-length Bits type
111
+ is_fixed_length = (cls._min_length == cls._max_length and cls._min_length > 0)
112
+
113
+ original_offset = offset
114
+
115
+ if is_fixed_length:
116
+ _len = cls._min_length
117
+ else:
118
+ # Variable length - decode length from buffer
104
119
  _len, size = Uint.decode_from(buffer, offset)
105
120
  offset += size
106
121
 
107
122
  if _len == 0:
108
- return [], 0
123
+ return cls([]), offset - original_offset
109
124
 
110
125
  # Calculate required bytes
111
126
  byte_count = (_len + 7) // 8
112
127
  cls._check_buffer_size(buffer, byte_count, offset)
113
128
 
114
- result = Bytes(buffer[offset : offset + byte_count]).to_bits(bit_order=cls._order)
115
- return cls(result), byte_count
129
+ result_bits = Bytes(buffer[offset : offset + byte_count]).to_bits(bit_order=cls._order)
130
+ # Trim to exact bit length
131
+ result_bits = result_bits[:_len]
132
+
133
+ total_bytes_read = offset + byte_count - original_offset
134
+ return cls(result_bits), total_bytes_read
tsrkit_types/bool.py CHANGED
@@ -29,13 +29,9 @@ class Bool(Codable):
29
29
  # JSON Parse #
30
30
  # ---------------------------------------------------------------------------- #
31
31
 
32
- def to_json(self) -> str:
33
- return "true" if self._value else "false"
32
+ def to_json(self) -> bool:
33
+ return bool(self)
34
34
 
35
35
  @classmethod
36
- def from_json(cls, json_str: str) -> "Bool":
37
- if json_str == "true":
38
- return cls(True)
39
- if json_str == "false":
40
- return cls(False)
41
- raise ValueError("Invalid JSON string for Bool")
36
+ def from_json(cls, data: bool) -> "Bool":
37
+ return cls(data)
@@ -0,0 +1,37 @@
1
+ from typing import Tuple, Union
2
+ from tsrkit_types.integers import Uint
3
+ from tsrkit_types.itf.codable import Codable
4
+ from tsrkit_types.bytes_common import BytesMixin
5
+
6
+
7
+ class ByteArray(bytearray, Codable, BytesMixin):
8
+ """Variable Size ByteArray"""
9
+
10
+ # Bit conversion and JSON methods inherited from BytesMixin
11
+
12
+ # ---------------------------------------------------------------------------- #
13
+ # Serialization #
14
+ # ---------------------------------------------------------------------------- #
15
+ def encode_size(self) -> int:
16
+ return Uint(len(self)).encode_size() + len(self)
17
+
18
+ def encode_into(self, buf: bytearray, offset: int = 0) -> int:
19
+ current_offset = offset
20
+ _len = len(self)
21
+ current_offset += Uint(_len).encode_into(buf, current_offset)
22
+ buf[current_offset:current_offset+_len] = self
23
+ current_offset += _len
24
+ return current_offset - offset
25
+
26
+ @classmethod
27
+ def decode_from(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple["ByteArray", int]:
28
+ current_offset = offset
29
+ _len, _inc_offset = Uint.decode_from(buffer, offset)
30
+ current_offset += _inc_offset
31
+ return cls(buffer[current_offset:current_offset+_len]), current_offset + _len - offset
32
+
33
+ # ---------------------------------------------------------------------------- #
34
+ # JSON Serialization #
35
+ # ---------------------------------------------------------------------------- #
36
+ # JSON methods inherited from BytesMixin
37
+
tsrkit_types/bytes.py CHANGED
@@ -1,8 +1,19 @@
1
+ import abc
1
2
  from typing import Tuple, Union, ClassVar
2
3
  from tsrkit_types.integers import Uint
3
4
  from tsrkit_types.itf.codable import Codable
5
+ from tsrkit_types.bytes_common import BytesMixin
4
6
 
5
- class Bytes(bytes, Codable):
7
+ class BytesCheckMeta(abc.ABCMeta):
8
+ """Meta class to check if the instance is a bytes with the same key and value types"""
9
+ def __instancecheck__(cls, instance):
10
+ # TODO - This needs more false positive testing
11
+ _matches_length = str(getattr(cls, "_length", None)) == str(getattr(instance, "_length", None))
12
+ return isinstance(instance, bytes) and _matches_length
13
+
14
+
15
+ class Bytes(bytes, Codable, BytesMixin, metaclass=BytesCheckMeta):
16
+ """Fixed Size Bytes"""
6
17
 
7
18
  _length: ClassVar[Union[None, int]] = None
8
19
 
@@ -16,47 +27,7 @@ class Bytes(bytes, Codable):
16
27
  "_length": _len,
17
28
  })
18
29
 
19
- def __str__(self):
20
- return f"{self.__class__.__name__}({self.hex()})"
21
-
22
- @classmethod
23
- def from_bits(cls, bits: list[bool], bit_order = "msb") -> "Bytes":
24
- # Sanitize input: make sure bits are 0 or 1
25
- bits = [int(bool(b)) for b in bits]
26
- n = len(bits)
27
- # Pad with zeros to multiple of 8
28
- pad = (8 - n % 8) % 8
29
- bits += [0] * pad
30
-
31
- byte_arr = []
32
- for i in range(0, len(bits), 8):
33
- byte_bits = bits[i:i + 8]
34
- if bit_order == "msb":
35
- # Most significant bit first
36
- val = 0
37
- for bit in byte_bits:
38
- val = (val << 1) | bit
39
- elif bit_order == "lsb":
40
- # Least significant bit first
41
- val = 0
42
- for bit in reversed(byte_bits):
43
- val = (val << 1) | bit
44
- else:
45
- raise ValueError(f"Unknown bit_order: {bit_order}")
46
- # noinspection PyUnreachableCode
47
- byte_arr.append(val)
48
- return cls(bytes(byte_arr))
49
-
50
- def to_bits(self, bit_order="msb") -> list[bool]:
51
- bits = []
52
- for byte in self:
53
- if bit_order == "msb":
54
- bits.extend([(byte >> i) & 1 for i in reversed(range(8))])
55
- elif bit_order == "lsb":
56
- bits.extend([(byte >> i) & 1 for i in range(8)])
57
- else:
58
- raise ValueError(f"Unknown bit_order: {bit_order}")
59
- return bits
30
+ # Bit conversion methods inherited from BytesMixin
60
31
 
61
32
  # ---------------------------------------------------------------------------- #
62
33
  # Serialization #
@@ -88,13 +59,12 @@ class Bytes(bytes, Codable):
88
59
  # ---------------------------------------------------------------------------- #
89
60
  # JSON Serialization #
90
61
  # ---------------------------------------------------------------------------- #
91
- def to_json(self):
92
- """Convert bytes to hex string for JSON serialization"""
93
- return self.hex()
94
-
95
- @classmethod
96
- def from_json(cls, data: str):
97
- """Create Bytes instance from hex string"""
98
- data = data.replace("0x", "")
99
- return cls(bytes.fromhex(data))
100
-
62
+ # JSON methods inherited from BytesMixin
63
+
64
+ ByteArray16 = Bytes[16]
65
+ ByteArray32 = Bytes[32]
66
+ ByteArray64 = Bytes[64]
67
+ ByteArray128 = Bytes[128]
68
+ ByteArray256 = Bytes[256]
69
+ ByteArray512 = Bytes[512]
70
+ ByteArray1024 = Bytes[1024]
@@ -0,0 +1,68 @@
1
+ """
2
+ Common functionality for Bytes and ByteArray types.
3
+ """
4
+
5
+ from typing import Union
6
+
7
+
8
+ class BytesMixin:
9
+ """Mixin providing common functionality for bytes-like types."""
10
+
11
+ @classmethod
12
+ def from_bits(cls, bits: list[bool], bit_order: str = "msb"):
13
+ """Convert a list of bits to bytes with specified bit order."""
14
+ # Sanitize input: make sure bits are 0 or 1
15
+ bits = [int(bool(b)) for b in bits]
16
+ n = len(bits)
17
+ # Pad with zeros to multiple of 8
18
+ pad = (8 - n % 8) % 8
19
+ bits += [0] * pad
20
+
21
+ byte_arr = []
22
+ for i in range(0, len(bits), 8):
23
+ byte_bits = bits[i:i + 8]
24
+ if bit_order == "msb":
25
+ # Most significant bit first
26
+ val = 0
27
+ for bit in byte_bits:
28
+ val = (val << 1) | bit
29
+ elif bit_order == "lsb":
30
+ # Least significant bit first
31
+ val = 0
32
+ for bit in reversed(byte_bits):
33
+ val = (val << 1) | bit
34
+ else:
35
+ raise ValueError(f"Unknown bit_order: {bit_order}")
36
+ byte_arr.append(val)
37
+ return cls(bytes(byte_arr))
38
+
39
+ def to_bits(self, bit_order: str = "msb") -> list[bool]:
40
+ """Convert bytes to a list of bits with specified bit order."""
41
+ bits = []
42
+ for byte in self:
43
+ if bit_order == "msb":
44
+ bits.extend([bool((byte >> i) & 1) for i in reversed(range(8))])
45
+ elif bit_order == "lsb":
46
+ bits.extend([bool((byte >> i) & 1) for i in range(8)])
47
+ else:
48
+ raise ValueError(f"Unknown bit_order: {bit_order}")
49
+ return bits
50
+
51
+ def to_json(self):
52
+ """Convert bytes to hex string for JSON serialization."""
53
+ return self.hex()
54
+
55
+ @classmethod
56
+ def from_json(cls, data: str):
57
+ """Create instance from hex string."""
58
+ data = data.replace("0x", "")
59
+ return cls(bytes.fromhex(data))
60
+
61
+ def __str__(self):
62
+ return f"{self.__class__.__name__}({self.hex()})"
63
+
64
+
65
+ def validate_bit_order(bit_order: str) -> None:
66
+ """Validate bit order parameter."""
67
+ if bit_order not in ("msb", "lsb"):
68
+ raise ValueError(f"Unknown bit_order: {bit_order}. Must be 'msb' or 'lsb'")
tsrkit_types/choice.py CHANGED
@@ -112,13 +112,13 @@ class Choice(Codable):
112
112
 
113
113
  def encode_into(self, buf: bytearray, offset: int = 0) -> int:
114
114
  current_offset = offset
115
- # Find the first isinstance(self._value, type) for self._choice_types
116
- for i, t in enumerate(self._choice_types):
117
- if isinstance(self._value, t):
115
+ # Find the index of the (key, type) pair that matches our current choice
116
+ for i, (key, choice_type) in enumerate(self._opt_types):
117
+ if self._choice_key == key and isinstance(self._value, choice_type):
118
118
  current_offset += Uint(i).encode_into(buf, current_offset)
119
119
  break
120
120
  else:
121
- raise ValueError(f"Value {self._value} is not a valid choice")
121
+ raise ValueError(f"Value {self._value} with key {self._choice_key} is not a valid choice")
122
122
  current_offset += self._value.encode_into(buf, current_offset)
123
123
  return current_offset - offset
124
124
 
@@ -102,7 +102,7 @@ class Dictionary(dict, Codable, Generic[K, V], metaclass=DictCheckMeta):
102
102
  if not isinstance(data, dict):
103
103
  _value = cls({})
104
104
  for val in data:
105
- _value[cls.key_type.from_json(val[cls.key_name])] = cls.value_type.from_json(val[cls.value_name])
105
+ _value[cls._key_type.from_json(val[cls._key_name])] = cls._value_type.from_json(val[cls._value_name])
106
106
  return _value
107
107
  else:
108
108
  return cls(
tsrkit_types/integers.py CHANGED
@@ -195,6 +195,23 @@ class Uint(int, Codable, metaclass=IntCheckMeta):
195
195
  beta = int.from_bytes(buffer[offset + 1 : offset + 1 + _l], "little")
196
196
  value = alpha * 2 ** (_l * 8) + beta
197
197
  return cls(value), _l + 1
198
+
199
+ def to_bits(self, bit_order: str = "msb") -> list[bool]:
200
+ """Convert an int to bits"""
201
+ if bit_order == "msb":
202
+ return [bool((self >> i) & 1) for i in reversed(range(self.byte_size * 8 if self.byte_size > 0 else 64))]
203
+ elif bit_order == "lsb":
204
+ return [bool((self >> i) & 1) for i in range(self.byte_size * 8 if self.byte_size > 0 else 64)]
205
+ else:
206
+ raise ValueError(f"Invalid bit order: {bit_order}")
207
+
208
+ @classmethod
209
+ def from_bits(cls, bits: list[bool], bit_order: str = "msb") -> "Uint":
210
+ """Convert bits to an int"""
211
+ if bit_order == "msb":
212
+ return cls(int("".join(str(int(b)) for b in bits), 2))
213
+ elif bit_order == "lsb":
214
+ return cls(int("".join(str(int(b)) for b in reversed(bits)), 2))
198
215
 
199
216
 
200
217
  U8 = Uint[8]
@@ -69,7 +69,8 @@ class Codable(ABC, Generic[T]):
69
69
  value, bytes_read = cls.decode_from(buffer, offset)
70
70
  return value
71
71
 
72
- def _check_buffer_size(self, buffer: bytearray, size: int, offset: int) -> None:
72
+ @classmethod
73
+ def _check_buffer_size(cls, buffer: bytearray, size: int, offset: int) -> None:
73
74
  """
74
75
  Check if the buffer has enough space to encode the value.
75
76
 
tsrkit_types/option.py CHANGED
@@ -16,7 +16,7 @@ class Option(Choice, Generic[T]):
16
16
  name = f"Option[{opt_t.__class__.__name__}]"
17
17
  return type(name,
18
18
  (Option,),
19
- {"_opt_types": ((None, opt_t), (None, NullType))})
19
+ {"_opt_types": ((None, NullType), (None, opt_t))})
20
20
 
21
21
  def __init__(self, val: T|NullType = Null):
22
22
  super().__init__(val)
@@ -27,4 +27,22 @@ class Option(Choice, Generic[T]):
27
27
  super().set(value, key)
28
28
 
29
29
  def __bool__(self):
30
- return self._value != Null
30
+ return self._value != Null
31
+
32
+ # ---------------------------------------------------------------------------- #
33
+ # JSON Serde #
34
+ # ---------------------------------------------------------------------------- #
35
+
36
+ def to_json(self):
37
+ """Convert Option to JSON. Returns None for empty Options."""
38
+ if self._value == Null:
39
+ return None
40
+ return self._value.to_json()
41
+
42
+ @classmethod
43
+ def from_json(cls, data):
44
+ """Create Option from JSON data. None creates empty Option."""
45
+ if data is None:
46
+ return cls() # Empty Option
47
+ # Try to create the wrapped type from the data
48
+ return cls(cls._opt_types[1][1].from_json(data))
tsrkit_types/sequences.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import abc
2
- from typing import TypeVar, Type, ClassVar, Tuple, Generic
2
+ from typing import TypeVar, Type, ClassVar, Tuple, Generic, Optional
3
3
  from tsrkit_types.integers import Uint
4
4
  from tsrkit_types.itf.codable import Codable
5
5
 
@@ -118,7 +118,7 @@ class Seq(list, Codable, Generic[T], metaclass=SeqCheckMeta):
118
118
  return f"{self.__class__.__name__}({list(self)})"
119
119
 
120
120
  @property
121
- def _length(self):
121
+ def _length(self) -> Optional[int]:
122
122
  if self._min_length == self._max_length:
123
123
  return self._min_length
124
124
  return None
@@ -143,7 +143,7 @@ class Seq(list, Codable, Generic[T], metaclass=SeqCheckMeta):
143
143
  def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
144
144
  current_offset = offset
145
145
  # If length is not defined
146
- if(self._min_length != len(self)) and (self._max_length != len(self)):
146
+ if(self._min_length != self._max_length):
147
147
  current_offset += Uint(len(self)).encode_into(buffer, current_offset)
148
148
 
149
149
  for item in self:
tsrkit_types/struct.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from dataclasses import dataclass, fields
2
2
  from typing import Any, Tuple, Union
3
3
  from tsrkit_types.itf.codable import Codable
4
+ from tsrkit_types.null import NullType
5
+ from tsrkit_types.option import Option
4
6
 
5
7
 
6
8
  def structure(_cls=None, *, frozen=False, **kwargs):
@@ -58,22 +60,26 @@ def structure(_cls=None, *, frozen=False, **kwargs):
58
60
  for field in fields(cls):
59
61
  k = field.metadata.get("name", field.name)
60
62
  v = data.get(k)
61
- if v is None:
62
- if field.metadata.get("default") is not None:
63
- init_data[field.name] = field.metadata.get("default")
64
- else:
65
- raise ValueError(f"Field {k} not found in {cls}")
63
+ if v is None and field.metadata.get("default") is not None:
64
+ init_data[field.name] = field.metadata.get("default")
66
65
  else:
67
66
  init_data[field.name] = field.type.from_json(v)
68
67
  return cls(**init_data)
69
68
 
70
69
 
71
70
  new_cls.__init__ = __init__
72
- new_cls.encode_size = encode_size
73
- new_cls.decode_from = decode_from
74
- new_cls.encode_into = encode_into
75
- new_cls.to_json = to_json
76
- new_cls.from_json = from_json
71
+
72
+ # Only overwrite if the method is not already defined
73
+ if not new_cls.__dict__.get("encode_size"):
74
+ new_cls.encode_size = encode_size
75
+ if not new_cls.__dict__.get("decode_from"):
76
+ new_cls.decode_from = decode_from
77
+ if not new_cls.__dict__.get("encode_into"):
78
+ new_cls.encode_into = encode_into
79
+ if not new_cls.__dict__.get("to_json"):
80
+ new_cls.to_json = to_json
81
+ if not new_cls.__dict__.get("from_json"):
82
+ new_cls.from_json = from_json
77
83
 
78
84
  new_cls = type(new_cls.__name__, (Codable, new_cls), dict(new_cls.__dict__))
79
85
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsrkit-types
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Performant Python Typings library for type-safe binary serialization, JSON encoding, and data validation with zero dependencies
5
5
  Author-email: chainscore-labs <hello@chainscore.finance>, prasad-kumkar <prasad@chainscore.finance>
6
6
  License-Expression: MIT
@@ -299,7 +299,9 @@ restored = StringToInt.from_json(json_data)
299
299
 
300
300
  ### Bytes Types
301
301
 
302
- #### Bytes (extension of Python in-built bytes)
302
+ The library provides two complementary byte array types: immutable `Bytes` (extending Python's built-in `bytes`) and mutable `ByteArray` (extending Python's `bytearray`). Both types share common functionality through a mixin architecture for bit conversion, JSON serialization, and binary encoding.
303
+
304
+ #### Bytes (Immutable - extension of Python bytes)
303
305
 
304
306
  ```python
305
307
  from tsrkit_types.bytes import Bytes
@@ -307,47 +309,115 @@ from tsrkit_types.bytes import Bytes
307
309
  # Creation
308
310
  data = Bytes(b"Hello, binary world!")
309
311
  data = Bytes([0x01, 0x02, 0x03, 0x04])
312
+ data = Bytes("48656c6c6f") # From hex string
310
313
 
311
- # Bytes <-> Bits Operations
312
- data.to_bits() # [True, False, True, ...]
313
- Bytes.from_bits([True, False, True, ...])
314
+ # Shared Operations (via BytesMixin)
315
+ bits = data.to_bits() # Convert to bit list [True, False, True, ...]
316
+ data2 = Bytes.from_bits(bits) # Create from bit list
314
317
 
315
- # Operations
318
+ # Properties
316
319
  length = len(data) # Byte length
317
320
  raw_bytes = bytes(data) # Convert to Python bytes
318
321
 
319
- # Encoding
322
+ # Encoding/Decoding
320
323
  encoded = data.encode() # [length][raw_bytes]
321
324
  decoded = Bytes.decode(encoded)
322
325
 
323
326
  # JSON serialization (hex encoded)
324
327
  json_str = data.to_json() # "48656c6c6f2c2062696e61727920776f726c6421"
325
328
  restored = Bytes.from_json(json_str)
329
+ restored2 = Bytes.from_json("0x48656c6c6f") # Supports 0x prefix
326
330
  ```
327
331
 
328
- #### Bit Arrays (Sequence of bool)
332
+ #### ByteArray (Mutable - extension of Python bytearray)
329
333
 
330
334
  ```python
331
- from tsrkit_types.bits import BitArray
335
+ from tsrkit_types.bytearray import ByteArray
332
336
 
333
337
  # Creation
334
- bits = BitArray([True, False, True, True, False])
335
- bits = BitArray.from_hex("1A3F") # From hex string
336
- bits = BitArray.from_int(42, 8) # From integer with bit width
338
+ data = ByteArray(b"Hello, binary world!")
339
+ data = ByteArray([0x01, 0x02, 0x03, 0x04])
340
+ data = ByteArray("48656c6c6f") # From hex string
341
+
342
+ # Mutable Operations
343
+ data.append(0xFF) # Add single byte
344
+ data.extend([0xAB, 0xCD]) # Add multiple bytes
345
+ data.insert(0, 0x00) # Insert byte at position
346
+ data.pop() # Remove and return last byte
347
+ data.remove(0xFF) # Remove first occurrence
348
+ data.clear() # Remove all bytes
349
+ data.reverse() # Reverse in-place
350
+
351
+ # Indexing and Slicing (mutable)
352
+ data[0] = 0x42 # Set byte at index
353
+ data[1:3] = [0x43, 0x44] # Set slice
354
+ del data[0] # Delete byte at index
355
+
356
+ # Shared Operations (via BytesMixin) - same as Bytes
357
+ bits = data.to_bits() # Convert to bit list
358
+ data2 = ByteArray.from_bits(bits) # Create from bit list
359
+
360
+ # Properties and Conversion
361
+ length = len(data) # Byte length
362
+ raw_bytes = bytes(data) # Convert to immutable bytes
363
+ immutable = Bytes(data) # Convert to immutable Bytes
364
+
365
+ # Encoding/Decoding (same interface as Bytes)
366
+ encoded = data.encode() # [length][raw_bytes]
367
+ decoded = ByteArray.decode(encoded)
368
+
369
+ # JSON serialization (same interface as Bytes)
370
+ json_str = data.to_json() # "48656c6c6f..."
371
+ restored = ByteArray.from_json(json_str)
372
+ ```
373
+
374
+ **Key Differences:**
375
+ - **Bytes**: Immutable, extends `bytes`, memory-efficient for read-only data
376
+ - **ByteArray**: Mutable, extends `bytearray`, suitable for dynamic byte manipulation
377
+ - **Shared Functionality**: Both support identical bit conversion, JSON serialization, and binary encoding through `BytesMixin`
378
+
379
+ **Common Features (Both Types):**
380
+ - Bit-level conversion with MSB/LSB support
381
+ - Hex string JSON serialization with 0x prefix support
382
+ - Efficient binary encoding with length prefix
383
+ - String representation and validation
384
+ - Memory-efficient operations
385
+
386
+ #### Bits (Bit Arrays - Sequence of bool)
387
+
388
+ ```python
389
+ from tsrkit_types.bits import Bits
390
+
391
+ # Creation
392
+ bits = Bits([True, False, True, True, False])
393
+ bits = Bits.from_hex("1A3F") # From hex string
394
+ bits = Bits.from_int(42, 8) # From integer with bit width
395
+
396
+ # Fixed-size parameterized bits
397
+ FixedBits = Bits[8] # Exactly 8 bits
398
+ fixed = FixedBits([True, False, True, True, False, False, True, False])
337
399
 
338
400
  # Operations
339
- bit_val = bits[0] # Get bit at index
340
- bits[1] = True # Set bit at index
341
- bits.append(False) # Add bit
342
- bits.extend([True, False]) # Add multiple bits
401
+ bit_val = bits[0] # Get bit at index
402
+ bits[1] = True # Set bit at index
403
+ bits.append(False) # Add bit
404
+ bits.extend([True, False]) # Add multiple bits
343
405
 
344
406
  # Conversion
345
- hex_str = bits.to_hex() # Convert to hex string
346
- int_val = bits.to_int() # Convert to integer
407
+ hex_str = bits.to_hex() # Convert to hex string
408
+ int_val = bits.to_int() # Convert to integer
409
+
410
+ # Bit order specification
411
+ bits_msb = Bits([True, False], bit_order="MSB") # Most significant bit first
412
+ bits_lsb = Bits([True, False], bit_order="LSB") # Least significant bit first
347
413
 
348
414
  # Encoding
349
- encoded = bits.encode() # [length][packed_bits]
350
- decoded = BitArray.decode(encoded)
415
+ encoded = bits.encode() # [length][packed_bits]
416
+ decoded = Bits.decode(encoded)
417
+
418
+ # JSON serialization
419
+ json_str = bits.to_json() # Hex string representation
420
+ restored = Bits.from_json(json_str)
351
421
  ```
352
422
 
353
423
  ### Enum (Extension of Python Enum, with Codable + JSON support)
@@ -558,6 +628,16 @@ buffer = bytearray(1024)
558
628
  offset = 0
559
629
  offset += value1.encode_into(buffer, offset)
560
630
  offset += value2.encode_into(buffer, offset)
631
+
632
+ # Choose appropriate byte type for your use case
633
+ # Use Bytes for immutable binary data (memory efficient, read-only)
634
+ config_data = Bytes(b"static configuration")
635
+
636
+ # Use ByteArray for dynamic binary buffers (mutable, growing data)
637
+ dynamic_buffer = ByteArray()
638
+ dynamic_buffer.extend([0x01, 0x02])
639
+ dynamic_buffer.append(0x03)
640
+ dynamic_buffer.insert(0, 0x00) # Result: [0x00, 0x01, 0x02, 0x03]
561
641
  ```
562
642
 
563
643
  ## Examples
@@ -0,0 +1,21 @@
1
+ tsrkit_types/__init__.py,sha256=dPiakZ3q_YZLcj3nW5CLjI8CA9rE8umqtv2camWl3Cs,1956
2
+ tsrkit_types/bits.py,sha256=33WLfuUYdzGscoc-E3InTYdbJRIDeixoe_pG0XAv71c,4213
3
+ tsrkit_types/bool.py,sha256=Veu6KLIeiRFnIW1sbawJ0haF0veyaL-As5mBiypq-uE,1280
4
+ tsrkit_types/bytearray.py,sha256=DaMnACq-7pzEW-pRO2f_IAny_8ldhSt3G5D24Soywk4,1662
5
+ tsrkit_types/bytes.py,sha256=ayuD8AUTbZ4LFnceG8TBzAGAYXGht0pYQcmX-7ZtwyI,2762
6
+ tsrkit_types/bytes_common.py,sha256=b1Zqh_NhkCX718QaZC52J3nEzKAi1Fe8E0nefWVwwmo,2301
7
+ tsrkit_types/choice.py,sha256=zNLvB1jSZERot6Qo9cxxkPcY84DAJss8o0ULyp9D--4,5196
8
+ tsrkit_types/dictionary.py,sha256=GcUvaHC1VOEc3OzUVAH0id--ncLqTJ6ZRz-lucMNRdY,5618
9
+ tsrkit_types/enum.py,sha256=3MyLW15_ToQQdctJjcMY8Xb3OsS_Ad997OOEo8FjVeA,4256
10
+ tsrkit_types/integers.py,sha256=w8WCbzzQP0YX3ky4iP-fbNeOBHsIydsU0Tady8iGQPQ,8026
11
+ tsrkit_types/null.py,sha256=OwHVhWLdazaFLXXR8iHPaHe67NPmYHDCavBxUfcZ53s,1318
12
+ tsrkit_types/option.py,sha256=xwtaAFN20GjaGqzYZAAo1fZqqUZnCYHdy2bjshhY620,1631
13
+ tsrkit_types/sequences.py,sha256=KOT5-4mia5Lc0CMjIF7Yqe34Sei92ZQ2s6GaP13soUg,8090
14
+ tsrkit_types/string.py,sha256=8rvg0BwvyhQnbMW3qornmBFTQHeTQFUfwD7OC0eVgcY,2575
15
+ tsrkit_types/struct.py,sha256=GoXWFc1OqO9CXnxFS7Xkms7J-rtZe8h32LkAIBlzdSM,3497
16
+ tsrkit_types/itf/codable.py,sha256=agx8YSVWGBeEiLrU9eqh3ZoeTgpJsmmIlW622xIKqPI,2810
17
+ tsrkit_types-0.1.3.dist-info/licenses/LICENSE,sha256=TwnDvVCPwHadHWLUuY1sPx03XNw1jzh_ZmoDBNai9Uc,1072
18
+ tsrkit_types-0.1.3.dist-info/METADATA,sha256=W_kNQJPjZnMV4wh3tqG7ggWcec8tL1d6gLWx2K4jvVM,22755
19
+ tsrkit_types-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ tsrkit_types-0.1.3.dist-info/top_level.txt,sha256=pnVhnUsnZ_A0FIj1zHwDw3suMGrfMJwusp-4GPVY1CM,13
21
+ tsrkit_types-0.1.3.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- tsrkit_types/__init__.py,sha256=Vw03Fv9RnfmZGqzBpFhuglyewh4MxKR8T5bdWdal1VU,1870
2
- tsrkit_types/bits.py,sha256=ggx2lDRWtBBIYQBtKEx-XirwYTJdajctCEpd3VmuXrc,3275
3
- tsrkit_types/bool.py,sha256=anu5RquxV8xdDMj7aIwOOCP3WnK6vMNst4WUPlfeKm4,1460
4
- tsrkit_types/bytes.py,sha256=rw0sjw1fO9bOElftWehs8oLCZFMXWeajjCyOH-5kChU,3756
5
- tsrkit_types/choice.py,sha256=L4Pb0ZBvaoCW2h9VBLclmmgIJvfO0Yeb2khPniFN2Xo,5113
6
- tsrkit_types/dictionary.py,sha256=EktyMKH27HVoR1xNhrkV8fW6KfcxpbHiiYPYoCrFdoI,5614
7
- tsrkit_types/enum.py,sha256=3MyLW15_ToQQdctJjcMY8Xb3OsS_Ad997OOEo8FjVeA,4256
8
- tsrkit_types/integers.py,sha256=GT9xVZYBNMcPpShJIlSazwiZYGhoBowhR0MA_cGO5Q0,7200
9
- tsrkit_types/null.py,sha256=OwHVhWLdazaFLXXR8iHPaHe67NPmYHDCavBxUfcZ53s,1318
10
- tsrkit_types/option.py,sha256=V4J8SmhUDjINqMM6Ml3ho0YwTy2UT-avrjoYh32Swyg,879
11
- tsrkit_types/sequences.py,sha256=6aseAdoAkD3RG20ag6CTzVZPR_YlJfLHo82JrNZY3dE,8092
12
- tsrkit_types/string.py,sha256=8rvg0BwvyhQnbMW3qornmBFTQHeTQFUfwD7OC0eVgcY,2575
13
- tsrkit_types/struct.py,sha256=ni8jwZlfT_wehardfImBJ0AsvnO-oCW-3APplLXsqGY,3206
14
- tsrkit_types/itf/codable.py,sha256=jWfoYZ31MZ6k3nlHb2IwJdjD3G0pVPyZWlF0Sv9MAWs,2794
15
- tsrkit_types-0.1.2.dist-info/licenses/LICENSE,sha256=TwnDvVCPwHadHWLUuY1sPx03XNw1jzh_ZmoDBNai9Uc,1072
16
- tsrkit_types-0.1.2.dist-info/METADATA,sha256=Yt9wxA4VDq7jhjzPGrrpcQ4rhjgMUQN3hoTl9rjT3ec,19353
17
- tsrkit_types-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
- tsrkit_types-0.1.2.dist-info/top_level.txt,sha256=pnVhnUsnZ_A0FIj1zHwDw3suMGrfMJwusp-4GPVY1CM,13
19
- tsrkit_types-0.1.2.dist-info/RECORD,,