structo 0.0.2__tar.gz → 0.0.3__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.2 → structo-0.0.3}/PKG-INFO +1 -1
- {structo-0.0.2 → structo-0.0.3}/examples/custom_serializer.py +12 -14
- {structo-0.0.2 → structo-0.0.3}/examples/iterable_serializer.py +19 -19
- structo-0.0.3/examples/wav.py +39 -0
- structo-0.0.3/pytest.ini +4 -0
- {structo-0.0.2 → structo-0.0.3}/structo/__init__.py +5 -7
- structo-0.0.3/structo/object.py +58 -0
- structo-0.0.3/structo/serialise.py +41 -0
- structo-0.0.3/structo/serializer.py +20 -0
- {structo-0.0.2 → structo-0.0.3}/structo/types/__init__.py +6 -2
- structo-0.0.3/structo/types/array.py +34 -0
- structo-0.0.3/structo/types/blob.py +19 -0
- structo-0.0.3/structo/types/buffer.py +21 -0
- structo-0.0.3/structo/types/list.py +27 -0
- structo-0.0.3/structo/types/literal.py +30 -0
- structo-0.0.3/structo/types/object.py +51 -0
- structo-0.0.3/structo/types/primatives.py +101 -0
- structo-0.0.3/structo/types/string.py +21 -0
- {structo-0.0.2 → structo-0.0.3}/structo/utils.py +5 -39
- structo-0.0.3/tests/test_array.py +7 -0
- {structo-0.0.2/structo → structo-0.0.3}/tests/test_primatives.py +21 -30
- structo-0.0.3/tests/test_size.py +68 -0
- structo-0.0.3/tests/utils.py +22 -0
- structo-0.0.3/union.py +8 -0
- structo-0.0.2/examples/wav.py +0 -40
- structo-0.0.2/structo/object.py +0 -48
- structo-0.0.2/structo/serialise.py +0 -77
- structo-0.0.2/structo/serializer.py +0 -13
- structo-0.0.2/structo/tests/utils.py +0 -12
- structo-0.0.2/structo/types/dynamic.py +0 -98
- structo-0.0.2/structo/types/fixed.py +0 -82
- structo-0.0.2/structo/types/literal.py +0 -34
- structo-0.0.2/structo/types/primatives.py +0 -89
- {structo-0.0.2 → structo-0.0.3}/.gitignore +0 -0
- {structo-0.0.2 → structo-0.0.3}/LICENSE +0 -0
- {structo-0.0.2 → structo-0.0.3}/README.md +0 -0
- {structo-0.0.2 → structo-0.0.3}/pyproject.toml +0 -0
- {structo-0.0.2 → structo-0.0.3}/uv.lock +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from structo import Serializer
|
|
1
|
+
from structo import Serializer
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
import typing as t
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
# We don't inherit from SerialiableObject
|
|
6
7
|
# since we're using a custom serializer
|
|
7
8
|
@dataclass
|
|
@@ -10,14 +11,9 @@ class Flags:
|
|
|
10
11
|
flag_b: bool
|
|
11
12
|
flag_c: bool
|
|
12
13
|
|
|
13
|
-
class FlagsSerialiser(Serializer[Flags]):
|
|
14
|
-
# This is optional, but allows for size calculations
|
|
15
|
-
@staticmethod
|
|
16
|
-
def length(format):
|
|
17
|
-
return 1
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
def write(
|
|
15
|
+
class FlagsSerialiser(Serializer[Flags]):
|
|
16
|
+
def write(self, buf, value):
|
|
21
17
|
byte = 0
|
|
22
18
|
if value.flag_a:
|
|
23
19
|
byte += 1 << 0
|
|
@@ -28,8 +24,7 @@ class FlagsSerialiser(Serializer[Flags]):
|
|
|
28
24
|
|
|
29
25
|
byte = buf.write(bytes([byte]))
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
def read(buf, format):
|
|
27
|
+
def read(self, buf):
|
|
33
28
|
byte = buf.read(1)[0]
|
|
34
29
|
flag_a = ((byte >> 0) & 1) != 0
|
|
35
30
|
flag_b = ((byte >> 1) & 1) != 0
|
|
@@ -40,7 +35,11 @@ class FlagsSerialiser(Serializer[Flags]):
|
|
|
40
35
|
flag_c=flag_c,
|
|
41
36
|
)
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
def sizeof(self):
|
|
39
|
+
return 1
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
type FlagsDatatype = t.Annotated[Flags, FlagsSerialiser()]
|
|
44
43
|
|
|
45
44
|
|
|
46
45
|
value = Flags(
|
|
@@ -48,7 +47,6 @@ value = Flags(
|
|
|
48
47
|
flag_b=True,
|
|
49
48
|
flag_c=False,
|
|
50
49
|
)
|
|
51
|
-
output =
|
|
50
|
+
output = FlagsSerialiser().to_bytes(value)
|
|
52
51
|
print(output)
|
|
53
|
-
print(
|
|
54
|
-
|
|
52
|
+
print(FlagsSerialiser().from_bytes(output))
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from typing import Annotated
|
|
1
3
|
from structo import (
|
|
2
4
|
SerializableObject,
|
|
3
5
|
Serializer,
|
|
4
|
-
write_serializable,
|
|
5
|
-
read_serializable,
|
|
6
6
|
uint32_LE,
|
|
7
7
|
String,
|
|
8
8
|
List,
|
|
9
9
|
)
|
|
10
10
|
import random
|
|
11
|
-
import typing as t
|
|
12
11
|
from pathlib import Path
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class Post(SerializableObject):
|
|
16
|
-
id: uint32_LE
|
|
17
|
-
author: String
|
|
18
|
-
tags:
|
|
15
|
+
id: Annotated[int, uint32_LE]
|
|
16
|
+
author: Annotated[str, String(uint32_LE)]
|
|
17
|
+
tags: Annotated[list[str], List(String(uint32_LE), uint32_LE)]
|
|
18
|
+
|
|
19
|
+
CONTINUE_BYTE = bytes([255])
|
|
20
|
+
NULL_TERMINATOR = bytes([0])
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
class PostsSerialiser(Serializer[t.Iterable[Post]]):
|
|
22
|
-
|
|
23
|
-
def write(buf, format, value):
|
|
24
|
+
def write(self, buf, value):
|
|
24
25
|
for item in value:
|
|
25
|
-
buf.write(
|
|
26
|
-
|
|
26
|
+
buf.write(CONTINUE_BYTE)
|
|
27
|
+
item.write(buf)
|
|
27
28
|
|
|
28
|
-
buf.write(
|
|
29
|
+
buf.write(NULL_TERMINATOR)
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
def read(buf, format):
|
|
31
|
+
def read(self, buf):
|
|
32
32
|
while True:
|
|
33
|
-
continue_byte = buf.read(1)
|
|
34
|
-
if continue_byte ==
|
|
33
|
+
continue_byte = buf.read(1)
|
|
34
|
+
if continue_byte == NULL_TERMINATOR:
|
|
35
35
|
break
|
|
36
|
-
assert continue_byte ==
|
|
36
|
+
assert continue_byte == CONTINUE_BYTE, "Continue byte was not 255"
|
|
37
37
|
|
|
38
38
|
print("Loading...") # to prove it's interspliced loading and yielding
|
|
39
|
-
yield
|
|
39
|
+
yield Post.read(buf)
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
type PostsIterable = t.Annotated[t.Iterable[Post], PostsSerialiser()]
|
|
@@ -56,9 +56,9 @@ output = Path("output.raw")
|
|
|
56
56
|
if not output.exists():
|
|
57
57
|
with open(output, "wb") as f:
|
|
58
58
|
posts = generate_posts()
|
|
59
|
-
|
|
59
|
+
PostsSerialiser().write(f, posts)
|
|
60
60
|
|
|
61
61
|
with open(output, "rb") as f:
|
|
62
|
-
iterable_posts =
|
|
62
|
+
iterable_posts = PostsSerialiser().read(f)
|
|
63
63
|
for post in iterable_posts:
|
|
64
64
|
print(post)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
from structo import uint16_LE, uint32_LE, SerializableObject, Literal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class WavHeader(SerializableObject):
|
|
6
|
+
chunk_id: Annotated[bytes, Literal(b"RIFF")]
|
|
7
|
+
chunk_size: Annotated[int, uint32_LE]
|
|
8
|
+
format: Annotated[bytes, Literal(b"WAVE")]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ChunkHeader(SerializableObject):
|
|
12
|
+
id: Annotated[bytes, Literal(b"fmt ", b"data")]
|
|
13
|
+
size: Annotated[int, uint32_LE]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WavFormat(SerializableObject):
|
|
17
|
+
audio_format: Annotated[int, uint16_LE]
|
|
18
|
+
num_channels: Annotated[int, uint16_LE]
|
|
19
|
+
sample_rate: Annotated[int, uint32_LE]
|
|
20
|
+
byte_range: Annotated[int, uint32_LE]
|
|
21
|
+
block_align: Annotated[int, uint16_LE]
|
|
22
|
+
bits_per_sample: Annotated[int, uint16_LE]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
with open("example.wav", "rb") as f:
|
|
26
|
+
WavHeader.read(f)
|
|
27
|
+
|
|
28
|
+
format_header = ChunkHeader.read(f)
|
|
29
|
+
assert format_header.id == b'fmt '
|
|
30
|
+
|
|
31
|
+
format_data = f.read(format_header.size)
|
|
32
|
+
format = WavFormat.from_bytes(format_data)
|
|
33
|
+
|
|
34
|
+
data_header = ChunkHeader.read(f)
|
|
35
|
+
assert data_header.id == b"data"
|
|
36
|
+
wav_data = f.read(data_header.size)
|
|
37
|
+
|
|
38
|
+
print(format)
|
|
39
|
+
print(len(wav_data))
|
structo-0.0.3/pytest.ini
ADDED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
Structify
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
__version__ = "0.0.
|
|
5
|
+
__version__ = "0.0.3"
|
|
6
6
|
|
|
7
|
-
from .
|
|
8
|
-
from .serialise import write_serializable, read_serializable
|
|
9
|
-
from .serializer import Serializer, Format
|
|
7
|
+
from .serializer import Serializer
|
|
10
8
|
from .types import (
|
|
11
9
|
uint64_BE,
|
|
12
10
|
uint64_LE,
|
|
@@ -39,11 +37,11 @@ from .types import (
|
|
|
39
37
|
List,
|
|
40
38
|
String,
|
|
41
39
|
Blob,
|
|
42
|
-
Literal
|
|
40
|
+
Literal,
|
|
41
|
+
ObjectSerializer,
|
|
43
42
|
)
|
|
43
|
+
from .object import SerializableObject
|
|
44
44
|
from .utils import (
|
|
45
45
|
StructoReader,
|
|
46
46
|
StructifyWriter,
|
|
47
|
-
serialize_to_bytes,
|
|
48
|
-
deserialize_from_bytes,
|
|
49
47
|
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import annotationlib
|
|
3
|
+
import typing as t
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from .serializer import Serializer
|
|
7
|
+
from .serialise import get_serializer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SerializableObjectMeta(type):
|
|
11
|
+
def __new__(cls, name, bases, dct):
|
|
12
|
+
new_class = t.cast(type, super().__new__(cls, name, bases, dct))
|
|
13
|
+
if name == "SerializableObject":
|
|
14
|
+
return new_class
|
|
15
|
+
|
|
16
|
+
annotate = annotationlib.get_annotate_from_class_namespace(dct)
|
|
17
|
+
_annotations: dict[str, Serializer] = {}
|
|
18
|
+
|
|
19
|
+
if annotate:
|
|
20
|
+
attrs = annotate(annotationlib.Format.VALUE_WITH_FAKE_GLOBALS)
|
|
21
|
+
for key, value in attrs.items():
|
|
22
|
+
try:
|
|
23
|
+
serializer = get_serializer(value)
|
|
24
|
+
_annotations[key] = serializer
|
|
25
|
+
except Exception as e:
|
|
26
|
+
raise ValueError(
|
|
27
|
+
f"Invalid attribute defintion for {name}.{key}"
|
|
28
|
+
) from e
|
|
29
|
+
|
|
30
|
+
return t.cast(SerializableObject, dataclass(new_class))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# This is very messed up since we
|
|
34
|
+
@t.dataclass_transform()
|
|
35
|
+
class SerializableObject(metaclass=SerializableObjectMeta):
|
|
36
|
+
@classmethod
|
|
37
|
+
def serializer(cls) -> Serializer[t.Self]:
|
|
38
|
+
from .types import ObjectSerializer
|
|
39
|
+
|
|
40
|
+
return ObjectSerializer(cls)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def sizeof(cls) -> int | None:
|
|
44
|
+
return cls.serializer().sizeof()
|
|
45
|
+
|
|
46
|
+
def write(self, buf: io.Writer):
|
|
47
|
+
return self.serializer().write(buf, self)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def read(cls, buf: io.Reader) -> t.Self:
|
|
51
|
+
return cls.serializer().read(buf)
|
|
52
|
+
|
|
53
|
+
def to_bytes(self) -> bytes:
|
|
54
|
+
return self.serializer().to_bytes(self)
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def from_bytes(cls, data: bytes) -> t.Self:
|
|
58
|
+
return cls.serializer().from_bytes(data)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import typing as t
|
|
3
|
+
from .serializer import Serializer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_serializer(format: type) -> Serializer:
|
|
7
|
+
from .serializer import Serializer
|
|
8
|
+
from .object import SerializableObject
|
|
9
|
+
from .types.object import ObjectSerializer
|
|
10
|
+
|
|
11
|
+
if t.get_origin(format) is t.Annotated:
|
|
12
|
+
args = t.get_args(format)
|
|
13
|
+
serializers = [arg for arg in args if isinstance(arg, Serializer)]
|
|
14
|
+
assert len(serializers) != 0, f"No serializers for {format} found"
|
|
15
|
+
assert len(serializers) == 1, f"More than one serializers for {format} found"
|
|
16
|
+
serializer = serializers[0]
|
|
17
|
+
|
|
18
|
+
return serializer
|
|
19
|
+
|
|
20
|
+
if issubclass(format, SerializableObject):
|
|
21
|
+
return ObjectSerializer(format)
|
|
22
|
+
|
|
23
|
+
# Nicely formatted errors:
|
|
24
|
+
if format == int:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
f"No serializer for int, you need to use structo.int32, structo.int32 or similar instead"
|
|
27
|
+
)
|
|
28
|
+
if format == float:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"No serializer for float, you need to use structo.float32 or structo.float64 instead"
|
|
31
|
+
)
|
|
32
|
+
if format == bytes:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"No serializer for bytes, you need to use structo.Buffer or structo.Blob instead"
|
|
35
|
+
)
|
|
36
|
+
if format == list:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"No serializer for list, you need to use structo.List or structo.Array instead"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
raise NotImplementedError(f"No serializer found for {format}")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import io
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Serializer[T]:
|
|
5
|
+
def sizeof(self) -> int | None:
|
|
6
|
+
return None
|
|
7
|
+
|
|
8
|
+
def write(self, buf: io.Writer, value: T): ...
|
|
9
|
+
|
|
10
|
+
def read(self, buf: io.Reader) -> T: ...
|
|
11
|
+
|
|
12
|
+
def to_bytes(self, value: T) -> bytes:
|
|
13
|
+
buf = io.BytesIO()
|
|
14
|
+
self.write(buf, value)
|
|
15
|
+
buf.seek(0)
|
|
16
|
+
return buf.getvalue()
|
|
17
|
+
|
|
18
|
+
def from_bytes(self, data: bytes) -> T:
|
|
19
|
+
buf = io.BytesIO(data)
|
|
20
|
+
return self.read(buf)
|
|
@@ -26,6 +26,10 @@ from .primatives import (
|
|
|
26
26
|
float32_LE,
|
|
27
27
|
float32,
|
|
28
28
|
)
|
|
29
|
-
from .
|
|
30
|
-
from .
|
|
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
|
|
31
34
|
from .literal import Literal
|
|
35
|
+
from .object import ObjectSerializer
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from ..serializer import Serializer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Array[T](Serializer[list[T]]):
|
|
5
|
+
length: int
|
|
6
|
+
type: Serializer[T]
|
|
7
|
+
|
|
8
|
+
def __init__(self, length: int, type: Serializer[T]) -> None:
|
|
9
|
+
assert length > 0, "Array must be longer than 0"
|
|
10
|
+
self.length = length
|
|
11
|
+
self.type = type
|
|
12
|
+
|
|
13
|
+
def write(self, buf, value):
|
|
14
|
+
assert (
|
|
15
|
+
len(value) == self.length
|
|
16
|
+
), f"expected array with {self.length} length, receieved {len(value)}"
|
|
17
|
+
|
|
18
|
+
for item in value:
|
|
19
|
+
self.type.write(buf, item)
|
|
20
|
+
|
|
21
|
+
def read(self, buf):
|
|
22
|
+
items = []
|
|
23
|
+
for _ in range(self.length):
|
|
24
|
+
items.append(self.type.read(buf))
|
|
25
|
+
|
|
26
|
+
return items
|
|
27
|
+
|
|
28
|
+
def sizeof(self):
|
|
29
|
+
element_length = self.type.sizeof()
|
|
30
|
+
if element_length is None:
|
|
31
|
+
return None
|
|
32
|
+
else:
|
|
33
|
+
return element_length * self.length
|
|
34
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ..serializer import Serializer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Blob(Serializer[bytes]):
|
|
5
|
+
"A set of arbitrary bytes, prefixed with it's length"
|
|
6
|
+
|
|
7
|
+
length_type: Serializer[int]
|
|
8
|
+
|
|
9
|
+
def __init__(self, length_type: Serializer[int]) -> None:
|
|
10
|
+
self.length_type = length_type
|
|
11
|
+
|
|
12
|
+
def write(self, buf, value):
|
|
13
|
+
self.length_type.write(buf, len(value))
|
|
14
|
+
buf.write(value)
|
|
15
|
+
|
|
16
|
+
def read(self, buf):
|
|
17
|
+
length = self.length_type.read(buf)
|
|
18
|
+
data = buf.read(length)
|
|
19
|
+
return data
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from ..serializer 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, buf, value):
|
|
11
|
+
assert (
|
|
12
|
+
len(value) == self.length
|
|
13
|
+
), f"expected data with length {self.length}, received {len(value)}"
|
|
14
|
+
buf.write(value)
|
|
15
|
+
|
|
16
|
+
def read(self, buf):
|
|
17
|
+
data = buf.read(self.length)
|
|
18
|
+
return data
|
|
19
|
+
|
|
20
|
+
def sizeof(self):
|
|
21
|
+
return self.length
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from ..serializer import Serializer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class List[T](Serializer[list[T]]):
|
|
5
|
+
"A list of items, prefixed with it's length"
|
|
6
|
+
|
|
7
|
+
value_type: Serializer[T]
|
|
8
|
+
length_type: Serializer[int]
|
|
9
|
+
|
|
10
|
+
def __init__(self, value_type: Serializer[T], length_type: Serializer[int]) -> None:
|
|
11
|
+
self.value_type = value_type
|
|
12
|
+
self.length_type = length_type
|
|
13
|
+
|
|
14
|
+
def write(self, buf, value):
|
|
15
|
+
length = len(value)
|
|
16
|
+
self.length_type.write(buf, length)
|
|
17
|
+
for item in value:
|
|
18
|
+
self.value_type.write(buf, item)
|
|
19
|
+
|
|
20
|
+
def read(self, buf):
|
|
21
|
+
length = self.length_type.read(buf)
|
|
22
|
+
values = []
|
|
23
|
+
for _ in range(length):
|
|
24
|
+
value = self.value_type.read(buf)
|
|
25
|
+
values.append(value)
|
|
26
|
+
|
|
27
|
+
return values
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from ..serializer 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, buf, value):
|
|
21
|
+
assert value in self.values, f"{value} not in {b", ".join(self.values)}"
|
|
22
|
+
buf.write(self.values)
|
|
23
|
+
|
|
24
|
+
def read(self, buf):
|
|
25
|
+
value = buf.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,51 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import annotationlib
|
|
3
|
+
|
|
4
|
+
from ..serializer import Serializer
|
|
5
|
+
from ..object import SerializableObject
|
|
6
|
+
from ..serialise import get_serializer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ObjectSerializer[T: SerializableObject](Serializer[T]):
|
|
10
|
+
_annotations: dict[str, Serializer] = {}
|
|
11
|
+
_type: type
|
|
12
|
+
|
|
13
|
+
def __init__(self, type: type[T]) -> None:
|
|
14
|
+
annotations: dict[str, Serializer] = {}
|
|
15
|
+
self._type = type
|
|
16
|
+
|
|
17
|
+
attrs = annotationlib.get_annotations(type)
|
|
18
|
+
for key, value in attrs.items():
|
|
19
|
+
try:
|
|
20
|
+
annotations[key] = get_serializer(value)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
f"Invalid attribute defintion for {type.__name__}.{key}"
|
|
24
|
+
) from e
|
|
25
|
+
|
|
26
|
+
self._annotations = annotations
|
|
27
|
+
|
|
28
|
+
def sizeof(self) -> int | None:
|
|
29
|
+
total_size = 0
|
|
30
|
+
for serializer in self._annotations.values():
|
|
31
|
+
field_size = serializer.sizeof()
|
|
32
|
+
if field_size is None:
|
|
33
|
+
return None
|
|
34
|
+
else:
|
|
35
|
+
total_size += field_size
|
|
36
|
+
|
|
37
|
+
return total_size
|
|
38
|
+
|
|
39
|
+
def write(self, buf: io.Writer, value: T):
|
|
40
|
+
for field_key, field_format in self._annotations.items():
|
|
41
|
+
field_value = getattr(value, field_key)
|
|
42
|
+
field_format.write(buf, field_value)
|
|
43
|
+
|
|
44
|
+
def read(self, buf: io.Reader) -> T:
|
|
45
|
+
attrs = {}
|
|
46
|
+
|
|
47
|
+
for field_key, field_format in self._annotations.items():
|
|
48
|
+
field_value = field_format.read(buf)
|
|
49
|
+
attrs[field_key] = field_value
|
|
50
|
+
|
|
51
|
+
return self._type(**attrs)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
import io
|
|
3
|
+
import struct
|
|
4
|
+
from ..serializer import Serializer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def struct_serializer(sformat: str, bytes: int) -> Serializer:
|
|
8
|
+
class StructSerializer(Serializer):
|
|
9
|
+
def sizeof(self):
|
|
10
|
+
return bytes
|
|
11
|
+
|
|
12
|
+
def write(self, buf: io.Writer, value: int):
|
|
13
|
+
data = struct.pack(sformat, value)
|
|
14
|
+
return buf.write(data)
|
|
15
|
+
|
|
16
|
+
def read(self, buf: io.Reader):
|
|
17
|
+
data = buf.read(bytes)
|
|
18
|
+
return struct.unpack(sformat, data)[0]
|
|
19
|
+
|
|
20
|
+
return StructSerializer()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
uint64_BE: Serializer[int] = struct_serializer(">Q", bytes=8)
|
|
24
|
+
"**unsigned 64bit integer - big endian**"
|
|
25
|
+
|
|
26
|
+
uint64_LE: Serializer[int] = struct_serializer("<Q", bytes=8)
|
|
27
|
+
"**unsigned 64bit integer - little endian**"
|
|
28
|
+
|
|
29
|
+
uint64 = uint64_BE
|
|
30
|
+
"**unsigned 64bit integer - big endian**"
|
|
31
|
+
|
|
32
|
+
uint32_BE: Serializer[int] = struct_serializer(">I", bytes=4)
|
|
33
|
+
"**unsigned 32bit integer - big endian**"
|
|
34
|
+
|
|
35
|
+
uint32_LE: Serializer[int] = struct_serializer("<I", bytes=4)
|
|
36
|
+
"**unsigned 32bit integer - little endian**"
|
|
37
|
+
|
|
38
|
+
uint32 = uint32_BE
|
|
39
|
+
"**unsigned 32bit integer - big endian**"
|
|
40
|
+
|
|
41
|
+
uint16_BE: Serializer[int] = struct_serializer(">H", bytes=2)
|
|
42
|
+
"**unsigned 16bit integer - big endian**"
|
|
43
|
+
|
|
44
|
+
uint16_LE: Serializer[int] = struct_serializer("<H", bytes=2)
|
|
45
|
+
"**unsigned 16bit integer - little endian**"
|
|
46
|
+
|
|
47
|
+
uint16 = uint16_BE
|
|
48
|
+
"**unsigned 16bit integer - big endian**"
|
|
49
|
+
|
|
50
|
+
uint8: Serializer[int] = struct_serializer("B", bytes=1)
|
|
51
|
+
"**unsigned 8bit integer**"
|
|
52
|
+
|
|
53
|
+
int64_BE: Serializer[int] = struct_serializer(">q", bytes=8)
|
|
54
|
+
"**signed 64bit integer - big endian**"
|
|
55
|
+
|
|
56
|
+
int64_LE: Serializer[int] = struct_serializer("<q", bytes=8)
|
|
57
|
+
"**signed 64bit integer - little endian**"
|
|
58
|
+
|
|
59
|
+
int64 = int64_BE
|
|
60
|
+
"**signed 64bit integer - big endian**"
|
|
61
|
+
|
|
62
|
+
int32_BE: Serializer[int] = struct_serializer(">i", bytes=4)
|
|
63
|
+
"**signed 32bit integer - big endian**"
|
|
64
|
+
|
|
65
|
+
int32_LE: Serializer[int] = struct_serializer("<i", bytes=4)
|
|
66
|
+
"**signed 32bit integer - little endian**"
|
|
67
|
+
|
|
68
|
+
int32 = int32_BE
|
|
69
|
+
"**signed 32bit integer - big endian**"
|
|
70
|
+
|
|
71
|
+
int16_BE: Serializer[int] = struct_serializer(">h", bytes=2)
|
|
72
|
+
"**signed 16bit integer - big endian**"
|
|
73
|
+
|
|
74
|
+
int16_LE: Serializer[int] = struct_serializer("<h", bytes=2)
|
|
75
|
+
"**signed 16bit integer - little endian**"
|
|
76
|
+
|
|
77
|
+
int16 = int16_BE
|
|
78
|
+
"**signed 16bit integer - big endian**"
|
|
79
|
+
|
|
80
|
+
int8: Serializer[int] = struct_serializer("b", bytes=1)
|
|
81
|
+
"**signed 8bit integer**"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
float64_BE: Serializer[float] = struct_serializer(">d", bytes=8)
|
|
85
|
+
"**64bit float - big endian**"
|
|
86
|
+
|
|
87
|
+
float64_LE: Serializer[float] = struct_serializer("<d", bytes=8)
|
|
88
|
+
"**64bit float - little endian**"
|
|
89
|
+
|
|
90
|
+
float64 = float64_BE
|
|
91
|
+
"**64bit float - big endian**"
|
|
92
|
+
|
|
93
|
+
float32_BE: Serializer[float] = struct_serializer(">f", bytes=4)
|
|
94
|
+
"**32bit float - big endian**"
|
|
95
|
+
|
|
96
|
+
float32_LE: Serializer[float] = struct_serializer("<f", bytes=4)
|
|
97
|
+
"**32bit float - little endian**"
|
|
98
|
+
|
|
99
|
+
float32 = float32_BE
|
|
100
|
+
"**32bit float - big endian**"
|
|
101
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from ..serializer import Serializer
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class String(Serializer[str]):
|
|
6
|
+
"A unicode string, prefixed with it's byte length"
|
|
7
|
+
|
|
8
|
+
length_type: Serializer[int]
|
|
9
|
+
|
|
10
|
+
def __init__(self, length_type: Serializer[int]) -> None:
|
|
11
|
+
self.length_type = length_type
|
|
12
|
+
|
|
13
|
+
def write(self, buf, value):
|
|
14
|
+
data = value.encode("utf-8")
|
|
15
|
+
self.length_type.write(buf, len(data))
|
|
16
|
+
buf.write(data)
|
|
17
|
+
|
|
18
|
+
def read(self, buf):
|
|
19
|
+
length = self.length_type.read(buf)
|
|
20
|
+
data = buf.read(length)
|
|
21
|
+
return data.decode("utf-8")
|