tsrkit-types 0.1.8__py3-none-any.whl → 0.1.9__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
@@ -90,6 +90,6 @@ __all__ = [
90
90
  ]
91
91
 
92
92
  # Version information
93
- __version__ = "1.0.0"
93
+ __version__ = "0.1.9"
94
94
  __author__ = "TSRKit Team"
95
95
  __license__ = "MIT"
tsrkit_types/bytes.py CHANGED
@@ -4,6 +4,7 @@ from tsrkit_types.integers import Uint
4
4
  from tsrkit_types.itf.codable import Codable
5
5
  from tsrkit_types.bytes_common import BytesMixin
6
6
 
7
+
7
8
  class BytesCheckMeta(abc.ABCMeta):
8
9
  """Meta class to check if the instance is a bytes with the same key and value types"""
9
10
  def __instancecheck__(cls, instance):
@@ -58,8 +59,20 @@ class Bytes(bytes, Codable, BytesMixin, metaclass=BytesCheckMeta):
58
59
 
59
60
  if len(buffer[current_offset:current_offset+_len]) < _len:
60
61
  raise TypeError("Insufficient buffer")
61
- return cls(buffer[current_offset:current_offset+_len]), current_offset + _len - offset
62
-
62
+
63
+ result = (cls(buffer[current_offset:current_offset+_len]), current_offset + _len - offset)
64
+
65
+ return result
66
+
67
+ def __deepcopy__(self, memo):
68
+ # immutable; safe to reuse or create a new same-typed instance
69
+ existing = memo.get(id(self))
70
+ if existing is not None:
71
+ return existing
72
+ new = type(self)(bytes(self))
73
+ memo[id(self)] = new
74
+ return new
75
+
63
76
  # ---------------------------------------------------------------------------- #
64
77
  # JSON Serialization #
65
78
  # ---------------------------------------------------------------------------- #
@@ -4,6 +4,23 @@ Common functionality for Bytes and ByteArray types.
4
4
 
5
5
  from typing import Union
6
6
 
7
+ # Global lookup tables for maximum performance - initialized once
8
+ _BYTE_TO_BITS_MSB = []
9
+ _BITS_TO_BYTE_MSB = {}
10
+ _TABLES_INITIALIZED = False
11
+
12
+ def _init_lookup_tables():
13
+ """Initialize lookup tables once for optimal performance."""
14
+ global _BYTE_TO_BITS_MSB, _BITS_TO_BYTE_MSB, _TABLES_INITIALIZED
15
+ if not _TABLES_INITIALIZED:
16
+ for i in range(256):
17
+ # Convert byte to 8 bits (MSB first)
18
+ bits = [(i >> (7 - j)) & 1 for j in range(8)]
19
+ _BYTE_TO_BITS_MSB.append(bits)
20
+ # Reverse lookup: bits tuple to byte value
21
+ _BITS_TO_BYTE_MSB[tuple(bits)] = i
22
+ _TABLES_INITIALIZED = True
23
+
7
24
 
8
25
  class BytesMixin:
9
26
  """Mixin providing common functionality for bytes-like types."""
@@ -11,42 +28,57 @@ class BytesMixin:
11
28
  @classmethod
12
29
  def from_bits(cls, bits: list[bool], bit_order: str = "msb"):
13
30
  """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
+ # Fast path for MSB (most common case) using lookup tables
32
+ if bit_order == "msb":
33
+ _init_lookup_tables()
34
+
35
+ # Convert and pad to multiple of 8
36
+ int_bits = [int(bool(b)) for b in bits]
37
+ pad = (8 - len(int_bits) % 8) % 8
38
+ int_bits.extend([0] * pad)
39
+
40
+ result = []
41
+ for i in range(0, len(int_bits), 8):
42
+ byte_bits = tuple(int_bits[i:i+8])
43
+ result.append(_BITS_TO_BYTE_MSB[byte_bits])
44
+ return cls(bytes(result))
45
+
46
+ # LSB implementation
47
+ elif bit_order == "lsb":
48
+ int_bits = [int(bool(b)) for b in bits]
49
+ pad = (8 - len(int_bits) % 8) % 8
50
+ int_bits.extend([0] * pad)
51
+
52
+ result = []
53
+ for i in range(0, len(int_bits), 8):
54
+ byte_bits = int_bits[i:i + 8]
31
55
  val = 0
32
56
  for bit in reversed(byte_bits):
33
57
  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))
58
+ result.append(val)
59
+ return cls(bytes(result))
60
+ else:
61
+ raise ValueError(f"Unknown bit_order: {bit_order}")
38
62
 
39
63
  def to_bits(self, bit_order: str = "msb") -> list[bool]:
40
64
  """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":
