68 lines
2.7 KiB
Python
68 lines
2.7 KiB
Python
"""Helpers for writing runtime configs and preprocessing bundles."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from python_app.models.run_config_model import RunConfigModel, parse_combos_from_text
|
|
from python_app.orchestration.preprocess_assets import (
|
|
PREPROCESS_ASSET_KEYS,
|
|
PREPROCESS_ASSET_SPECS,
|
|
preprocess_asset_model,
|
|
runtime_preprocess_asset_keys,
|
|
)
|
|
from python_app.storage.npz_store import NpzStore
|
|
|
|
|
|
class ConfigWriter:
|
|
"""Write runtime artifacts consumed by C++ processes."""
|
|
|
|
def __init__(self, runtime_dir: Path) -> None:
|
|
"""Create writer rooted at runtime directory."""
|
|
self._runtime_dir = runtime_dir
|
|
self._runtime_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
def prepare_preprocess_bundles(
|
|
self,
|
|
store: NpzStore,
|
|
radar_key: str,
|
|
config: RunConfigModel,
|
|
) -> None:
|
|
"""Export selected preprocess sets into runtime bundles and update config paths."""
|
|
for key in PREPROCESS_ASSET_KEYS:
|
|
preprocess_asset_model(config, key).bundle_path = ""
|
|
|
|
for key in runtime_preprocess_asset_keys(config):
|
|
spec = PREPROCESS_ASSET_SPECS[key]
|
|
asset = preprocess_asset_model(config, key)
|
|
bundle_path = self._runtime_dir / spec.runtime_filename
|
|
store.export_set_bundle(spec.set_kind, radar_key, asset.set_name, bundle_path)
|
|
asset.bundle_path = str(bundle_path)
|
|
|
|
def write(self, config: RunConfigModel, output_path: Path) -> Path:
|
|
"""Atomically write run configuration JSON file.
|
|
|
|
Mirrors ProcessingLiveConfigWriter: dump to a sibling .tmp, flush+fsync to
|
|
durably commit the bytes, then os.replace() onto the destination. The replace
|
|
is atomic, so a C++ consumer can never observe a half-written config (which
|
|
would abort it with an opaque JSON parse error), even across a crash or power
|
|
loss mid-write on the SD-card-backed Pi.
|
|
"""
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
# allow_nan=False: a stray NaN/Infinity must fail loudly here in Python
|
|
# rather than serialize to a non-standard token that aborts every C++
|
|
# consumer at startup with an opaque JSON parse error.
|
|
serialized = json.dumps(config.to_dict(), indent=2, allow_nan=False)
|
|
tmp_path = output_path.with_suffix(output_path.suffix + ".tmp")
|
|
with open(tmp_path, "w", encoding="utf-8") as handle:
|
|
handle.write(serialized)
|
|
handle.flush()
|
|
os.fsync(handle.fileno())
|
|
os.replace(tmp_path, output_path)
|
|
return output_path
|
|
|
|
|
|
__all__ = ["ConfigWriter", "parse_combos_from_text"]
|