Source code for simplebench.reporters.log.report_log_metadata
"""Data structures for logging."""
from __future__ import annotations
from json import JSONEncoder
from pathlib import Path
from typing import TYPE_CHECKING, Any
from simplebench.exceptions import SimpleBenchTypeError, SimpleBenchValueError
from simplebench.type_proxies import is_case, is_choice
from simplebench.utils import get_machine_info
from simplebench.validators import validate_float, validate_type
from simplebench.vcs import GitInfo
from .exceptions import _ReportLogMetadataErrorTag
if TYPE_CHECKING:
from simplebench.case import Case
from simplebench.reporters.choice.choice import Choice
[docs]
class ReportLogMetadata:
"""Container for report log entry metadata.
:ivar filepath: The path to the generated report file.
:ivar timestamp: The timestamp of the report generation.
:ivar reports_log_path: The path to the reports log file.
:ivar case: The Case instance containing benchmark results.
:ivar choice: The Choice instance specifying the report configuration.
"""
def __init__(
self,
*,
filepath: Path | None = None,
timestamp: float,
reports_log_path: Path | None = None,
case: Case,
choice: Choice,
) -> None:
"""Initialize ReportLogMetadata.
:param filepath: The path to the generated report file. If None, no file path is set.
:param timestamp: The timestamp of the report generation.
:param reports_log_path: The path to the reports log file.
:param case: The Case instance containing benchmark results.
:param choice: The Choice instance specifying the report configuration.
:raises SimpleBenchTypeError: If any argument is of invalid type.
"""
self._filepath: Path | None = None
self.filepath = filepath
self._timestamp: float = None # type: ignore[assignment]
self.timestamp = timestamp
self._reports_log_path: Path | None = None
self.reports_log_path = reports_log_path
self._case: Case = None # type: ignore[assignment]
self.case = case
self._choice: Choice = None # type: ignore[assignment]
self.choice = choice
@property
def filepath(self) -> Path | None:
"""The path to the generated report file."""
return self._filepath
@filepath.setter
def filepath(self, value: Path | None) -> None:
"""Set the path to the generated report file.
:param value: The new filepath.
:type value: Path | None
:raises SimpleBenchTypeError: If value is not a Path instance or None.
"""
if value is None:
self._filepath = None
else:
self._filepath = validate_type(
value, Path, 'filepath',
_ReportLogMetadataErrorTag.INVALID_FILEPATH_ARG_TYPE,)
@property
def timestamp(self) -> float:
"""The timestamp of the report generation."""
return self._timestamp
@timestamp.setter
def timestamp(self, value: float) -> None:
"""Set the timestamp of the report generation.
:param value: The new timestamp.
:type value: float
:raises SimpleBenchTypeError: If value is not a float.
"""
self._timestamp = validate_float(
value, 'timestamp',
_ReportLogMetadataErrorTag.INVALID_TIMESTAMP_ARG_TYPE)
@property
def reports_log_path(self) -> Path | None:
"""The path to the reports log file."""
return self._reports_log_path
@reports_log_path.setter
def reports_log_path(self, value: Path | None) -> None:
"""Set the path to the reports log file.
:param value: The new reports log path.
:type value: Path | None
:raises SimpleBenchTypeError: If value is not a Path instance or None.
"""
if value is None:
self._reports_log_path = None
else:
self._reports_log_path = validate_type(
value, Path, 'reports_log_path',
_ReportLogMetadataErrorTag.INVALID_REPORTS_LOG_PATH_ARG_TYPE)
@property
def case(self) -> Case:
"""The Case instance containing benchmark results."""
return self._case
@case.setter
def case(self, value: Case) -> None:
"""Set the Case instance containing benchmark results.
:param value: The new Case instance.
:type value: Case
:raises SimpleBenchTypeError: If value is not a Case instance.
"""
if is_case(value):
self._case = value
else:
raise SimpleBenchTypeError(
f"Expected a Case instance for 'case', got: {type(value)}",
tag=_ReportLogMetadataErrorTag.INVALID_CASE_ARG_TYPE)
@property
def choice(self) -> Choice:
"""The Choice instance specifying the report configuration."""
return self._choice
@choice.setter
def choice(self, value: Choice) -> None:
"""Set the Choice instance specifying the report configuration.
:param value: The new Choice instance.
:type value: Choice
:raises SimpleBenchTypeError: If value is not a Choice instance.
"""
if is_choice(value):
self._choice = value
else:
raise SimpleBenchTypeError(
f"Expected a Choice instance for 'choice', got: {type(value)}",
tag=_ReportLogMetadataErrorTag.INVALID_CHOICE_ARG_TYPE)
[docs]
def save_to_log(self) -> None:
"""Append the metadata as a JSON entry to the reports log file."""
if self.reports_log_path is None:
raise SimpleBenchValueError(
"Cannot save to log: 'reports_log_path' is not set.",
tag=_ReportLogMetadataErrorTag.REPORTS_LOG_PATH_NOT_SET)
json_log_entry = self.to_json()
reports_log_path = self.reports_log_path
if not reports_log_path.parent.exists():
reports_log_path.parent.mkdir(parents=True, exist_ok=True)
with reports_log_path.open(mode='a', encoding='utf-8') as log_file:
log_file.write(json_log_entry + '\n')
[docs]
def to_dict(self) -> dict[str, Any]:
"""Convert metadata to a dictionary for JSON serialization.
:return: A dictionary representation of the metadata.
:rtype: dict[str, Any]
"""
git_info = self.case.git_info.to_dict() if isinstance(self.case.git_info, GitInfo) else None
return {
"timestamp": self.timestamp,
"benchmark_id": self.case.benchmark_id,
"benchmark_group": self.case.group,
"reporter_type": self.choice.reporter.__class__.__name__,
"reporter_name": self.choice.reporter.name,
"output_format": self.choice.output_format.name,
"benchmark_title": self.case.title,
"filepath": self.filepath.as_posix() if self.filepath else None,
"git": git_info,
"machine_info": get_machine_info(),
}
[docs]
def to_json(self) -> str:
"""Convert metadata to a one line JSON string.
:return: A JSON string representation of the metadata.
:rtype: str
"""
return self._one_line_string(JSONEncoder(indent=None).encode(self.to_dict()))
def _one_line_string(self, string: str) -> str:
"""Convert a multi-line string into a single line by replacing newlines with spaces.
:param string: The input string.
:type string: str
:return: The single-line string.
"""
return ' '.join(string.splitlines())