65
+ # Fast path for MSB using lookup table
66
+ if bit_order == "msb":
67
+ _init_lookup_tables()
68
+
69
+ result = []
70
+ for byte in self:
71
+ result.extend(_BYTE_TO_BITS_MSB[byte])
72
+ return [bool(b) for b in result]
73
+
74
+ # LSB implementation
75
+ elif bit_order == "lsb":
76
+ bits = []
77
+ for byte in self:
46
78
  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
79
+ return bits
80
+ else:
81
+ raise ValueError(f"Unknown bit_order: {bit_order}")
50
82
 
51
83
  def to_json(self):
52
84
  """Convert bytes to hex string for JSON serialization."""
@@ -61,6 +93,40 @@ class BytesMixin:
61
93
  def __str__(self):
62
94
  return f"{self.__class__.__name__}({self.hex()})"
63
95
 
96
+ def slice_bits(self, start_bit: int, end_bit: int) -> list[bool]:
97
+ """Extract bit slice efficiently without converting entire byte array."""
98
+ if start_bit >= end_bit:
99
+ return []
100
+
101
+ start_byte = start_bit // 8
102
+ end_byte = (end_bit - 1) // 8 + 1
103
+ start_bit_offset = start_bit % 8
104
+
105
+ if start_byte >= len(self):
106
+ return [False] * (end_bit - start_bit)
107
+
108
+ # Extract relevant bytes and convert only what we need
109
+ relevant_bytes = self[start_byte:min(end_byte, len(self))]
110
+
111
+ # Use global lookup table for optimal performance
112
+ _init_lookup_tables()
113
+
114
+ result = []
115
+ for byte in relevant_bytes:
116
+ result.extend(_BYTE_TO_BITS_MSB[byte])
117
+
118
+ # Slice to exact range
119
+ local_start = start_bit_offset if start_byte < len(self) else 0
120
+ local_end = len(result) if end_byte > len(self) else (end_bit - start_byte * 8)
121
+
122
+ bits = result[local_start:local_end]
123
+
124
+ # Pad with False if we ran out of data
125
+ if len(bits) < (end_bit - start_bit):
126
+ bits.extend([False] * (end_bit - start_bit - len(bits)))
127
+
128
+ return [bool(b) for b in bits]
129
+
64
130
 
65
131
  def validate_bit_order(bit_order: str) -> None:
66
132
  """Validate bit order parameter."""
tsrkit_types/choice.py CHANGED
@@ -3,7 +3,7 @@ from typing import ClassVar, Optional, Union, Tuple, Any
3
3
  from tsrkit_types.integers import Uint
4
4
  from tsrkit_types.itf.codable import Codable
5
5
 
6
- ChoiceType = Tuple[Optional[str], type] | type
6
+ ChoiceType = Union[Tuple[Optional[str], type], type]
7
7
 
8
8
  class Choice(Codable):
9
9
  """
@@ -43,7 +43,7 @@ class Choice(Codable):
43
43
  def _choice_keys(self) -> Tuple[Optional[str]]:
44
44
  return tuple(self._choice[0] for self._choice in self._opt_types)
45
45
 
46
- def __class_getitem__(cls, opt_t: Tuple[type] | type):
46
+ def __class_getitem__(cls, opt_t: Union[Tuple[type], type]):
47
47
  _opt_types = []
48
48
  if isinstance(opt_t, type):
49
49
  _opt_types.append((None, opt_t))
@@ -98,7 +98,7 @@ class Choice(Codable):
98
98
  return self._value.to_json() if not self._choice_key else {self._choice_key: self._value.to_json()}
99
99
 
100
100
  @classmethod
101
- def from_json(cls, data: dict | Any) -> "Choice":
101
+ def from_json(cls, data: Union[dict, Any]) -> "Choice":
102
102
  if isinstance(data, dict):
103
103
  opt_type = next((x for x in cls._opt_types if x[0] == list(data.keys())[0]), None)
104
104
  if opt_type is None:
@@ -132,4 +132,4 @@ class Choice(Codable):
132
132
  tag, tag_size = Uint.decode_from(buffer, offset)
133
133
  value, val_size = cls._opt_types[tag][1].decode_from(buffer, offset+tag_size)
134
134
 
135
- return cls(value), tag_size+val_size
135
+ return cls(value, key=cls._opt_types[tag][0]), tag_size+val_size
@@ -126,7 +126,7 @@ class Dictionary(dict, Codable, Generic[K, V], metaclass=DictCheckMeta):
126
126
  def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
127
127
  current_offset = offset
128
128
  current_offset += Uint(len(self)).encode_into(buffer, current_offset)
129
- for k, v in sorted(self.items(), key=lambda x: x[0].encode()):
129
+ for k, v in sorted(self.items(), key=lambda x: x[0]):
130
130
  current_offset += k.encode_into(buffer, current_offset)
131
131
  current_offset += v.encode_into(buffer, current_offset)
132
132
  return current_offset - offset
tsrkit_types/integers.py CHANGED
@@ -1,7 +1,18 @@
1
1
  import abc
2
2
  from decimal import Decimal
3
3
  import math
4
- from typing import Any, Tuple, Union, Callable
4
+ from typing import Any, Optional, Tuple, Union, Callable
5
+
6
+ try:
7
+ from typing import Self
8
+ except ImportError:
9
+ # For Python < 3.11, use TYPE_CHECKING and forward reference
10
+ from typing import TYPE_CHECKING
11
+ if TYPE_CHECKING:
12
+ from typing import Self
13
+ else:
14
+ Self = "Uint" # Forward reference string
15
+
5
16
  from tsrkit_types.itf.codable import Codable
6
17
 
7
18
 
@@ -54,8 +65,9 @@ class Int(int, Codable, metaclass=IntCheckMeta):
54
65
  byte_size: int = 0
55
66
  signed = False
56
67
  _bound = 1 << 64
57
-
58
- def __class_getitem__(cls, data: int | tuple | bool | None):
68
+
69
+ @classmethod
70
+ def __class_getitem__(cls, data: Optional[Union[int, tuple, bool]]):
59
71
  """
