Source code for osi_utilities.tracefile.reader

# SPDX-License-Identifier: MPL-2.0
# SPDX-FileCopyrightText: Copyright (c) 2026, Bayerische Motoren Werke Aktiengesellschaft (BMW AG)

"""Abstract base class for trace file readers and factory for reader creation."""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from collections.abc import Iterator
from pathlib import Path

from osi_utilities.tracefile._types import MessageType, ReadResult

logger = logging.getLogger(__name__)


[docs] class TraceFileReader(ABC): """Abstract base class for reading OSI trace files. Supports context manager protocol and iteration. Usage:: with TraceFileReaderFactory.create_reader("trace.mcap") as reader: for result in reader: print(result.message_type, result.message) """
[docs] @abstractmethod def open(self, path: Path) -> bool: """Open a trace file for reading. Args: path: Path to the trace file. Returns: True if the file was opened successfully, False otherwise. """
[docs] @abstractmethod def read_message(self) -> ReadResult | None: """Read the next message from the trace file. Returns: A ReadResult containing the deserialized message, or None if no more messages. Raises: RuntimeError: If deserialization fails. """
[docs] @abstractmethod def has_next(self) -> bool: """Check if there are more messages to read."""
[docs] @abstractmethod def close(self) -> None: """Close the trace file and release resources."""
def __enter__(self) -> TraceFileReader: return self def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: self.close() def __iter__(self) -> Iterator[ReadResult]: return self def __next__(self) -> ReadResult: result = self.read_message() if result is None: raise StopIteration return result
[docs] class TraceFileReaderFactory: """Factory for creating trace file readers based on file extension."""
[docs] @staticmethod def create_reader( path: str | Path, *, message_type: MessageType | None = None, ) -> TraceFileReader: """Create a trace file reader appropriate for the given file. Args: path: Path to the trace file. Extension determines the reader type. message_type: Explicit message type for binary/txth files. If provided, overrides filename-based inference. Ignored for MCAP files (which store the schema in the file itself). Returns: An opened TraceFileReader instance. Raises: ValueError: If the file extension is not supported. RuntimeError: If the file cannot be opened. """ path = Path(path) suffix = path.suffix.lower() if suffix == ".mcap": from osi_utilities.tracefile.mcap_reader import MCAPTraceFileReader reader = MCAPTraceFileReader() elif suffix == ".osi": from osi_utilities.tracefile.binary_reader import BinaryTraceFileReader if message_type is not None: reader = BinaryTraceFileReader(message_type=message_type) else: reader = BinaryTraceFileReader() elif suffix == ".txth": from osi_utilities.tracefile.txth_reader import TXTHTraceFileReader reader = TXTHTraceFileReader() else: raise ValueError(f"Unsupported trace file extension: '{suffix}'") if not reader.open(path): raise RuntimeError(f"Failed to open trace file: {path}") return reader
[docs] def open_trace_file( path: str | Path, *, message_type: MessageType | None = None, ) -> TraceFileReader: """Convenience function to open a trace file for reading. Equivalent to ``TraceFileReaderFactory.create_reader(path)``. Args: path: Path to the trace file. message_type: Explicit message type for binary/txth files. Returns: An opened TraceFileReader instance (use as context manager). """ return TraceFileReaderFactory.create_reader(path, message_type=message_type)