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/__init__.py +76 -0
- tsrkit_types/bits.py +115 -0
- tsrkit_types/bool.py +41 -0
- tsrkit_types/bytes.py +102 -0
- tsrkit_types/choice.py +127 -0
- tsrkit_types/dictionary.py +145 -0
- tsrkit_types/enum.py +128 -0
- tsrkit_types/integers.py +203 -0
- tsrkit_types/itf/codable.py +82 -0
- tsrkit_types/null.py +43 -0
- tsrkit_types/option.py +30 -0
- tsrkit_types/sequences.py +212 -0
- tsrkit_types/string.py +66 -0
- tsrkit_types/struct.py +82 -0
- tsrkit_types-0.1.0.dist-info/METADATA +750 -0
- tsrkit_types-0.1.0.dist-info/RECORD +19 -0
- tsrkit_types-0.1.0.dist-info/WHEEL +5 -0
- tsrkit_types-0.1.0.dist-info/licenses/LICENSE +21 -0
- tsrkit_types-0.1.0.dist-info/top_level.txt +1 -0
tsrkit_types/string.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from typing import Union, Tuple
|
|
2
|
+
|
|
3
|
+
from tsrkit_types.integers import Uint
|
|
4
|
+
from tsrkit_types.itf.codable import Codable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class String(str, Codable):
|
|
8
|
+
"""
|
|
9
|
+
UTF-8 encoded string type that implements the Codable interface.
|
|
10
|
+
|
|
11
|
+
Examples:
|
|
12
|
+
>>> s = String("Hello")
|
|
13
|
+
>>> str(s)
|
|
14
|
+
'Hello'
|
|
15
|
+
>>> len(s)
|
|
16
|
+
5
|
|
17
|
+
>>> s.encode()
|
|
18
|
+
b'\\x05Hello' # Length prefix followed by UTF-8 bytes
|
|
19
|
+
|
|
20
|
+
Note:
|
|
21
|
+
String length is measured in UTF-16 code units, which means some Unicode
|
|
22
|
+
characters (like emojis) may count as 2 units. This matches Python's
|
|
23
|
+
string length behavior.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------- #
|
|
27
|
+
# Serialization #
|
|
28
|
+
# ---------------------------------------------------------------------------- #
|
|
29
|
+
def encode(self) -> bytes:
|
|
30
|
+
buffer = bytearray(self.encode_size())
|
|
31
|
+
self.encode_into(buffer)
|
|
32
|
+
return buffer
|
|
33
|
+
|
|
34
|
+
def encode_size(self) -> int:
|
|
35
|
+
utf8_bytes = str(self).encode('utf-8')
|
|
36
|
+
return Uint(len(utf8_bytes)).encode_size() + len(utf8_bytes)
|
|
37
|
+
|
|
38
|
+
def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
|
|
39
|
+
current_offset = offset
|
|
40
|
+
utf8_bytes = str(self).encode('utf-8')
|
|
41
|
+
current_offset += Uint(len(utf8_bytes)).encode_into(buffer, current_offset)
|
|
42
|
+
buffer[current_offset:current_offset + len(utf8_bytes)] = utf8_bytes
|
|
43
|
+
return current_offset + len(utf8_bytes) - offset
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def decode_from(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple["String", int]:
|
|
47
|
+
current_offset = offset
|
|
48
|
+
byte_len, size = Uint.decode_from(buffer, current_offset)
|
|
49
|
+
current_offset += size
|
|
50
|
+
utf8_bytes = buffer[current_offset:current_offset + byte_len]
|
|
51
|
+
return cls(utf8_bytes.decode('utf-8')), current_offset + byte_len - offset
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def decode(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple["String", int]:
|
|
55
|
+
value, bytes_read = cls.decode_from(buffer, offset)
|
|
56
|
+
return value
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------------------- #
|
|
59
|
+
# JSON Serde #
|
|
60
|
+
# ---------------------------------------------------------------------------- #
|
|
61
|
+
def to_json(self) -> str:
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_json(cls, data: str) -> "String":
|
|
66
|
+
return cls(data)
|
tsrkit_types/struct.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from dataclasses import dataclass, fields
|
|
2
|
+
from typing import Any, Tuple, Union
|
|
3
|
+
from tsrkit_types.itf.codable import Codable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def struct(_cls=None, *, frozen=False, **kwargs):
|
|
7
|
+
"""Extension of dataclass to support serialization and json operations.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
>>> @struct
|
|
11
|
+
>>> class Person:
|
|
12
|
+
>>> name: String = field(metadata={"name": "first_name"})
|
|
13
|
+
>>> age: Uint[8] = field(metadata={"default": 0})
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
def wrap(cls):
|
|
17
|
+
new_cls = dataclass(cls, frozen=frozen, **kwargs)
|
|
18
|
+
|
|
19
|
+
orig_init = new_cls.__init__
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
for field in fields(self):
|
|
23
|
+
# If the field is not found, but has a default, set it
|
|
24
|
+
if field.name not in kwargs and field.metadata.get("default") is not None:
|
|
25
|
+
kwargs[field.name] = field.metadata.get("default")
|
|
26
|
+
orig_init(self, *args, **kwargs)
|
|
27
|
+
|
|
28
|
+
def encode_size(self) -> int:
|
|
29
|
+
return sum(getattr(self, field.name).encode_size() for field in fields(self))
|
|
30
|
+
|
|
31
|
+
def encode_into(self, buffer: bytes, offset = 0) -> int:
|
|
32
|
+
current_offset = offset
|
|
33
|
+
for field in fields(self):
|
|
34
|
+
item = getattr(self, field.name)
|
|
35
|
+
size = item.encode_into(buffer, current_offset)
|
|
36
|
+
current_offset += size
|
|
37
|
+
|
|
38
|
+
return current_offset - offset
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def decode_from(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple[Any, int]:
|
|
42
|
+
current_offset = offset
|
|
43
|
+
decoded_values = {}
|
|
44
|
+
for field in fields(cls):
|
|
45
|
+
field_type = field.type
|
|
46
|
+
value, size = field_type.decode_from(buffer, current_offset)
|
|
47
|
+
decoded_values[field.name] = value
|
|
48
|
+
current_offset += size
|
|
49
|
+
instance = cls(**decoded_values)
|
|
50
|
+
return instance, current_offset - offset
|
|
51
|
+
|
|
52
|
+
def to_json(self) -> dict:
|
|
53
|
+
return {field.metadata.get("name", field.name): getattr(self, field.name).to_json() for field in fields(self)}
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_json(cls, data: dict) -> Any:
|
|
57
|
+
init_data = {}
|
|
58
|
+
for field in fields(cls):
|
|
59
|
+
k = field.metadata.get("name", field.name)
|
|
60
|
+
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}")
|
|
66
|
+
else:
|
|
67
|
+
init_data[field.name] = field.type.from_json(v)
|
|
68
|
+
return cls(**init_data)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
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
|
|
77
|
+
|
|
78
|
+
new_cls = type(new_cls.__name__, (Codable, new_cls), dict(new_cls.__dict__))
|
|
79
|
+
|
|
80
|
+
return new_cls
|
|
81
|
+
|
|
82
|
+
return wrap if _cls is None else wrap(_cls)
|