60
72
  Args:
61
73
  data: either byte_size or (byte_size, signed)
@@ -230,7 +242,7 @@ class Int(int, Codable, metaclass=IntCheckMeta):
230
242
  raise ValueError(f"Invalid bit order: {bit_order}")
231
243
 
232
244
  @classmethod
233
- def from_bits(cls, bits: list[bool], bit_order: str = "msb") -> "Uint":
245
+ def from_bits(cls, bits: list[bool], bit_order: str = "msb") -> "Int":
234
246
  """Convert bits to an int"""
235
247
  if bit_order == "msb":
236
248
  return cls(int("".join(str(int(b)) for b in bits), 2))
@@ -80,4 +80,7 @@ class Codable(ABC, Generic[T]):
80
80
  offset: The offset at which to start encoding the value.
81
81
  """
82
82
  if len(buffer) - offset < size:
83
- raise ValueError("Buffer too small to encode value")
83
+ raise ValueError("Buffer too small to encode value")
84
+
85
+ def __reduce__(self):
86
+ return (self.__class__.decode, (self.encode(),))
tsrkit_types/option.py CHANGED
@@ -18,7 +18,7 @@ class Option(Choice, Generic[T]):
18
18
  (Option,),
19
19
  {"_opt_types": ((None, NullType), (None, opt_t))})
20
20
 
21
- def __init__(self, val: T|NullType = Null):
21
+ def __init__(self, val: T|NullType = Null, key = None):
22
22
  super().__init__(val)
23
23
 
24
24
  def set(self, value: T|NullType = Null, key: Optional[str] = None):
tsrkit_types/struct.py CHANGED
@@ -1,10 +1,11 @@
1
1
  from dataclasses import dataclass, fields
2
- from typing import Any, Tuple, Union
2
+ from typing import Any, Tuple, Union, dataclass_transform
3
3
  from tsrkit_types.itf.codable import Codable
4
4
  from tsrkit_types.null import NullType
5
5
  from tsrkit_types.option import Option
6
6
 
7
7
 
8
+ @dataclass_transform()
8
9
  def structure(_cls=None, *, frozen=False, **kwargs):
9
10
  """Extension of dataclass to support serialization and json operations.
10
11
 
@@ -66,7 +67,6 @@ def structure(_cls=None, *, frozen=False, **kwargs):
66
67
  init_data[field.name] = field.type.from_json(v)
67
68
  return cls(**init_data)
68
69
 
69
-
70
70
  new_cls.__init__ = __init__
71
71
 
72
72
  # Only overwrite if the method is not already defined
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsrkit-types
3
- Version: 0.1.8
3
+ Version: 0.1.9
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
@@ -27,7 +27,7 @@ Classifier: Topic :: Utilities
27
27
  Classifier: Topic :: System :: Archiving
28
28
  Classifier: Topic :: Communications
29
29
  Classifier: Typing :: Typed
30
- Requires-Python: >=3.11
30
+ Requires-Python: <3.13,>=3.11
31
31
  Description-Content-Type: text/markdown
32
32
  License-File: LICENSE
33
33
  Provides-Extra: dev
