structo 0.0.1__tar.gz
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.
- structo-0.0.1/LICENSE +21 -0
- structo-0.0.1/PKG-INFO +8 -0
- structo-0.0.1/pyproject.toml +13 -0
- structo-0.0.1/structo/__init__.py +28 -0
- structo-0.0.1/structo/serialise.py +169 -0
- structo-0.0.1/structo/types.py +80 -0
- structo-0.0.1/structo/utils.py +128 -0
structo-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ben Brady
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
structo-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["flit_core >=3.11,<4"]
|
|
3
|
+
build-backend = "flit_core.buildapi"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "structo"
|
|
7
|
+
authors = [{name = "Ben Brady", email = "benbradybusiness@gmail.com"}]
|
|
8
|
+
license = "MIT"
|
|
9
|
+
license-files = ["LICENSE"]
|
|
10
|
+
dynamic = ["version", "description"]
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
Home = "https://nnilky.site"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Structify
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.0.1"
|
|
6
|
+
|
|
7
|
+
from .serialise import serialize, deserialize
|
|
8
|
+
from .types import (
|
|
9
|
+
SerialiableObject,
|
|
10
|
+
uint8,
|
|
11
|
+
uint16,
|
|
12
|
+
uint32,
|
|
13
|
+
uint64,
|
|
14
|
+
int8,
|
|
15
|
+
int16,
|
|
16
|
+
int32,
|
|
17
|
+
int64,
|
|
18
|
+
float32,
|
|
19
|
+
float64,
|
|
20
|
+
array,
|
|
21
|
+
string,
|
|
22
|
+
)
|
|
23
|
+
from .utils import (
|
|
24
|
+
StructifyReader,
|
|
25
|
+
StructifyWriter,
|
|
26
|
+
serialize_to_bytes,
|
|
27
|
+
deserialize_from_bytes,
|
|
28
|
+
)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import annotationlib
|
|
2
|
+
import io
|
|
3
|
+
import struct
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
from .types import (
|
|
7
|
+
Format,
|
|
8
|
+
SerialiableObject,
|
|
9
|
+
array,
|
|
10
|
+
string,
|
|
11
|
+
blob,
|
|
12
|
+
uint64,
|
|
13
|
+
uint32,
|
|
14
|
+
uint16,
|
|
15
|
+
uint8,
|
|
16
|
+
int64,
|
|
17
|
+
int32,
|
|
18
|
+
int16,
|
|
19
|
+
int8,
|
|
20
|
+
float64,
|
|
21
|
+
float32,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Serializer[T]:
|
|
26
|
+
@staticmethod
|
|
27
|
+
def write(buf: io.Writer, format: Format, value: T): ...
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def read(buf: io.Reader, format: Format) -> T: ...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SerialiableObjectSerializer(Serializer):
|
|
34
|
+
@staticmethod
|
|
35
|
+
def write(buf: io.Writer, format: type, value: SerialiableObject):
|
|
36
|
+
annotations = annotationlib.get_annotations(format)
|
|
37
|
+
for field_key, field_format in annotations.items():
|
|
38
|
+
field_value = getattr(value, field_key)
|
|
39
|
+
serialize(buf, field_format, field_value)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def read(buf: io.Reader, format: type) -> SerialiableObject:
|
|
43
|
+
annotations = annotationlib.get_annotations(format)
|
|
44
|
+
attrs = {}
|
|
45
|
+
for field_key, field_format in annotations.items():
|
|
46
|
+
field_value = deserialize(buf, field_format)
|
|
47
|
+
attrs[field_key] = field_value
|
|
48
|
+
|
|
49
|
+
return format(**attrs)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ArraySerializer(Serializer[list]):
|
|
53
|
+
@staticmethod
|
|
54
|
+
def write(buf: io.Writer, format: Format, value: list):
|
|
55
|
+
tlength, tvalue = t.get_args(format)
|
|
56
|
+
|
|
57
|
+
length = len(value)
|
|
58
|
+
serialize(buf, tlength, length)
|
|
59
|
+
for item in value:
|
|
60
|
+
serialize(buf, tvalue, item)
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def read(buf: io.Reader, format: Format):
|
|
64
|
+
tlength, tvalue = t.get_args(format)
|
|
65
|
+
|
|
66
|
+
length = read_uint(buf, tlength)
|
|
67
|
+
values = []
|
|
68
|
+
for _ in range(length):
|
|
69
|
+
value = deserialize(buf, tvalue)
|
|
70
|
+
values.append(value)
|
|
71
|
+
|
|
72
|
+
return values
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class StringSerializer(Serializer[str]):
|
|
76
|
+
@staticmethod
|
|
77
|
+
def write(buf: io.Writer, format: Format, value: str):
|
|
78
|
+
(tlength,) = t.get_args(format)
|
|
79
|
+
|
|
80
|
+
data = value.encode("utf-8")
|
|
81
|
+
length = len(data)
|
|
82
|
+
|
|
83
|
+
serialize(buf, tlength, length)
|
|
84
|
+
buf.write(data)
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def read(buf: io.Reader, format: Format):
|
|
88
|
+
(tlength,) = t.get_args(format)
|
|
89
|
+
|
|
90
|
+
length = deserialize(buf, tlength)
|
|
91
|
+
data = buf.read(length)
|
|
92
|
+
return data.decode("utf-8")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class BlobSerializer(Serializer[bytes]):
|
|
96
|
+
@staticmethod
|
|
97
|
+
def write(buf: io.Writer, format: Format, value: bytes):
|
|
98
|
+
(tlength,) = t.get_args(format)
|
|
99
|
+
|
|
100
|
+
length = len(value)
|
|
101
|
+
serialize(buf, tlength, length)
|
|
102
|
+
buf.write(value)
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def read(buf: io.Reader, format: Format):
|
|
106
|
+
(tlength,) = t.get_args(format)
|
|
107
|
+
|
|
108
|
+
length = deserialize(buf, tlength)
|
|
109
|
+
data = buf.read(length)
|
|
110
|
+
return data
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def create_struct_serializer[T](sformat: str, length: int) -> type[Serializer[T]]:
|
|
114
|
+
class StructSerializer(Serializer[T]):
|
|
115
|
+
@staticmethod
|
|
116
|
+
def write(buf: io.Writer, _: type, value: int):
|
|
117
|
+
return buf.write(struct.pack(sformat, value))
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def read(buf: io.Reader, _: type):
|
|
121
|
+
return struct.unpack(sformat, buf.read(length))[0]
|
|
122
|
+
|
|
123
|
+
return StructSerializer
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _get_serializer(format: Format) -> type[Serializer]:
|
|
127
|
+
DIRECT_PRIMATIVES = {
|
|
128
|
+
uint64: create_struct_serializer(">Q", 8),
|
|
129
|
+
uint32: create_struct_serializer(">I", 4),
|
|
130
|
+
uint16: create_struct_serializer(">H", 2),
|
|
131
|
+
uint8: create_struct_serializer("B", 1),
|
|
132
|
+
int64: create_struct_serializer(">q", 8),
|
|
133
|
+
int32: create_struct_serializer(">i", 4),
|
|
134
|
+
int16: create_struct_serializer(">h", 2),
|
|
135
|
+
int8: create_struct_serializer("b", 1),
|
|
136
|
+
float64: create_struct_serializer(">d", 8),
|
|
137
|
+
float32: create_struct_serializer(">f", 4),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if format in DIRECT_PRIMATIVES:
|
|
141
|
+
return DIRECT_PRIMATIVES[format]
|
|
142
|
+
|
|
143
|
+
GENERICS_PRIMATIVES = {
|
|
144
|
+
array: ArraySerializer,
|
|
145
|
+
string: StringSerializer,
|
|
146
|
+
blob: BlobSerializer,
|
|
147
|
+
}
|
|
148
|
+
origin = t.get_origin(format)
|
|
149
|
+
if origin in GENERICS_PRIMATIVES:
|
|
150
|
+
return GENERICS_PRIMATIVES[origin]
|
|
151
|
+
|
|
152
|
+
if isinstance(format, type) and issubclass(format, SerialiableObject):
|
|
153
|
+
return SerialiableObjectSerializer
|
|
154
|
+
|
|
155
|
+
raise NotImplementedError(f"No serializer for {format}")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def read_uint(buf: io.Reader, format: Format) -> int:
|
|
159
|
+
value = deserialize(buf, format)
|
|
160
|
+
assert isinstance(value, int), "uint not value"
|
|
161
|
+
return value
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def serialize(buf: io.Writer, format: Format, value: t.Any):
|
|
165
|
+
return _get_serializer(format).write(buf, format, value)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def deserialize(buf: io.Reader, format: Format) -> t.Any:
|
|
169
|
+
return _get_serializer(format).read(buf, format)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
import io
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import annotationlib
|
|
5
|
+
import struct
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
type uint8 = int
|
|
9
|
+
type uint16 = int
|
|
10
|
+
type uint32 = int
|
|
11
|
+
type uint64 = int
|
|
12
|
+
|
|
13
|
+
type int8 = int
|
|
14
|
+
type int16 = int
|
|
15
|
+
type int32 = int
|
|
16
|
+
type int64 = int
|
|
17
|
+
|
|
18
|
+
type float32 = float
|
|
19
|
+
type float64 = float
|
|
20
|
+
|
|
21
|
+
type uint = uint8 | uint16 | uint32 | uint64
|
|
22
|
+
type sint = int8 | int16 | int32 | int64
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
type blob[Length: uint] = bytes
|
|
26
|
+
"""
|
|
27
|
+
blob[Length: uint]
|
|
28
|
+
|
|
29
|
+
> Length: The integer type used to store the length
|
|
30
|
+
"""
|
|
31
|
+
type string[Length: uint] = str
|
|
32
|
+
"""
|
|
33
|
+
string[Length: uint]
|
|
34
|
+
|
|
35
|
+
> Length: The integer type used to store the length
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
type array[Length: uint, Value: Serializable] = list
|
|
39
|
+
"""
|
|
40
|
+
array[Length: uint, Value: Serializable]
|
|
41
|
+
|
|
42
|
+
> Length: The integer type used to store the length
|
|
43
|
+
|
|
44
|
+
> Value: The value to store in the array
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _SerialiableObjectMeta(type):
|
|
49
|
+
def __new__(cls, name, bases, dct):
|
|
50
|
+
new_class = super().__new__(cls, name, bases, dct)
|
|
51
|
+
return dataclass(new_class) # type: ignore
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@t.dataclass_transform()
|
|
55
|
+
class SerialiableObject(metaclass=_SerialiableObjectMeta):
|
|
56
|
+
@classmethod
|
|
57
|
+
def read(cls, buf: io.Reader) -> t.Self:
|
|
58
|
+
from .serialise import deserialize
|
|
59
|
+
|
|
60
|
+
return deserialize(buf, type(cls))
|
|
61
|
+
|
|
62
|
+
def write(self, buf: io.Writer):
|
|
63
|
+
from .serialise import serialize
|
|
64
|
+
|
|
65
|
+
serialize(buf, type(self), self)
|
|
66
|
+
|
|
67
|
+
def from_bytes(self, data: bytes) -> t.Self:
|
|
68
|
+
buf = io.BytesIO(data)
|
|
69
|
+
return self.read(buf)
|
|
70
|
+
|
|
71
|
+
def to_bytes(self) -> bytes:
|
|
72
|
+
buf = io.BytesIO()
|
|
73
|
+
self.write(buf)
|
|
74
|
+
buf.seek(0)
|
|
75
|
+
return buf.read()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
type Primative = string | array | uint | int | float
|
|
79
|
+
type Serializable = SerialiableObject | Primative
|
|
80
|
+
type Format = type | t.TypeAliasType
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
import io
|
|
3
|
+
from .types import (
|
|
4
|
+
Format,
|
|
5
|
+
SerialiableObject,
|
|
6
|
+
uint8,
|
|
7
|
+
uint16,
|
|
8
|
+
uint32,
|
|
9
|
+
uint64,
|
|
10
|
+
int8,
|
|
11
|
+
int16,
|
|
12
|
+
int32,
|
|
13
|
+
int64,
|
|
14
|
+
float32,
|
|
15
|
+
float64,
|
|
16
|
+
blob,
|
|
17
|
+
string,
|
|
18
|
+
)
|
|
19
|
+
from .serialise import serialize, deserialize
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def serialize_to_bytes(format: Format, value: t.Any) -> bytes:
|
|
23
|
+
buf = io.BytesIO()
|
|
24
|
+
serialize(buf, format, value)
|
|
25
|
+
buf.seek(0)
|
|
26
|
+
return buf.read()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def deserialize_from_bytes(format: Format, data: bytes) -> t.Any:
|
|
30
|
+
buf = io.BytesIO(data)
|
|
31
|
+
return deserialize(buf, format)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class StructifyReader:
|
|
35
|
+
reader: io.Reader
|
|
36
|
+
|
|
37
|
+
def __init__(self, reader: io.Reader) -> None:
|
|
38
|
+
self.reader = reader
|
|
39
|
+
|
|
40
|
+
@t.overload
|
|
41
|
+
def read[T: SerialiableObject](self, format: type[T]) -> T: ...
|
|
42
|
+
|
|
43
|
+
@t.overload
|
|
44
|
+
def read(self, format: Format) -> t.Any: ...
|
|
45
|
+
|
|
46
|
+
def read(self, format: Format) -> t.Any:
|
|
47
|
+
return deserialize(self.reader, format)
|
|
48
|
+
|
|
49
|
+
def read_uint8(self) -> int:
|
|
50
|
+
return self.read(uint8)
|
|
51
|
+
|
|
52
|
+
def read_uint16(self) -> int:
|
|
53
|
+
return self.read(uint16)
|
|
54
|
+
|
|
55
|
+
def read_uint32(self) -> int:
|
|
56
|
+
return self.read(uint32)
|
|
57
|
+
|
|
58
|
+
def read_uint64(self) -> int:
|
|
59
|
+
return self.read(uint64)
|
|
60
|
+
|
|
61
|
+
def read_int8(self) -> int:
|
|
62
|
+
return self.read(int8)
|
|
63
|
+
|
|
64
|
+
def read_int16(self) -> int:
|
|
65
|
+
return self.read(int16)
|
|
66
|
+
|
|
67
|
+
def read_int32(self) -> int:
|
|
68
|
+
return self.read(int32)
|
|
69
|
+
|
|
70
|
+
def read_int64(self) -> int:
|
|
71
|
+
return self.read(int64)
|
|
72
|
+
|
|
73
|
+
def read_float32(self) -> float:
|
|
74
|
+
return self.read(float32)
|
|
75
|
+
|
|
76
|
+
def read_float64(self) -> float:
|
|
77
|
+
return self.read(float64)
|
|
78
|
+
|
|
79
|
+
def read_blob(self) -> bytes:
|
|
80
|
+
return self.read(blob)
|
|
81
|
+
|
|
82
|
+
def read_string(self) -> str:
|
|
83
|
+
return self.read(string)
|
|
84
|
+
|
|
85
|
+
class StructifyWriter:
|
|
86
|
+
buf: io.Writer
|
|
87
|
+
|
|
88
|
+
def __init__(self, buf: io.Writer) -> None:
|
|
89
|
+
self.buf = buf
|
|
90
|
+
|
|
91
|
+
def write(self, format: Format, value: t.Any) -> t.Any:
|
|
92
|
+
return serialize(self.buf, format, value)
|
|
93
|
+
|
|
94
|
+
def write_uint8(self, value: int):
|
|
95
|
+
return self.write(uint8, value)
|
|
96
|
+
|
|
97
|
+
def write_uint16(self, value: int):
|
|
98
|
+
return self.write(uint16, value)
|
|
99
|
+
|
|
100
|
+
def write_uint32(self, value: int):
|
|
101
|
+
return self.write(uint32, value)
|
|
102
|
+
|
|
103
|
+
def write_uint64(self, value: int):
|
|
104
|
+
return self.write(uint64, value)
|
|
105
|
+
|
|
106
|
+
def write_int8(self, value: int):
|
|
107
|
+
return self.write(int8, value)
|
|
108
|
+
|
|
109
|
+
def write_int16(self, value: int):
|
|
110
|
+
return self.write(int16, value)
|
|
111
|
+
|
|
112
|
+
def write_int32(self, value: int):
|
|
113
|
+
return self.write(int32, value)
|
|
114
|
+
|
|
115
|
+
def write_int64(self, value: int):
|
|
116
|
+
return self.write(int64, value)
|
|
117
|
+
|
|
118
|
+
def write_float32(self, value: float):
|
|
119
|
+
return self.write(float32, value)
|
|
120
|
+
|
|
121
|
+
def write_float64(self, value: float):
|
|
122
|
+
return self.write(float64, value)
|
|
123
|
+
|
|
124
|
+
def write_blob(self, value: bytes):
|
|
125
|
+
return self.write(string, value)
|
|
126
|
+
|
|
127
|
+
def write_string(self, value: str):
|
|
128
|
+
return self.write(string, value)
|