Source code for osi_utilities.tracefile.readers.base
# 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._types import ReadResult, ReadStatus
logger = logging.getLogger(__name__)
[docs]
class TraceReader(ABC):
"""Abstract base class for reading OSI trace files.
Supports context manager protocol and iteration.
Read contract:
- ``read_message()`` returns ``None`` on EOF.
- ``read_message()`` returns ``ReadResult(status=ReadStatus.OK, ...)`` for valid messages.
- ``read_message()`` returns ``ReadResult(status=ReadStatus.INCOMPATIBLE, ...)``
for incompatible schema/encoding/message-type cases.
- ``read_message()`` returns ``ReadResult(status=ReadStatus.ERROR, ...)``
for actual runtime read/decode/parse failures.
Iterator contract (``for ... in reader``):
- raises ``ValueError`` for ``ReadStatus.INCOMPATIBLE``
- raises ``RuntimeError`` for ``ReadStatus.ERROR``
"""
[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`` for message/status outcomes, or ``None`` if no more messages.
"""
[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) -> TraceReader:
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
if result.status == ReadStatus.INCOMPATIBLE:
raise ValueError(result.error_message or "Trace reader encountered an incompatible message.")
if result.status == ReadStatus.ERROR:
raise RuntimeError(result.error_message or "Trace reader encountered an error.")
return result