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 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,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: structo
3
+ Version: 0.0.1
4
+ Summary: Structify
5
+ Author-email: Ben Brady <benbradybusiness@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Project-URL: Home, https://nnilky.site
@@ -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)