@@ -0,0 +1,21 @@
1
+ tsrkit_types/__init__.py,sha256=5TTpyp57FCC9FKOXQ8XQl2Tnow9Re99jW3yepMl5ZOk,1900
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=5uBK_5bHVxRAocfRHDojCTtS2ysUh-8bFdWglO-ZM48,1781
5
+ tsrkit_types/bytes.py,sha256=hsUJu1YQoFI68RE4lhAPkNUppXGNFvL21NKKyIumUi8,3195
6
+ tsrkit_types/bytes_common.py,sha256=bx1B3smZaT0Z5P_9nHJ-WCZQxH7XKAN1uakEWdqDous,4724
7
+ tsrkit_types/choice.py,sha256=k0ZBvWEniP4ewc1W8BAbwaFffiauEUegQhTY6OPYZ0o,5299
8
+ tsrkit_types/dictionary.py,sha256=M8kugpiwwi383kgq0JMJMgkNU-VlPdbbUYHWqFynEFc,5609
9
+ tsrkit_types/enum.py,sha256=3MyLW15_ToQQdctJjcMY8Xb3OsS_Ad997OOEo8FjVeA,4256
10
+ tsrkit_types/integers.py,sha256=zeJZ-Q3S74xx6D9BvGYP94zQhslH05lbTSb2rGfiVCk,9176
11
+ tsrkit_types/null.py,sha256=vzobz4-xJrUeky8pzbg-Qmz4N6b7GsCNf8TafbstlgY,1372
12
+ tsrkit_types/option.py,sha256=m6ltnfTQQDL1yh1PMo7pEElflrYp90Y0AHikP4ujXwI,1643
13
+ tsrkit_types/sequences.py,sha256=KOT5-4mia5Lc0CMjIF7Yqe34Sei92ZQ2s6GaP13soUg,8090
14
+ tsrkit_types/string.py,sha256=8rvg0BwvyhQnbMW3qornmBFTQHeTQFUfwD7OC0eVgcY,2575
15
+ tsrkit_types/struct.py,sha256=qD_hIz0-7gAryhc79ARod-ylqSbjTiZLbXCmdLB4lmo,3540
16
+ tsrkit_types/itf/codable.py,sha256=qZ_UsFl3gcpaeztbHcaxF4U3Km_PfPa7ZvW3z5gk_Gk,2894
17
+ tsrkit_types-0.1.9.dist-info/licenses/LICENSE,sha256=TwnDvVCPwHadHWLUuY1sPx03XNw1jzh_ZmoDBNai9Uc,1072
18
+ tsrkit_types-0.1.9.dist-info/METADATA,sha256=yhVuEqs6w0KwCfcnSpOJ7k2JX2HFvdUapqIy4m7WMy4,22902
19
+ tsrkit_types-0.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ tsrkit_types-0.1.9.dist-info/top_level.txt,sha256=pnVhnUsnZ_A0FIj1zHwDw3suMGrfMJwusp-4GPVY1CM,13
21
+ tsrkit_types-0.1.9.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- tsrkit_types/__init__.py,sha256=PlhqxNwjXaJU5vTIqJdLvC5BBfExLAoKQ57peNG2Opc,1900
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=5uBK_5bHVxRAocfRHDojCTtS2ysUh-8bFdWglO-ZM48,1781
5
- tsrkit_types/bytes.py,sha256=tsyeItlI4LZPnkxFY-5_kgoDs04Zk_Xuan1erkG4Ff0,2863
6
- tsrkit_types/bytes_common.py,sha256=b1Zqh_NhkCX718QaZC52J3nEzKAi1Fe8E0nefWVwwmo,2301
7
- tsrkit_types/choice.py,sha256=OVHxIr2PcnaTpo5sAMjipCArc5oATNsyYkqZNRqU7bA,5253
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=Xu9hjGQWW_NP08QIhKSvlxTeA3UmKsQC1A3NatgILQo,8867
11
- tsrkit_types/null.py,sha256=vzobz4-xJrUeky8pzbg-Qmz4N6b7GsCNf8TafbstlgY,1372
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.8.dist-info/licenses/LICENSE,sha256=TwnDvVCPwHadHWLUuY1sPx03XNw1jzh_ZmoDBNai9Uc,1072
18
- tsrkit_types-0.1.8.dist-info/METADATA,sha256=XcMJiwod5CkNHpwSSrMTOvRZxMMpv9FEDhi_AS7IYPU,22896
19
- tsrkit_types-0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- tsrkit_types-0.1.8.dist-info/top_level.txt,sha256=pnVhnUsnZ_A0FIj1zHwDw3suMGrfMJwusp-4GPVY1CM,13
21
- tsrkit_types-0.1.8.dist-info/RECORD,,