Skip to content

Commit

Permalink
Serializable dev
Browse files Browse the repository at this point in the history
  • Loading branch information
vkottler committed Aug 26, 2023
1 parent 719c59b commit accb989
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 60 deletions.
4 changes: 4 additions & 0 deletions runtimepy/primitives/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
Int16,
Int32,
Int64,
SignedInt,
Uint8,
Uint16,
Uint32,
Uint64,
UnsignedInt,
)

__all__ = [
Expand All @@ -40,6 +42,8 @@
"Primitivelike",
"normalize",
"create",
"SignedInt",
"UnsignedInt",
]

AnyPrimitive = _Union[
Expand Down
2 changes: 1 addition & 1 deletion runtimepy/primitives/array/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def __init__(
for item in primitives:
self.add(item)

super().__init__(chain=next_array)
super().__init__(byte_order=self.byte_order, chain=next_array)

self._fragments: _List["PrimitiveArray"] = []
self._fragment_specs: _List[ArrayFragmentSpec] = []
Expand Down
1 change: 1 addition & 0 deletions runtimepy/primitives/int.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,5 @@ def __init__(self, value: int = 0) -> None:


Uint64 = Uint64Primitive
SignedInt = _Union[Int8, Int16, Int32, Int64]
UnsignedInt = _Union[Uint8, Uint16, Uint32, Uint64]
9 changes: 9 additions & 0 deletions runtimepy/primitives/serializable/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
A module defining an interface for serializable objects.
"""

# internal
from runtimepy.primitives.serializable.base import Serializable
from runtimepy.primitives.serializable.fixed import FixedChunk

__all__ = ["Serializable", "FixedChunk"]
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
A module defining an interface for serializable objects.
A module defining a base interface fore serializable objects.
"""

# built-in
Expand All @@ -8,6 +8,12 @@
from typing import BinaryIO as _BinaryIO
from typing import TypeVar

# internal
from runtimepy.primitives.byte_order import (
DEFAULT_BYTE_ORDER as _DEFAULT_BYTE_ORDER,
)
from runtimepy.primitives.byte_order import ByteOrder as _ByteOrder

T = TypeVar("T", bound="Serializable")


Expand All @@ -16,11 +22,17 @@ class Serializable(ABC):

size: int

def __init__(self, chain: T = None) -> None:
def __init__(
self,
byte_order: _ByteOrder = _DEFAULT_BYTE_ORDER,
chain: T = None,
) -> None:
"""Initialize this instance."""

if not hasattr(self, "size"):
self.size = 0

self.byte_order = byte_order
self.chain = chain

def length(self) -> int:
Expand Down Expand Up @@ -51,6 +63,7 @@ def __copy__(self: T) -> T:

if self.chain is not None:
result.assign(self.chain.copy())
result.byte_order = self.byte_order

return result

Expand Down Expand Up @@ -80,14 +93,19 @@ def to_stream(self, stream: _BinaryIO) -> int:

return result

@abstractmethod
def update(self, data: bytes) -> int:
"""Update this serializable from a bytes instance."""
raise NotImplementedError

Check warning on line 98 in runtimepy/primitives/serializable/base.py

View check run for this annotation

Codecov / codecov/patch

runtimepy/primitives/serializable/base.py#L98

Added line #L98 was not covered by tests

def _from_stream(self, stream: _BinaryIO) -> int:
"""Update just this instance from a stream."""

return self.update(stream.read(self.size))

def from_stream(self, stream: _BinaryIO) -> int:
"""Update this serializable from a stream."""

result = self.update(stream.read(self.size))
result = self._from_stream(stream)

if self.chain is not None:
result += self.chain.from_stream(stream)
Expand All @@ -104,30 +122,3 @@ def add_to_end(self, chain: T) -> None:
"""Add a new serializable to the end of this chain."""

self.end.assign(chain)

Check warning on line 124 in runtimepy/primitives/serializable/base.py

View check run for this annotation

Codecov / codecov/patch

runtimepy/primitives/serializable/base.py#L124

Added line #L124 was not covered by tests


class FixedChunk(Serializable):
"""A simple fixed-size serializable chunk."""

def __init__(self, data: bytes, chain: Serializable = None) -> None:
"""Initialize this instance."""

super().__init__(chain=chain)
assert data
self.data = data
self.size = len(self.data)

def _copy_impl(self) -> "FixedChunk":
"""Make a copy of this instance."""
return FixedChunk(self.data)

def __bytes__(self) -> bytes:
"""Get this serializable as a bytes instance."""
return self.data

def update(self, data: bytes) -> int:
"""Update this serializable from a bytes instance."""

assert len(data) == self.size
self.data = data
return self.size
43 changes: 43 additions & 0 deletions runtimepy/primitives/serializable/fixed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
A module implementing a fixed-size bytes serializable.
"""

# built-in
from copy import copy as _copy

# internal
from runtimepy.primitives.serializable.base import Serializable


class FixedChunk(Serializable):
"""A simple fixed-size serializable chunk."""

def __init__(self, data: bytes, chain: Serializable = None) -> None:
"""Initialize this instance."""

super().__init__(chain=chain)
self.data = data
self.size = len(self.data)

def __str__(self) -> str:
"""Get this chunk as a string."""
return self.data.decode()

def _copy_impl(self) -> "FixedChunk":
"""Make a copy of this instance."""
return FixedChunk(_copy(self.data))

def __bytes__(self) -> bytes:
"""Get this serializable as a bytes instance."""
return self.data

def update(self, data: bytes) -> int:
"""Update this serializable from a bytes instance."""

self.data = data
self.size = len(self.data)
return self.size

def update_str(self, data: str) -> int:
"""Update this chunk from a string."""
return self.update(data.encode())
94 changes: 94 additions & 0 deletions runtimepy/primitives/serializable/prefixed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""
A module implementing a variable-size bytes serializable, using an integer
primitive prefix to determine the size of the chunk portion.
"""

# built-in
from typing import BinaryIO as _BinaryIO
from typing import Type, TypeVar

# internal
from runtimepy.primitives import Primitivelike, UnsignedInt, create
from runtimepy.primitives.byte_order import (
DEFAULT_BYTE_ORDER as _DEFAULT_BYTE_ORDER,
)
from runtimepy.primitives.byte_order import ByteOrder as _ByteOrder
from runtimepy.primitives.serializable.base import Serializable
from runtimepy.primitives.serializable.fixed import FixedChunk

T = TypeVar("T", bound="PrefixedChunk")


class PrefixedChunk(Serializable):
"""A simple integer-prefixed chunk serializable."""

def __init__(
self,
prefix: UnsignedInt,
byte_order: _ByteOrder = _DEFAULT_BYTE_ORDER,
chain: Serializable = None,
) -> None:
"""Initialize this instance."""

super().__init__(byte_order=byte_order, chain=chain)

# Validate prefix.
assert prefix.kind.is_integer, prefix
assert not prefix.kind.signed, prefix

self.prefix = prefix
self.chunk = FixedChunk(bytes(self.prefix.value))
self._update_size()

def __str__(self) -> str:
"""Get this chunk as a string."""
return str(self.chunk)

def update_str(self, data: str) -> int:
"""Update this chunk from a string."""

size = self.chunk.update_str(data)
self.prefix.value = size
return self._update_size()

def _update_size(self) -> int:
"""Update this instance's size."""

assert self.prefix.value == self.chunk.size
self.size = self.prefix.kind.size + self.prefix.value
return self.size

def _copy_impl(self) -> "PrefixedChunk":
"""Make a copy of this instance."""

result = PrefixedChunk(self.prefix.copy()) # type: ignore
result.chunk = self.chunk.copy()

return result

def __bytes__(self) -> bytes:
"""Get this serializable as a bytes instance."""

return self.prefix.binary(byte_order=self.byte_order) + bytes(
self.chunk
)

def _from_stream(self, stream: _BinaryIO) -> int:
"""Update just this instance from a stream."""

self.chunk.update(
stream.read(
self.prefix.from_stream(stream, byte_order=self.byte_order)
)
)
return self._update_size()

@classmethod
def create(
cls: Type[T],
prefix: Primitivelike = "uint16",
chain: Serializable = None,
) -> T:
"""Create a prefixed chunk."""

return cls(create(prefix), chain=chain) # type: ignore
27 changes: 5 additions & 22 deletions runtimepy/primitives/type/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,13 @@
Uint64Type,
)

AnyIntegerType = _Union[
Int8Type,
Int16Type,
Int32Type,
Int64Type,
Uint8Type,
Uint16Type,
Uint32Type,
Uint64Type,
]
SignedIntegerType = _Union[Int8Type, Int16Type, Int32Type, Int64Type]
UnsignedIntegerType = _Union[Uint8Type, Uint16Type, Uint32Type, Uint64Type]

AnyIntegerType = _Union[SignedIntegerType, UnsignedIntegerType]

AnyPrimitiveType = _Union[
Int8Type,
Int16Type,
Int32Type,
Int64Type,
Uint8Type,
Uint16Type,
Uint32Type,
Uint64Type,
HalfType,
FloatType,
DoubleType,
BooleanType,
AnyIntegerType, HalfType, FloatType, DoubleType, BooleanType
]

PrimitiveTypes: _Dict[str, AnyPrimitiveType] = {
Expand Down
3 changes: 2 additions & 1 deletion runtimepy/primitives/type/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ class PrimitiveType(_Generic[T]):
name: str
c_type: _Type[T]

def __init__(self, struct_format: str) -> None:
def __init__(self, struct_format: str, signed: bool = True) -> None:
"""Initialize this primitive type."""

self.format = struct_format
self.signed = signed

# Make sure that the struct size and ctype size match. There's
# unfortunately no obvious (or via public interfaces) way to just
Expand Down
2 changes: 1 addition & 1 deletion runtimepy/primitives/type/bool.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class BooleanType(_PrimitiveType[_BoolCtype]):

def __init__(self) -> None:
"""Initialize this type."""
super().__init__("?")
super().__init__("?", signed=False)
assert self.is_boolean


Expand Down
8 changes: 4 additions & 4 deletions runtimepy/primitives/type/int.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Uint8Type(_PrimitiveType[_Uint8Ctype]):

def __init__(self) -> None:
"""Initialize this type."""
super().__init__("B")
super().__init__("B", signed=False)
assert self.is_integer


Expand All @@ -97,7 +97,7 @@ class Uint16Type(_PrimitiveType[_Uint16Ctype]):

def __init__(self) -> None:
"""Initialize this type."""
super().__init__("H")
super().__init__("H", signed=False)
assert self.is_integer


Expand All @@ -112,7 +112,7 @@ class Uint32Type(_PrimitiveType[_Uint32Ctype]):

def __init__(self) -> None:
"""Initialize this type."""
super().__init__("I")
super().__init__("I", signed=False)
assert self.is_integer


Expand All @@ -127,7 +127,7 @@ class Uint64Type(_PrimitiveType[_Uint64Ctype]):

def __init__(self) -> None:
"""Initialize this type."""
super().__init__("Q")
super().__init__("Q", signed=False)
assert self.is_integer


Expand Down
Empty file.
Loading

0 comments on commit accb989

Please sign in to comment.