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.
@@ -0,0 +1,76 @@
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
+ # ]
tsrkit_types/bits.py ADDED
@@ -0,0 +1,115 @@
1
+ from typing import ClassVar, Sequence, Tuple, Union
2
+
3
+ from tsrkit_types.bytes import Bytes
4
+ from tsrkit_types.integers import Uint
5
+ from tsrkit_types.sequences import Seq
6
+
7
+
8
+ class Bits(Seq):
9
+ """Bits[size, order]"""
10
+ _element_type = bool
11
+ _min_length: ClassVar[int] = 0
12
+ _max_length: ClassVar[int] = 2 ** 64
13
+ _order: ClassVar[str] = "msb"
14
+
15
+ def __class_getitem__(cls, params):
16
+ min_l, max_l, _bo = 0, 2**64, "msb"
17
+ if isinstance(params, tuple):
18
+ min_l, max_l, _bo = params[0], params[0], params[1]
19
+ else:
20
+ if isinstance(params, int):
21
+ min_l, max_l = params, params
22
+ else:
23
+ _bo = params
24
+
25
+ return type(cls.__class__.__name__, (cls,), {"_min_length": min_l, "_max_length": max_l, "_order": _bo})
26
+
27
+
28
+ # ---------------------------------------------------------------------------- #
29
+ # JSON Parse #
30
+ # ---------------------------------------------------------------------------- #
31
+
32
+ def to_json(self) -> str:
33
+ return Bytes.from_bits(self, bit_order=self._order).hex()
34
+
35
+ @classmethod
36
+ def from_json(cls, json_str: str) -> "Bits":
37
+ return cls(Bytes.from_hex(json_str).to_bits(bit_order=cls._order))
38
+
39
+ # ---------------------------------------------------------------------------- #
40
+ # Serialization #
41
+ # ---------------------------------------------------------------------------- #
42
+
43
+ def encode_size(self) -> int:
44
+ # Calculate the number of bytes needed
45
+ bit_enc = 0
46
+ if self._length is None:
47
+ bit_enc = Uint(len(self)).encode_size()
48
+
49
+ return bit_enc + ((len(self) + 7) // 8)
50
+
51
+ def encode_into(
52
+ self, buffer: bytearray, offset: int = 0
53
+ ) -> int:
54
+ total_size = self.encode_size()
55
+ self._check_buffer_size(buffer, total_size, offset)
56
+
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:
62
+ # Encode the bit length first
63
+ offset += Uint(len(self)).encode_into(buffer, offset)
64
+ else:
65
+ # Ensure bit length is size of value
66
+ if len(self) != self._length:
67
+ raise ValueError("Bit sequence length mismatch")
68
+
69
+ if not all(
70
+ isinstance(bit, (bool, int)) and bit in (0, 1, True, False)
71
+ for bit in self
72
+ ):
73
+ raise ValueError(f"Bit sequence must contain only 0s and 1s, got an sequence of {self}")
74
+
75
+ buffer[offset : offset + total_size] = Bytes.from_bits(
76
+ self, bit_order=self._order
77
+ )
78
+
79
+ return total_size
80
+
81
+ @classmethod
82
+ def decode_from(
83
+ cls,
84
+ buffer: Union[bytes, bytearray, memoryview],
85
+ offset: int = 0,
86
+ ) -> Tuple[Sequence[bool], int]:
87
+ """
88
+ Decode bit sequence from buffer.
89
+
90
+ Args:
91
+ buffer: Source buffer
92
+ offset: Starting offset
93
+ bit_length: Expected number of bits (required)
94
+
95
+ Returns:
96
+ Tuple of (decoded bit list, bytes read)
97
+
98
+ Raises:
99
+ DecodeError: If buffer too small or bit_length not specified
100
+ """
101
+ _len = cls._length
102
+ if _len is None:
103
+ # Assume first byte is the bit length
104
+ _len, size = Uint.decode_from(buffer, offset)
105
+ offset += size
106
+
107
+ if _len == 0:
108
+ return [], 0
109
+
110
+ # Calculate required bytes
111
+ byte_count = (_len + 7) // 8
112
+ cls._check_buffer_size(buffer, byte_count, offset)
113
+
114
+ result = Bytes(buffer[offset : offset + byte_count]).to_bits(bit_order=cls._order)
115
+ return cls(result), byte_count
tsrkit_types/bool.py ADDED
@@ -0,0 +1,41 @@
1
+ from typing import Tuple, Union
2
+ from tsrkit_types.itf.codable import Codable
3
+
4
+
5
+ class Bool(Codable):
6
+ _value: bool
7
+ def __init__(self, value: bool):
8
+ self._value = value
9
+
10
+ def __bool__(self):
11
+ return bool(self._value)
12
+
13
+ # ---------------------------------------------------------------------------- #
14
+ # Serialization #
15
+ # ---------------------------------------------------------------------------- #
16
+
17
+ def encode_size(self) -> int:
18
+ return 1
19
+
20
+ def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
21
+ buffer[offset] = int(self._value)
22
+ return 1
23
+
24
+ @classmethod
25
+ def decode_from(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple["Bool", int]:
26
+ return cls(bool(buffer[offset])), 1
27
+
28
+ # ---------------------------------------------------------------------------- #
29
+ # JSON Parse #
30
+ # ---------------------------------------------------------------------------- #
31
+
32
+ def to_json(self) -> str:
33
+ return "true" if self._value else "false"
34
+
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")
tsrkit_types/bytes.py ADDED
@@ -0,0 +1,102 @@
1
+ from typing import Tuple, Union, ClassVar, Self
2
+ import base64
3
+
4
+ from tsrkit_types.integers import Uint
5
+ from tsrkit_types.itf.codable import Codable
6
+
7
+ class Bytes(bytes, Codable):
8
+
9
+ _length: ClassVar[Union[None, int]] = None
10
+
11
+ def __class_getitem__(cls, params):
12
+ _len = None
13
+ name = cls.__class__.__name__
14
+ if params and params > 0:
15
+ _len = params
16
+ name = f"ByteArray{_len}"
17
+ return type(name, (cls,), {
18
+ "_length": _len,
19
+ })
20
+
21
+ def __str__(self):
22
+ return f"{self.__class__.__name__}({self.hex()})"
23
+
24
+ @classmethod
25
+ def from_bits(cls, bits: list[bool], bit_order = "msb") -> Self:
26
+ # Sanitize input: make sure bits are 0 or 1
27
+ bits = [int(bool(b)) for b in bits]
28
+ n = len(bits)
29
+ # Pad with zeros to multiple of 8
30
+ pad = (8 - n % 8) % 8
31
+ bits += [0] * pad
32
+
33
+ byte_arr = []
34
+ for i in range(0, len(bits), 8):
35
+ byte_bits = bits[i:i + 8]
36
+ if bit_order == "msb":
37
+ # Most significant bit first
38
+ val = 0
39
+ for bit in byte_bits:
40
+ val = (val << 1) | bit
41
+ elif bit_order == "lsb":
42
+ # Least significant bit first
43
+ val = 0
44
+ for bit in reversed(byte_bits):
45
+ val = (val << 1) | bit
46
+ else:
47
+ raise ValueError(f"Unknown bit_order: {bit_order}")
48
+ # noinspection PyUnreachableCode
49
+ byte_arr.append(val)
50
+ return cls(bytes(byte_arr))
51
+
52
+ def to_bits(self, bit_order="msb") -> list[bool]:
53
+ bits = []
54
+ for byte in self:
55
+ if bit_order == "msb":
56
+ bits.extend([(byte >> i) & 1 for i in reversed(range(8))])
57
+ elif bit_order == "lsb":
58
+ bits.extend([(byte >> i) & 1 for i in range(8)])
59
+ else:
60
+ raise ValueError(f"Unknown bit_order: {bit_order}")
61
+ return bits
62
+
63
+ # ---------------------------------------------------------------------------- #
64
+ # Serialization #
65
+ # ---------------------------------------------------------------------------- #
66
+ def encode_size(self) -> int:
67
+ if self._length is None:
68
+ return Uint(len(self)).encode_size() + len(self)
69
+ return self._length
70
+
71
+ def encode_into(self, buf: bytearray, offset: int = 0) -> int:
72
+ current_offset = offset
73
+ _len = self._length
74
+ if _len is None:
75
+ _len = len(self)
76
+ current_offset += Uint(_len).encode_into(buf, current_offset)
77
+ buf[current_offset:current_offset+_len] = self
78
+ current_offset += _len
79
+ return current_offset - offset
80
+
81
+ @classmethod
82
+ def decode_from(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple[Self, int]:
83
+ current_offset = offset
84
+ _len = cls._length
85
+ if _len is None:
86
+ _len, _inc_offset = Uint.decode_from(buffer, offset)
87
+ current_offset += _inc_offset
88
+ return cls(buffer[current_offset:current_offset+_len]), current_offset + _len - offset
89
+
90
+ # ---------------------------------------------------------------------------- #
91
+ # JSON Serialization #
92
+ # ---------------------------------------------------------------------------- #
93
+ def to_json(self):
94
+ """Convert bytes to hex string for JSON serialization"""
95
+ return self.hex()
96
+
97
+ @classmethod
98
+ def from_json(cls, data: str):
99
+ """Create Bytes instance from hex string"""
100
+ data = data.replace("0x", "")
101
+ return cls(bytes.fromhex(data))
102
+
tsrkit_types/choice.py ADDED
@@ -0,0 +1,127 @@
1
+ from typing import ClassVar, Optional, Union, Tuple, Any, get_origin
2
+ from typing_extensions import get_args
3
+
4
+ from tsrkit_types.integers import Uint
5
+ from tsrkit_types.itf.codable import Codable
6
+
7
+ ChoiceType = Tuple[Optional[str], type] | type
8
+
9
+ class Choice(Codable):
10
+ """
11
+ Can either be defined as:
12
+
13
+
14
+ Usage:
15
+ >>> dir_choice = Choice[T1, T2, ...](T1(XYZ))
16
+ -> makes _opt_types = (None, T1), (None, T2), ...
17
+
18
+ >>> dir_choice.set(T2(ABC))
19
+ -> changes the choice to T2(ABC)
20
+
21
+ >>> class NamedChoice(Choice):
22
+ >>> type1: T1
23
+ >>> type2: T2
24
+ >>> type3: T2
25
+ -> makes _opt_types = (type1, T1), (type2, T2), ...
26
+ >>> a = NamedChoice(T1(XYZ))
27
+ # If setting a choice with multiple types, the first type is the default, or give an optional key string
28
+ >>> b = NamedChoice(T2(ABC))
29
+ >>> b.to_json()
30
+ -> {"type2": "ABC"}
31
+ >>> c = NamedChoice(T2(ABC), key="type3")
32
+ >>> c.to_json()
33
+ -> {"type3": "ABC"}
34
+ """
35
+ _opt_types: ClassVar[Tuple[ChoiceType]]
36
+ _choice_key: Optional[str]
37
+ _value: Any
38
+
39
+ @property
40
+ def _choice_types(self) -> Tuple[type]:
41
+ return tuple(self._choice[1] for self._choice in self._opt_types)
42
+
43
+ @property
44
+ def _choice_keys(self) -> Tuple[Optional[str]]:
45
+ return tuple(self._choice[0] for self._choice in self._opt_types)
46
+
47
+ def __class_getitem__(cls, opt_t: Tuple[type] | type):
48
+ _opt_types = []
49
+ if isinstance(opt_t, type):
50
+ _opt_types.append((None, opt_t))
51
+ else:
52
+ for op in opt_t:
53
+ _opt_types.append((None, op))
54
+ name = f"Choice[{'/'.join(op[1].__class__.__name__ for op in _opt_types)}]"
55
+ return type(name,
56
+ (Choice,),
57
+ {"_opt_types": tuple(_opt_types)})
58
+
59
+ def __init_subclass__(cls, **kwargs):
60
+ super().__init_subclass__(**kwargs)
61
+ ann = getattr(cls, "__annotations__", {})
62
+ if ann:
63
+ cls._opt_types = tuple((field, ann[field]) for field in ann)
64
+
65
+ def __init__(self, value: Any, key: Optional[str] = None) -> None:
66
+ super().__init__()
67
+ self.set(value, key)
68
+
69
+ def unwrap(self) -> Any:
70
+ return self._value
71
+
72
+ def __repr__(self) -> str:
73
+ return f"{self.__class__.__name__}({self._value!r})"
74
+
75
+ def __eq__(self, other):
76
+ if isinstance(other, Choice):
77
+ return other._value == self._value
78
+ return other == self._value
79
+
80
+ def set(self, value: Any, key: Optional[str] = None):
81
+ if not isinstance(value, self._choice_types):
82
+ raise TypeError(f"{value!r} is not a {self._choice_types}")
83
+
84
+ key = key or tuple(self._choice[0] for self._choice in self._opt_types if isinstance(value, self._choice[1]))[0]
85
+ if key not in self._choice_keys:
86
+ raise ValueError(f"Key {key!r} not in {self._choice_keys}")
87
+
88
+ self._choice_key = key
89
+ self._value = value
90
+
91
+ # ---------------------------------------------------------------------------- #
92
+ # JSON Serde #
93
+ # ---------------------------------------------------------------------------- #
94
+
95
+ def to_json(self):
96
+ return self._value.to_json() if not self._choice_key else {self._choice_key: self._value.to_json()}
97
+
98
+ @classmethod
99
+ def from_json(cls, data: dict | Any) -> "Choice":
100
+ if isinstance(data, dict):
101
+ opt_type = next((x for x in cls._opt_types if x[0] == list(data.keys())[0]), None)
102
+ if opt_type is None:
103
+ raise ValueError(f"Key {list(data.keys())[0]} not in {cls._opt_types}")
104
+ return cls(opt_type[1].from_json(list(data.values())[0]), key=opt_type[0])
105
+ return cls(cls._opt_types[0][1].from_json(data), key=cls._opt_types[0][0])
106
+
107
+ # ---------------------------------------------------------------------------- #
108
+ # Serialization #
109
+ # ---------------------------------------------------------------------------- #
110
+
111
+ def encode_size(self) -> int:
112
+ return Uint(len(self._opt_types)).encode_size() + self._value.encode_size()
113
+
114
+ def encode_into(self, buf: bytearray, offset: int = 0) -> int:
115
+ current_offset = offset
116
+ current_offset += Uint(self._choice_types.index(type(self._value))).encode_into(buf, current_offset)
117
+ current_offset += self._value.encode_into(buf, current_offset)
118
+ return current_offset - offset
119
+
120
+ @classmethod
121
+ def decode_from(
122
+ cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0
123
+ ) -> Tuple[Any, int]:
124
+ tag, tag_size = Uint.decode_from(buffer, offset)
125
+ value, val_size = cls._opt_types[tag][1].decode_from(buffer, offset+tag_size)
126
+
127
+ return cls(value), tag_size+val_size
@@ -0,0 +1,145 @@
1
+ import abc
2
+ from typing import (
3
+ Generic,
4
+ Mapping,
5
+ Optional,
6
+ Tuple,
7
+ Type,
8
+ TypeVar,
9
+ Union,
10
+ Dict,
11
+ Any,
12
+ Sequence,
13
+ )
14
+
15
+ from tsrkit_types.integers import Uint
16
+ from tsrkit_types.itf.codable import Codable
17
+
18
+ K = TypeVar("K", bound=Codable)
19
+ V = TypeVar("V", bound=Codable)
20
+
21
+
22
+ class DictCheckMeta(abc.ABCMeta):
23
+ """Meta class to check if the instance is a dictionary with the same key and value types"""
24
+ def __instancecheck__(cls, instance):
25
+ _matches_key_type = getattr(cls, "_key_type", None) == getattr(instance, "_key_type", None)
26
+ _matches_value_type = getattr(cls, "_value_type", None) == getattr(instance, "_value_type", None)
27
+ return isinstance(instance, dict) and _matches_key_type and _matches_value_type
28
+
29
+
30
+ class Dictionary(dict, Codable, Generic[K, V], metaclass=DictCheckMeta):
31
+ """
32
+ Dictionary implementation that supports codec operations.
33
+
34
+ A dictionary that maps keys to values, providing both standard
35
+ dictionary operations and codec functionality for serialization/deserialization.
36
+
37
+ Examples:
38
+ >>> from tsrkit_types.string import String
39
+ >>> from tsrkit_types.integers import Uint
40
+ >>> d = Dictionary({String("key"): Uint(42)})
41
+ >>> d[String("key")]
42
+ Uint(42)
43
+ >>> encoded = d.encode()
44
+ >>> decoded, _ = d.decode_from(encoded)
45
+ >>> decoded == d
46
+ True
47
+ """
48
+
49
+ _key_type: Type[K]
50
+ _value_type: Type[V]
51
+
52
+ _key_name: Optional[str]
53
+ _value_name: Optional[str]
54
+
55
+ def __class_getitem__(cls, params):
56
+ if len(params) >= 2:
57
+ return type(cls.__name__, (cls,), {
58
+ "_key_type": params[0],
59
+ "_value_type": params[1],
60
+ "_key_name": params[2] if len(params) == 4 else None,
61
+ "_value_name": params[3] if len(params) == 4 else None,
62
+ })
63
+ else:
64
+ raise ValueError("Dictionary must be initialized with types as such - Dictionary[K, V, key_name(optional), value_name(optional)]")
65
+
66
+ def __init__(self, initial: Optional[Mapping[K, V]] = None):
67
+ self.update(initial or {})
68
+
69
+ def _validate(self, key: K, value: V):
70
+ if not isinstance(key, self._key_type):
71
+ raise TypeError(f"Dictionary keys must be {self._key_type} but got {type(key)}")
72
+ if not isinstance(value, self._value_type):
73
+ raise TypeError(f"Dictionary values must be {self._value_type} but got {type(value)}")
74
+
75
+ def __setitem__(self, key: K, value: V) -> None:
76
+ """Set value for key."""
77
+ self._validate(key, value)
78
+ super().__setitem__(key, value)
79
+
80
+ def __repr__(self) -> str:
81
+ """Get string representation."""
82
+ items = [f"{k!r}: {v!r}" for k, v in self.items()]
83
+ return f"Dictionary({{{', '.join(items)}}})"
84
+
85
+ def update(self, other: Mapping[K, V]) -> None:
86
+ for key, value in other.items():
87
+ self._validate(key, value)
88
+ super().update(other)
89
+
90
+ # ---------------------------------------------------------------------------- #
91
+ # JSON Serde #
92
+ # ---------------------------------------------------------------------------- #
93
+
94
+ def to_json(self) -> Dict[Any, Any]:
95
+ """Convert to JSON representation."""
96
+ return {k.to_json(): v.to_json() for k, v in self.items()}
97
+
98
+ @classmethod
99
+ def from_json(cls: Type["Dictionary[K, V]"], data: Sequence[Any]) -> "Dictionary[K, V]":
100
+ """Create instance from JSON representation."""
101
+ if not isinstance(data, dict):
102
+ _value = cls({})
103
+ for val in data:
104
+ _value[cls.key_type.from_json(val[cls.key_name])] = cls.value_type.from_json(val[cls.value_name])
105
+ return _value
106
+ else:
107
+ return cls(
108
+ {
109
+ cls._key_type.from_json(k): cls._value_type.from_json(v)
110
+ for k, v in data.items()
111
+ }
112
+ )
113
+
114
+ # ---------------------------------------------------------------------------- #
115
+ # Serialization #
116
+ # ---------------------------------------------------------------------------- #
117
+
118
+ def encode_size(self) -> int:
119
+ total_size = 0
120
+ total_size += Uint(len(self)).encode_size()
121
+ for k, v in self.items():
122
+ total_size += k.encode_size() + v.encode_size()
123
+ return total_size
124
+
125
+ def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
126
+ current_offset = offset
127
+ current_offset += Uint(len(self)).encode_into(buffer, current_offset)
128
+ for k, v in sorted(self.items(), key=lambda x: x[0].encode()):
129
+ current_offset += k.encode_into(buffer, current_offset)
130
+ current_offset += v.encode_into(buffer, current_offset)
131
+ return current_offset - offset
132
+
133
+ @classmethod
134
+ def decode_from(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple["Dictionary[K, V]", int]:
135
+ current_offset = offset
136
+ dict_len, size = Uint.decode_from(buffer, offset)
137
+ current_offset += size
138
+ res = cls()
139
+ for _ in range(dict_len):
140
+ key, size = cls._key_type.decode_from(buffer, current_offset)
141
+ current_offset += size
142
+ value, size = cls._value_type.decode_from(buffer, current_offset)
143
+ current_offset += size
144
+ res[key] = value
145
+ return res, current_offset - offset