Source code for scfile.core.structio

"""
Low-level binary I/O with structured packing and unpacking operations.

Provides a base class for reading and writing binary data in various formats
using :mod:`struct` and :mod:`numpy`. Designed to be subclassed by format-specific I/O classes.
"""

import io
import struct
from typing import Any, Optional

import numpy as np

from scfile.enums import ByteOrder, F, UnicodeErrors


[docs] class StructIO(io.IOBase): """ Base class for structured binary I/O. Extends :class:`io.IOBase` with methods for packed binary operations. Subclasses must implement :class:`io.IOBase` interface. """ order: ByteOrder = ByteOrder.LITTLE """Default byte order for pack/unpack operations.""" unicode_errors: str = UnicodeErrors.REPLACE """Error handling mode for UTF-8 encoding/decoding.""" def _pack(self, fmt: str, *values: Any) -> bytes: """Serialize *values* to bytes.""" return struct.pack(str(fmt), *values) def _unpack(self, fmt: str) -> tuple[Any, ...]: """Deserialize bytes.""" size = struct.calcsize(str(fmt)) data = self.read(size) return struct.unpack(fmt, data) def _readarray(self, dtype: str, count: int, order: Optional[ByteOrder] = None): """Read an array of *count* elements of type *dtype*.""" order = order or self.order datatype = np.dtype(f"{order}{dtype}") datasize = count * datatype.itemsize return np.frombuffer(self.read(datasize), dtype=datatype, count=count) def _readb(self, fmt: str, order: Optional[ByteOrder] = None) -> Any: """Read single primitive value.""" order = order or self.order return self._unpack(f"{order}{fmt}")[0] def _reads(self, prefix: str = F.U16, order: Optional[ByteOrder] = None) -> bytes: """Read length prefixed string.""" order = order or self.order size = self._readb(prefix, order) return self._unpack(f"{size}s")[0] def _readutf8(self, prefix: str = F.U16, order: Optional[ByteOrder] = None) -> str: """Read a length prefixed UTF-8 string.""" return self._reads(prefix=prefix, order=order).decode("utf-8", errors=self.unicode_errors) def _writeb(self, fmt: str, *values: Any, order: Optional[ByteOrder] = None) -> None: """Serialize and write *values*.""" order = order or self.order data = self._pack(f"{order}{fmt}", *values) self.write(data) def _writenull(self, size: int = 4) -> None: """Write *size* null bytes.""" self.write(bytes(size)) def _writeutf8(self, string: str) -> None: """Write UTF-8 encoded *string*.""" self.write(string.encode("utf-8", errors=self.unicode_errors))