Source code for runtimepy.primitives.serializable.base
"""
A module defining a base interface fore serializable objects.
"""
# built-in
from abc import ABC, abstractmethod
from copy import copy as _copy
from typing import BinaryIO as _BinaryIO
from typing import TypeVar
# third-party
from vcorelib import DEFAULT_ENCODING
# 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")
[docs]
class Serializable(ABC):
"""An interface for serializable objects."""
size: int
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
[docs]
def length(self) -> int:
"""Get the full length of this chain."""
result = self.size
if self.chain is not None:
result += self.chain.length()
return result
[docs]
def length_trace(self) -> str:
"""Get a length-tracing string for this instance."""
current = f"{self.__class__.__name__}({self.size})"
if self.chain is not None:
current += " -> " + self.chain.length_trace()
return current
@property
def end(self) -> "Serializable":
"""Get the end of this chain."""
result = self
if self.chain is not None:
result = self.chain.end
return result
@abstractmethod
def _copy_impl(self: T) -> T:
"""Make a copy of this instance."""
[docs]
def copy_without_chain(self: T) -> T:
"""A method for copying instances without chain references."""
orig = self._copy_impl()
assert orig.chain is None
orig.byte_order = self.byte_order
return orig
def __copy__(self: T) -> T:
"""Make a copy of this serializable."""
result = self.copy_without_chain()
if self.chain is not None:
result.assign(self.chain.copy())
return result
[docs]
def copy(self: T) -> T:
"""Create a copy of a serializable instance."""
return _copy(self)
@abstractmethod
def __bytes__(self) -> bytes:
"""Get this serializable as a bytes instance."""
[docs]
def to_stream(self, stream: _BinaryIO) -> int:
"""Write this serializable to a stream."""
stream.write(bytes(self))
result = self.size
if self.chain is not None:
result += self.chain.to_stream(stream)
return result
[docs]
@abstractmethod
def update(self, data: bytes) -> int:
"""Update this serializable from a bytes instance."""
[docs]
def update_str(self, data: str) -> int:
"""Update this serializable from string data."""
return self.update(data.encode(encoding=DEFAULT_ENCODING))
def _from_stream(self, stream: _BinaryIO) -> int:
"""Update just this instance from a stream."""
return self.update(stream.read(self.size))
[docs]
def from_stream(self, stream: _BinaryIO) -> int:
"""Update this serializable from a stream."""
result = self._from_stream(stream)
if self.chain is not None:
result += self.chain.from_stream(stream)
return result
[docs]
def assign(self, chain: T) -> int:
"""Assign a next serializable."""
assert self.chain is None, self.chain
self.chain = chain
return self.chain.size
[docs]
def add_to_end(self, chain: T, array_length: int = None) -> int:
"""Add a new serializable to the end of this chain."""
# Copy the chain element before it becomes part of the current chain
# if an array is created.
copy_base = None
if array_length is not None:
copy_base = chain.copy()
size = self.end.assign(chain)
# Add additional array elements as copies.
if array_length is not None:
assert copy_base is not None
for _ in range(array_length - 1):
size += self.end.assign(copy_base.copy())
return size