structo 0.0.10__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.
- structo/__init__.py +44 -0
- structo/interfaces.py +46 -0
- structo/objects/__init__.py +2 -0
- structo/objects/packed.py +50 -0
- structo/objects/serializable_object.py +37 -0
- structo/serialise.py +46 -0
- structo/serializers/__init__.py +37 -0
- structo/serializers/array.py +34 -0
- structo/serializers/blob.py +20 -0
- structo/serializers/buffer.py +21 -0
- structo/serializers/list.py +29 -0
- structo/serializers/literal.py +30 -0
- structo/serializers/numbers.py +100 -0
- structo/serializers/object.py +52 -0
- structo/serializers/optional.py +39 -0
- structo/serializers/packed.py +57 -0
- structo/serializers/string.py +27 -0
- structo-0.0.10.dist-info/METADATA +17 -0
- structo-0.0.10.dist-info/RECORD +21 -0
- structo-0.0.10.dist-info/WHEEL +4 -0
- structo-0.0.10.dist-info/licenses/LICENSE +21 -0
structo/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Structify
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.0.10"
|
|
6
|
+
|
|
7
|
+
from .interfaces import Serializer, Serializable
|
|
8
|
+
from .serialise import get_serializer
|
|
9
|
+
from .objects import SerializableObject, PackedInt, PackedInts
|
|
10
|
+
from .serializers import (
|
|
11
|
+
uint64_BE,
|
|
12
|
+
uint64_LE,
|
|
13
|
+
uint64,
|
|
14
|
+
uint32_BE,
|
|
15
|
+
uint32_LE,
|
|
16
|
+
uint32,
|
|
17
|
+
uint16_BE,
|
|
18
|
+
uint16_LE,
|
|
19
|
+
uint16,
|
|
20
|
+
uint8,
|
|
21
|
+
int64_BE,
|
|
22
|
+
int64_LE,
|
|
23
|
+
int64,
|
|
24
|
+
int32_BE,
|
|
25
|
+
int32_LE,
|
|
26
|
+
int32,
|
|
27
|
+
int16_BE,
|
|
28
|
+
int16_LE,
|
|
29
|
+
int16,
|
|
30
|
+
int8,
|
|
31
|
+
float64_BE,
|
|
32
|
+
float64_LE,
|
|
33
|
+
float64,
|
|
34
|
+
float32_BE,
|
|
35
|
+
float32_LE,
|
|
36
|
+
float32,
|
|
37
|
+
Array,
|
|
38
|
+
Buffer,
|
|
39
|
+
List,
|
|
40
|
+
String,
|
|
41
|
+
Blob,
|
|
42
|
+
Literal,
|
|
43
|
+
Optional,
|
|
44
|
+
)
|
structo/interfaces.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import typing as t
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Serializable:
|
|
6
|
+
@classmethod
|
|
7
|
+
def serializer(cls) -> Serializer[t.Self]: ...
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
def sizeof(cls) -> int | None:
|
|
11
|
+
return cls.serializer().sizeof()
|
|
12
|
+
|
|
13
|
+
def write(self, f: t.IO[bytes]):
|
|
14
|
+
return self.serializer().write(f, self)
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def read(cls, f: t.IO[bytes]) -> t.Self:
|
|
18
|
+
return cls.serializer().read(f)
|
|
19
|
+
|
|
20
|
+
def to_bytes(self) -> bytes:
|
|
21
|
+
return self.serializer().to_bytes(self)
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_bytes(cls, data: bytes) -> t.Self:
|
|
25
|
+
return cls.serializer().from_bytes(data)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Serializer[T]:
|
|
29
|
+
def sizeof(self) -> int | None:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
def write(self, f: t.IO[bytes], value: T):
|
|
33
|
+
raise NotImplementedError(f"{type(self).__name__} can't be serialized")
|
|
34
|
+
|
|
35
|
+
def read(self, f: t.IO[bytes]) -> T:
|
|
36
|
+
raise NotImplementedError(f"{type(self).__name__} can't be deserialized")
|
|
37
|
+
|
|
38
|
+
def to_bytes(self, value: T) -> bytes:
|
|
39
|
+
buf = io.BytesIO()
|
|
40
|
+
self.write(buf, value)
|
|
41
|
+
buf.seek(0)
|
|
42
|
+
return buf.getvalue()
|
|
43
|
+
|
|
44
|
+
def from_bytes(self, data: bytes) -> T:
|
|
45
|
+
buf = io.BytesIO(data)
|
|
46
|
+
return self.read(buf)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from ..interfaces import Serializable, Serializer
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
import annotationlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PackedInt:
|
|
9
|
+
bits: int
|
|
10
|
+
|
|
11
|
+
def __init__(self, *, bits: int) -> None:
|
|
12
|
+
self.bits = bits
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PackedIntsMeta(type):
|
|
16
|
+
def __new__(cls, name, bases, dct):
|
|
17
|
+
new_class = t.cast(type, super().__new__(cls, name, bases, dct))
|
|
18
|
+
if name == "PackedInts":
|
|
19
|
+
return new_class
|
|
20
|
+
|
|
21
|
+
annotate = annotationlib.get_annotate_from_class_namespace(dct)
|
|
22
|
+
assert annotate, f"Annotations not found for {name}"
|
|
23
|
+
|
|
24
|
+
annotations = annotate(annotationlib.Format.VALUE_WITH_FAKE_GLOBALS)
|
|
25
|
+
|
|
26
|
+
bits: dict[str, int] = {}
|
|
27
|
+
for key, value in annotations.items():
|
|
28
|
+
err_msg = f"expected Annotated[int, PackedInt(...)] on {name}.{key}"
|
|
29
|
+
assert t.get_origin(value) is t.Annotated, err_msg
|
|
30
|
+
|
|
31
|
+
ints = [arg for arg in t.get_args(value) if isinstance(arg, PackedInt)]
|
|
32
|
+
assert len(ints) <= 1, err_msg
|
|
33
|
+
assert len(ints) == 1, err_msg
|
|
34
|
+
bits[key] = ints[0].bits
|
|
35
|
+
|
|
36
|
+
obj = t.cast(PackedInts, dataclass(new_class))
|
|
37
|
+
obj._bits = bits
|
|
38
|
+
return obj
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# This is very messed up since we
|
|
42
|
+
@t.dataclass_transform()
|
|
43
|
+
class PackedInts(Serializable, metaclass=PackedIntsMeta):
|
|
44
|
+
_bits: dict[str, int]
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def serializer(cls) -> Serializer[t.Self]:
|
|
48
|
+
from ..serializers import PackedIntSerializer
|
|
49
|
+
|
|
50
|
+
return PackedIntSerializer(cls)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import annotationlib
|
|
2
|
+
import typing as t
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from ..interfaces import Serializable, Serializer
|
|
6
|
+
from ..serialise import get_serializer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SerializableObjectMeta(type):
|
|
10
|
+
def __new__(cls, name, bases, dct):
|
|
11
|
+
new_class = t.cast(type, super().__new__(cls, name, bases, dct))
|
|
12
|
+
if name == "SerializableObject":
|
|
13
|
+
return new_class
|
|
14
|
+
|
|
15
|
+
annotate = annotationlib.get_annotate_from_class_namespace(dct)
|
|
16
|
+
|
|
17
|
+
assert annotate, "No annotations method available"
|
|
18
|
+
annotations = annotate(annotationlib.Format.VALUE_WITH_FAKE_GLOBALS)
|
|
19
|
+
|
|
20
|
+
fields: dict[str, Serializer] = {}
|
|
21
|
+
for key, annotation in annotations.items():
|
|
22
|
+
try:
|
|
23
|
+
fields[key] = get_serializer(annotation)
|
|
24
|
+
except Exception as e:
|
|
25
|
+
raise ValueError(f"Invalid attribute defintion for {name}.{key}") from e
|
|
26
|
+
|
|
27
|
+
return t.cast(SerializableObject, dataclass(new_class))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# This is very messed up since we
|
|
31
|
+
@t.dataclass_transform()
|
|
32
|
+
class SerializableObject(Serializable, metaclass=SerializableObjectMeta):
|
|
33
|
+
@classmethod
|
|
34
|
+
def serializer(cls) -> Serializer[t.Self]:
|
|
35
|
+
from ..serializers import ObjectSerializer
|
|
36
|
+
|
|
37
|
+
return ObjectSerializer(cls)
|
structo/serialise.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from .interfaces import Serializer, Serializable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def to_serializer[T](format: type[T] | Serializer[T]) -> Serializer[T]:
|
|
6
|
+
if isinstance(format, Serializer):
|
|
7
|
+
return format
|
|
8
|
+
|
|
9
|
+
if isinstance(format, type) and issubclass(format, Serializable):
|
|
10
|
+
return format.serializer()
|
|
11
|
+
|
|
12
|
+
raise AssertionError(f"Invalid value_type: {format}")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_serializer(format: type | Serializer) -> Serializer:
|
|
16
|
+
if t.get_origin(format) is t.Annotated:
|
|
17
|
+
args = t.get_args(format)
|
|
18
|
+
serializers = [arg for arg in args if isinstance(arg, Serializer)]
|
|
19
|
+
assert len(serializers) != 0, f"No serializers for {format} found"
|
|
20
|
+
assert len(serializers) == 1, f"More than one serializers for {format} found"
|
|
21
|
+
serializer = serializers[0]
|
|
22
|
+
|
|
23
|
+
return serializer
|
|
24
|
+
|
|
25
|
+
if isinstance(format, type) and issubclass(format, Serializable):
|
|
26
|
+
return format.serializer()
|
|
27
|
+
|
|
28
|
+
# Nicely formatted errors:
|
|
29
|
+
if format == int:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
f"No serializer for int, you need to use structo.int32, structo.int32 or similar instead"
|
|
32
|
+
)
|
|
33
|
+
if format == float:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"No serializer for float, you need to use structo.float32 or structo.float64 instead"
|
|
36
|
+
)
|
|
37
|
+
if format == bytes:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"No serializer for bytes, you need to use structo.Buffer or structo.Blob instead"
|
|
40
|
+
)
|
|
41
|
+
if format == list:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
f"No serializer for list, you need to use structo.List or structo.Array instead"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
raise NotImplementedError(f"No serializer found for {format}")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from .numbers import (
|
|
2
|
+
uint64_BE,
|
|
3
|
+
uint64_LE,
|
|
4
|
+
uint64,
|
|
5
|
+
uint32_BE,
|
|
6
|
+
uint32_LE,
|
|
7
|
+
uint32,
|
|
8
|
+
uint16_BE,
|
|
9
|
+
uint16_LE,
|
|
10
|
+
uint16,
|
|
11
|
+
uint8,
|
|
12
|
+
int64_BE,
|
|
13
|
+
int64_LE,
|
|
14
|
+
int64,
|
|
15
|
+
int32_BE,
|
|
16
|
+
int32_LE,
|
|
17
|
+
int32,
|
|
18
|
+
int16_BE,
|
|
19
|
+
int16_LE,
|
|
20
|
+
int16,
|
|
21
|
+
int8,
|
|
22
|
+
float64_BE,
|
|
23
|
+
float64_LE,
|
|
24
|
+
float64,
|
|
25
|
+
float32_BE,
|
|
26
|
+
float32_LE,
|
|
27
|
+
float32,
|
|
28
|
+
)
|
|
29
|
+
from .buffer import Buffer
|
|
30
|
+
from .array import Array
|
|
31
|
+
from .blob import Blob
|
|
32
|
+
from .list import List
|
|
33
|
+
from .string import String
|
|
34
|
+
from .literal import Literal
|
|
35
|
+
from .optional import Optional
|
|
36
|
+
from .object import ObjectSerializer
|
|
37
|
+
from .packed import PackedIntSerializer
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from ..serialise import to_serializer
|
|
2
|
+
from ..interfaces import Serializer
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Array[T](Serializer[list[T]]):
|
|
6
|
+
_length: int
|
|
7
|
+
_value_type: Serializer[T]
|
|
8
|
+
|
|
9
|
+
def __init__(self, value: Serializer[T] | type[T], length: int) -> None:
|
|
10
|
+
assert length > 0, "Array must be longer than 0"
|
|
11
|
+
self._length = length
|
|
12
|
+
self._value_type = to_serializer(value)
|
|
13
|
+
|
|
14
|
+
def write(self, f, value):
|
|
15
|
+
assert (
|
|
16
|
+
len(value) == self._length
|
|
17
|
+
), f"expected array with {self._length} length, receieved {len(value)}"
|
|
18
|
+
|
|
19
|
+
for item in value:
|
|
20
|
+
self._value_type.write(f, item)
|
|
21
|
+
|
|
22
|
+
def read(self, f):
|
|
23
|
+
items = []
|
|
24
|
+
for _ in range(self._length):
|
|
25
|
+
items.append(self._value_type.read(f))
|
|
26
|
+
|
|
27
|
+
return items
|
|
28
|
+
|
|
29
|
+
def sizeof(self):
|
|
30
|
+
element_length = self._value_type.sizeof()
|
|
31
|
+
if element_length is None:
|
|
32
|
+
return None
|
|
33
|
+
else:
|
|
34
|
+
return element_length * self._length
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .numbers import uint32_LE
|
|
2
|
+
from ..interfaces import Serializer
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Blob(Serializer[bytes]):
|
|
6
|
+
"A set of arbitrary bytes, prefixed with it's length"
|
|
7
|
+
|
|
8
|
+
_length_type: Serializer[int]
|
|
9
|
+
|
|
10
|
+
def __init__(self, length_type: Serializer[int] | None = None) -> None:
|
|
11
|
+
self._length_type = length_type or uint32_LE
|
|
12
|
+
|
|
13
|
+
def write(self, f, value):
|
|
14
|
+
self._length_type.write(f, len(value))
|
|
15
|
+
f.write(value)
|
|
16
|
+
|
|
17
|
+
def read(self, f):
|
|
18
|
+
length = self._length_type.read(f)
|
|
19
|
+
data = f.read(length)
|
|
20
|
+
return data
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from ..interfaces import Serializer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Buffer(Serializer[bytes]):
|
|
5
|
+
_length: int
|
|
6
|
+
|
|
7
|
+
def __init__(self, length: int) -> None:
|
|
8
|
+
self._length = length
|
|
9
|
+
|
|
10
|
+
def write(self, f, value):
|
|
11
|
+
assert (
|
|
12
|
+
len(value) == self._length
|
|
13
|
+
), f"expected data with length {self._length}, received {len(value)}"
|
|
14
|
+
f.write(value)
|
|
15
|
+
|
|
16
|
+
def read(self, f):
|
|
17
|
+
data = f.read(self._length)
|
|
18
|
+
return data
|
|
19
|
+
|
|
20
|
+
def sizeof(self):
|
|
21
|
+
return self._length
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .numbers import uint32_LE
|
|
2
|
+
from ..serialise import to_serializer
|
|
3
|
+
from ..interfaces import Serializer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class List[T](Serializer[list[T]]):
|
|
7
|
+
"A list of items, prefixed with it's length"
|
|
8
|
+
|
|
9
|
+
_length_type: Serializer[int]
|
|
10
|
+
_value_type: Serializer
|
|
11
|
+
|
|
12
|
+
def __init__(self, value: Serializer[T] | type[T], length: Serializer[int] | None = None) -> None:
|
|
13
|
+
self._length_type = length or uint32_LE
|
|
14
|
+
self._value_type = to_serializer(value)
|
|
15
|
+
|
|
16
|
+
def write(self, f, value):
|
|
17
|
+
length = len(value)
|
|
18
|
+
self._length_type.write(f, length)
|
|
19
|
+
for item in value:
|
|
20
|
+
self._value_type.write(f, item)
|
|
21
|
+
|
|
22
|
+
def read(self, f):
|
|
23
|
+
length = self._length_type.read(f)
|
|
24
|
+
values = []
|
|
25
|
+
for _ in range(length):
|
|
26
|
+
value = self._value_type.read(f)
|
|
27
|
+
values.append(value)
|
|
28
|
+
|
|
29
|
+
return values
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from ..interfaces import Serializer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Literal(Serializer[bytes]):
|
|
5
|
+
_values: list[bytes]
|
|
6
|
+
_length: int
|
|
7
|
+
|
|
8
|
+
def __init__(self, *values: bytes) -> None:
|
|
9
|
+
assert len(values) > 0, "foo"
|
|
10
|
+
length = len(values[0])
|
|
11
|
+
|
|
12
|
+
for value in values:
|
|
13
|
+
assert (
|
|
14
|
+
len(value) == length
|
|
15
|
+
), f"All values in structo.Literal have to be the same length, expected {value} to be length {length}"
|
|
16
|
+
|
|
17
|
+
self._length = length
|
|
18
|
+
self._values = list(values)
|
|
19
|
+
|
|
20
|
+
def write(self, f, value):
|
|
21
|
+
assert value in self._values, f"{value} not in {b", ".join(self._values)}"
|
|
22
|
+
f.write(value)
|
|
23
|
+
|
|
24
|
+
def read(self, f):
|
|
25
|
+
value = f.read(self._length)
|
|
26
|
+
assert value in self._values, f"{value} not in {b", ".join(self._values)}"
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
def sizeof(self):
|
|
30
|
+
return self._length
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import struct
|
|
3
|
+
from ..interfaces import Serializer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def struct_serializer(sformat: str, bytes: int) -> Serializer:
|
|
7
|
+
class StructSerializer(Serializer):
|
|
8
|
+
def sizeof(self):
|
|
9
|
+
return bytes
|
|
10
|
+
|
|
11
|
+
def write(self, f, value):
|
|
12
|
+
data = struct.pack(sformat, value)
|
|
13
|
+
return f.write(data)
|
|
14
|
+
|
|
15
|
+
def read(self, f):
|
|
16
|
+
data = f.read(bytes)
|
|
17
|
+
return struct.unpack(sformat, data)[0]
|
|
18
|
+
|
|
19
|
+
return StructSerializer()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
uint64_BE: Serializer[int] = struct_serializer(">Q", bytes=8)
|
|
23
|
+
"**unsigned 64bit integer - big endian**"
|
|
24
|
+
|
|
25
|
+
uint64_LE: Serializer[int] = struct_serializer("<Q", bytes=8)
|
|
26
|
+
"**unsigned 64bit integer - little endian**"
|
|
27
|
+
|
|
28
|
+
uint64 = uint64_BE
|
|
29
|
+
"**unsigned 64bit integer - big endian**"
|
|
30
|
+
|
|
31
|
+
uint32_BE: Serializer[int] = struct_serializer(">I", bytes=4)
|
|
32
|
+
"**unsigned 32bit integer - big endian**"
|
|
33
|
+
|
|
34
|
+
uint32_LE: Serializer[int] = struct_serializer("<I", bytes=4)
|
|
35
|
+
"**unsigned 32bit integer - little endian**"
|
|
36
|
+
|
|
37
|
+
uint32 = uint32_BE
|
|
38
|
+
"**unsigned 32bit integer - big endian**"
|
|
39
|
+
|
|
40
|
+
uint16_BE: Serializer[int] = struct_serializer(">H", bytes=2)
|
|
41
|
+
"**unsigned 16bit integer - big endian**"
|
|
42
|
+
|
|
43
|
+
uint16_LE: Serializer[int] = struct_serializer("<H", bytes=2)
|
|
44
|
+
"**unsigned 16bit integer - little endian**"
|
|
45
|
+
|
|
46
|
+
uint16 = uint16_BE
|
|
47
|
+
"**unsigned 16bit integer - big endian**"
|
|
48
|
+
|
|
49
|
+
uint8: Serializer[int] = struct_serializer("B", bytes=1)
|
|
50
|
+
"**unsigned 8bit integer**"
|
|
51
|
+
|
|
52
|
+
int64_BE: Serializer[int] = struct_serializer(">q", bytes=8)
|
|
53
|
+
"**signed 64bit integer - big endian**"
|
|
54
|
+
|
|
55
|
+
int64_LE: Serializer[int] = struct_serializer("<q", bytes=8)
|
|
56
|
+
"**signed 64bit integer - little endian**"
|
|
57
|
+
|
|
58
|
+
int64 = int64_BE
|
|
59
|
+
"**signed 64bit integer - big endian**"
|
|
60
|
+
|
|
61
|
+
int32_BE: Serializer[int] = struct_serializer(">i", bytes=4)
|
|
62
|
+
"**signed 32bit integer - big endian**"
|
|
63
|
+
|
|
64
|
+
int32_LE: Serializer[int] = struct_serializer("<i", bytes=4)
|
|
65
|
+
"**signed 32bit integer - little endian**"
|
|
66
|
+
|
|
67
|
+
int32 = int32_BE
|
|
68
|
+
"**signed 32bit integer - big endian**"
|
|
69
|
+
|
|
70
|
+
int16_BE: Serializer[int] = struct_serializer(">h", bytes=2)
|
|
71
|
+
"**signed 16bit integer - big endian**"
|
|
72
|
+
|
|
73
|
+
int16_LE: Serializer[int] = struct_serializer("<h", bytes=2)
|
|
74
|
+
"**signed 16bit integer - little endian**"
|
|
75
|
+
|
|
76
|
+
int16 = int16_BE
|
|
77
|
+
"**signed 16bit integer - big endian**"
|
|
78
|
+
|
|
79
|
+
int8: Serializer[int] = struct_serializer("b", bytes=1)
|
|
80
|
+
"**signed 8bit integer**"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
float64_BE: Serializer[float] = struct_serializer(">d", bytes=8)
|
|
84
|
+
"**64bit float - big endian**"
|
|
85
|
+
|
|
86
|
+
float64_LE: Serializer[float] = struct_serializer("<d", bytes=8)
|
|
87
|
+
"**64bit float - little endian**"
|
|
88
|
+
|
|
89
|
+
float64 = float64_BE
|
|
90
|
+
"**64bit float - big endian**"
|
|
91
|
+
|
|
92
|
+
float32_BE: Serializer[float] = struct_serializer(">f", bytes=4)
|
|
93
|
+
"**32bit float - big endian**"
|
|
94
|
+
|
|
95
|
+
float32_LE: Serializer[float] = struct_serializer("<f", bytes=4)
|
|
96
|
+
"**32bit float - little endian**"
|
|
97
|
+
|
|
98
|
+
float32 = float32_BE
|
|
99
|
+
"**32bit float - big endian**"
|
|
100
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
import io
|
|
3
|
+
import annotationlib
|
|
4
|
+
|
|
5
|
+
from ..interfaces import Serializer
|
|
6
|
+
from ..objects import SerializableObject
|
|
7
|
+
from ..serialise import get_serializer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ObjectSerializer[T: SerializableObject](Serializer[T]):
|
|
11
|
+
_annotations: dict[str, Serializer] = {}
|
|
12
|
+
_type: type
|
|
13
|
+
|
|
14
|
+
def __init__(self, type: type[T]) -> None:
|
|
15
|
+
annotations: dict[str, Serializer] = {}
|
|
16
|
+
self._type = type
|
|
17
|
+
|
|
18
|
+
attrs = annotationlib.get_annotations(type)
|
|
19
|
+
for key, value in attrs.items():
|
|
20
|
+
try:
|
|
21
|
+
annotations[key] = get_serializer(value)
|
|
22
|
+
except Exception as e:
|
|
23
|
+
raise ValueError(
|
|
24
|
+
f"Invalid attribute defintion for {type.__name__}.{key}"
|
|
25
|
+
) from e
|
|
26
|
+
|
|
27
|
+
self._annotations = annotations
|
|
28
|
+
|
|
29
|
+
def sizeof(self) -> int | None:
|
|
30
|
+
total_size = 0
|
|
31
|
+
for serializer in self._annotations.values():
|
|
32
|
+
field_size = serializer.sizeof()
|
|
33
|
+
if field_size is None:
|
|
34
|
+
return None
|
|
35
|
+
else:
|
|
36
|
+
total_size += field_size
|
|
37
|
+
|
|
38
|
+
return total_size
|
|
39
|
+
|
|
40
|
+
def write(self, f, value):
|
|
41
|
+
for field_key, field_format in self._annotations.items():
|
|
42
|
+
field_value = getattr(value, field_key)
|
|
43
|
+
field_format.write(f, field_value)
|
|
44
|
+
|
|
45
|
+
def read(self, f):
|
|
46
|
+
attrs = {}
|
|
47
|
+
|
|
48
|
+
for field_key, field_format in self._annotations.items():
|
|
49
|
+
field_value = field_format.read(f)
|
|
50
|
+
attrs[field_key] = field_value
|
|
51
|
+
|
|
52
|
+
return self._type(**attrs)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from ..interfaces import Serializer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
NONE_TYPE_BYTE = b""
|
|
5
|
+
VALUE_TYPE_BYTE = b""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Optional[T](Serializer[T | None]):
|
|
9
|
+
_value_type: Serializer[T]
|
|
10
|
+
|
|
11
|
+
def __init__(self, value: Serializer[T]) -> None:
|
|
12
|
+
self._value_type = value
|
|
13
|
+
|
|
14
|
+
def write(self, f, value):
|
|
15
|
+
if value is None:
|
|
16
|
+
f.write(NONE_TYPE_BYTE)
|
|
17
|
+
else:
|
|
18
|
+
f.write(VALUE_TYPE_BYTE)
|
|
19
|
+
self._value_type.write(f, value)
|
|
20
|
+
|
|
21
|
+
def read(self, f):
|
|
22
|
+
type_byte = f.read(1)
|
|
23
|
+
|
|
24
|
+
assert type_byte in (
|
|
25
|
+
NONE_TYPE_BYTE,
|
|
26
|
+
VALUE_TYPE_BYTE,
|
|
27
|
+
), f"Unexpected type byte {type_byte}"
|
|
28
|
+
|
|
29
|
+
if type_byte == NONE_TYPE_BYTE:
|
|
30
|
+
return None
|
|
31
|
+
else:
|
|
32
|
+
return self._value_type.read(f)
|
|
33
|
+
|
|
34
|
+
def sizeof(self):
|
|
35
|
+
value_length = self._value_type.sizeof()
|
|
36
|
+
if value_length is None:
|
|
37
|
+
return None
|
|
38
|
+
else:
|
|
39
|
+
return value_length + 1
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from math import ceil
|
|
2
|
+
|
|
3
|
+
from ..objects import PackedInts
|
|
4
|
+
from ..interfaces import Serializer
|
|
5
|
+
|
|
6
|
+
import typing as t
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PackedIntSerializer[T: PackedInts](Serializer[T]):
|
|
10
|
+
_size: int
|
|
11
|
+
_bits: dict[str, int]
|
|
12
|
+
_cls: type[type[T]]
|
|
13
|
+
|
|
14
|
+
def __init__(self, cls: type[T]) -> None:
|
|
15
|
+
self._cls = cls
|
|
16
|
+
self._bits = cls._bits
|
|
17
|
+
self._size = self.sizeof()
|
|
18
|
+
|
|
19
|
+
def write(self, f, value):
|
|
20
|
+
output = 0
|
|
21
|
+
offset = 0
|
|
22
|
+
for field_key, bits in self._bits.items():
|
|
23
|
+
field_value = getattr(value, field_key)
|
|
24
|
+
|
|
25
|
+
max_value = (2**bits) - 1
|
|
26
|
+
assert (
|
|
27
|
+
field_value <= max_value
|
|
28
|
+
), f"{self._cls.__name__}{field_key} exceed max value, {field_value} > {max_value}"
|
|
29
|
+
output += field_value << offset
|
|
30
|
+
offset += bits
|
|
31
|
+
|
|
32
|
+
data = bytearray(self._size)
|
|
33
|
+
for x in range(self._size):
|
|
34
|
+
byte = output & 255
|
|
35
|
+
output >>= 8
|
|
36
|
+
data[x] = byte
|
|
37
|
+
|
|
38
|
+
f.write(data)
|
|
39
|
+
|
|
40
|
+
def read(self, f):
|
|
41
|
+
data = f.read(self._size)
|
|
42
|
+
integer = int.from_bytes(data, "little")
|
|
43
|
+
attrs: dict[str, t.Any] = {}
|
|
44
|
+
|
|
45
|
+
offset = 0
|
|
46
|
+
for field_key, bits in self._bits.items():
|
|
47
|
+
|
|
48
|
+
mask = (2**bits) - 1
|
|
49
|
+
value = (integer >> offset) & mask
|
|
50
|
+
|
|
51
|
+
offset += bits
|
|
52
|
+
attrs[field_key] = value
|
|
53
|
+
|
|
54
|
+
return self._cls(**attrs)
|
|
55
|
+
|
|
56
|
+
def sizeof(self) -> int:
|
|
57
|
+
return ceil(sum(self._bits.values()) / 8)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from .numbers import uint32_LE
|
|
3
|
+
from ..interfaces import Serializer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class String(Serializer[str]):
|
|
7
|
+
"A unicode string, prefixed with it's byte length"
|
|
8
|
+
|
|
9
|
+
_length_type: Serializer[int]
|
|
10
|
+
|
|
11
|
+
def __init__(self, length_type: Serializer[int] | None = None) -> None:
|
|
12
|
+
"""_summary_
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
length_type (Serializer[int], optional): Defaults to uint32_LE
|
|
16
|
+
"""
|
|
17
|
+
self._length_type = length_type or uint32_LE
|
|
18
|
+
|
|
19
|
+
def write(self, f, value):
|
|
20
|
+
data = value.encode("utf-8")
|
|
21
|
+
self._length_type.write(f, len(data))
|
|
22
|
+
f.write(data)
|
|
23
|
+
|
|
24
|
+
def read(self, f):
|
|
25
|
+
length = self._length_type.read(f)
|
|
26
|
+
data = f.read(length)
|
|
27
|
+
return data.decode("utf-8")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: structo
|
|
3
|
+
Version: 0.0.10
|
|
4
|
+
Summary: Structify
|
|
5
|
+
Author-email: Ben Brady <benbradybusiness@gmail.com>
|
|
6
|
+
Requires-Python: >=3.14
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: black>=26.1.0 ; extra == "dev"
|
|
10
|
+
Requires-Dist: pytest>=9.0.2 ; extra == "dev"
|
|
11
|
+
Requires-Dist: black ; extra == "docs"
|
|
12
|
+
Requires-Dist: mkdocs ; extra == "docs"
|
|
13
|
+
Requires-Dist: mkdocs-material ; extra == "docs"
|
|
14
|
+
Requires-Dist: mkdocstrings[crystal, python] ; extra == "docs"
|
|
15
|
+
Project-URL: Home, https://nnilky.site
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Provides-Extra: docs
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
structo/__init__.py,sha256=-QD1o5pnWc1lpWW6mvdUXVtoswjhJzJ97grj1uFr-ds,658
|
|
2
|
+
structo/interfaces.py,sha256=ecmIOKz4Z06UNlUdspXzIjjCLiPJfZrahsFhEXSFjt8,1188
|
|
3
|
+
structo/serialise.py,sha256=GsrxqQGanqVmaM6s1J3T8PSdit-AmrGhdmyGfEi-uwI,1640
|
|
4
|
+
structo/objects/__init__.py,sha256=0FMm6WXU3PERdq_kU30u6ofTYAfQGmw_JQ6z4mC5Ru4,94
|
|
5
|
+
structo/objects/packed.py,sha256=9Fr3xjme9wvuh51jsGlM9pPb58zVO3-o_PqdVKXU9VI,1486
|
|
6
|
+
structo/objects/serializable_object.py,sha256=MsuJU1nrWULG7vXenUgkxB71xu9XKU4KaEEk0waOU_U,1250
|
|
7
|
+
structo/serializers/__init__.py,sha256=wd5FxsqqqT8vOAKhvqLp3qL0PoOXhJhkrzc7f-8edrM,641
|
|
8
|
+
structo/serializers/array.py,sha256=uYSEqTa2LYTINxgT2VWhi8dK5KgJyg-5_8kZx9sZZeA,964
|
|
9
|
+
structo/serializers/blob.py,sha256=63wABDOVnbeUD2Tga2hO35eSu8quLEJZfFGvxhAciqY,542
|
|
10
|
+
structo/serializers/buffer.py,sha256=7mIvqWVB679Sf9DpngnvVD1xMq-XnuWkq57W3QI8ccM,484
|
|
11
|
+
structo/serializers/list.py,sha256=-1mUYWpN22uVcvOf2Igdt4cw9RNNbjz7xD2ZnmCz1S8,849
|
|
12
|
+
structo/serializers/literal.py,sha256=ZQQ_SVFTwSe8cfOE9eeaw9j12ytYgvav0PPrve12JWY,857
|
|
13
|
+
structo/serializers/numbers.py,sha256=bNyzRtpXRmHIPx93JOG_B2CqrfkOk09Dfb01LYBpEaY,2767
|
|
14
|
+
structo/serializers/object.py,sha256=FH5w5GDwvo2P3HjWgmNDql_y0OlvpOVhGEjV0OqRxgs,1535
|
|
15
|
+
structo/serializers/optional.py,sha256=nUyZDaB_UIDb4dPxDQSkSEzi387AOERwskR2zwoZGDQ,924
|
|
16
|
+
structo/serializers/packed.py,sha256=RkhgXXfpOMYysolnL0mvezkyPje86ukOIYzUraAZ7k8,1471
|
|
17
|
+
structo/serializers/string.py,sha256=A-wFOGgNbnHZHDkOfwetb-O1m59hvOIehTzfq66n2bU,732
|
|
18
|
+
structo-0.0.10.dist-info/licenses/LICENSE,sha256=lOeFTSB86KGSl3-qqiI0zhIy0Nv-2USUp_93ANudE14,1076
|
|
19
|
+
structo-0.0.10.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
20
|
+
structo-0.0.10.dist-info/METADATA,sha256=DNRhNUEVH2KDrTjD4-uHL--VTElJFNdWH_Bmr1m7C6Y,557
|
|
21
|
+
structo-0.0.10.dist-info/RECORD,,
|
|
@@ -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.
|