"""Small, dependency-free persistence primitives for dashboard state.""" from __future__ import annotations import json import os import tempfile import threading from contextlib import contextmanager from datetime import datetime, timezone from pathlib import Path from typing import Any, Iterator try: import fcntl except ImportError: # pragma: no cover - Windows fallback uses the process lock fcntl = None _LOCKS: dict[str, threading.RLock] = {} _LOCKS_GUARD = threading.Lock() _METADATA_KEYS = {"observed_at", "source", "stale", "last_error"} def _thread_lock(path: Path) -> threading.RLock: key = str(path.resolve()) with _LOCKS_GUARD: return _LOCKS.setdefault(key, threading.RLock()) @contextmanager def file_lock(path: str | os.PathLike[str]) -> Iterator[None]: """Serialize readers/writers across threads and, on POSIX, processes.""" target = Path(path) target.parent.mkdir(parents=True, exist_ok=True) lock_path = target.with_name(f".{target.name}.lock") with _thread_lock(target): with lock_path.open("a+b") as lock_file: if fcntl is not None: fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX) try: yield finally: if fcntl is not None: fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN) def atomic_write_json(path: str | os.PathLike[str], data: Any, *, indent: int = 2) -> None: """Durably replace a JSON file without exposing a partial document.""" target = Path(path) target.parent.mkdir(parents=True, exist_ok=True) with file_lock(target): fd, temporary = tempfile.mkstemp( prefix=f".{target.name}.", suffix=".tmp", dir=target.parent ) try: with os.fdopen(fd, "w", encoding="utf-8") as handle: json.dump(data, handle, indent=indent, default=str) handle.write("\n") handle.flush() os.fsync(handle.fileno()) os.replace(temporary, target) try: directory_fd = os.open(target.parent, os.O_DIRECTORY) try: os.fsync(directory_fd) finally: os.close(directory_fd) except (AttributeError, OSError): pass finally: try: os.unlink(temporary) except FileNotFoundError: pass def load_json(path: str | os.PathLike[str], default: Any = None) -> Any: target = Path(path) if not target.exists(): return default with file_lock(target): try: with target.open(encoding="utf-8") as handle: return json.load(handle) except (OSError, ValueError): return default def _tail_bytes(target: Path, *, line_hint: int, chunk_size: int) -> bytes: with target.open("rb") as handle: handle.seek(0, os.SEEK_END) position = handle.tell() blocks: list[bytes] = [] newlines = 0 while position > 0 and newlines <= line_hint: size = min(chunk_size, position) position -= size handle.seek(position) block = handle.read(size) blocks.append(block) newlines += block.count(b"\n") return b"".join(reversed(blocks)) def load_jsonl_tail( path: str | os.PathLike[str], *, limit: int = 90, chunk_size: int = 8192 ) -> list[dict[str, Any]]: """Read only enough of a JSONL file to return its last valid entries.""" if limit <= 0: return [] target = Path(path) if not target.exists(): return [] with file_lock(target): raw = _tail_bytes(target, line_hint=limit + 8, chunk_size=max(chunk_size, 32)) entries: list[dict[str, Any]] = [] for line in raw.splitlines(): try: value = json.loads(line) except (UnicodeDecodeError, ValueError): continue if isinstance(value, dict): entries.append(value) return entries[-limit:] def _utc_day(timestamp: Any) -> str | None: if not isinstance(timestamp, str): return None try: parsed = datetime.fromisoformat(timestamp.replace("Z", "+00:00")) if parsed.tzinfo is None: parsed = parsed.replace(tzinfo=timezone.utc) return parsed.astimezone(timezone.utc).date().isoformat() except ValueError: return None def append_daily_jsonl(path: str | os.PathLike[str], entry: dict[str, Any]) -> bool: """Append at most one record per UTC day, inspecting only the bounded tail.""" target = Path(path) target.parent.mkdir(parents=True, exist_ok=True) entry_day = _utc_day(entry.get("timestamp")) if entry_day is None: raise ValueError("entry timestamp must be an ISO-8601 datetime") with file_lock(target): if target.exists(): raw = _tail_bytes(target, line_hint=8, chunk_size=4096) for line in reversed(raw.splitlines()): try: previous = json.loads(line) except (UnicodeDecodeError, ValueError): continue if _utc_day(previous.get("timestamp")) == entry_day: return False break payload = (json.dumps(entry, default=str) + "\n").encode("utf-8") fd = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644) try: os.write(fd, payload) os.fsync(fd) finally: os.close(fd) return True def _has_observation(payload: Any) -> bool: if not isinstance(payload, dict): return payload is not None return any(value is not None for key, value in payload.items() if key not in _METADATA_KEYS) def merge_observation( previous: Any, observed: Any, *, source: str, observed_at: str | None = None, error: str | None = None, ) -> dict[str, Any]: """Annotate a fresh observation or retain the last-known-good value as stale.""" if _has_observation(observed): merged = dict(observed) if isinstance(observed, dict) else {"value": observed} merged.update( observed_at=observed_at or datetime.now(timezone.utc).isoformat(), source=source, stale=False, last_error=None, ) return merged merged = dict(previous) if isinstance(previous, dict) else {} merged.update( source=merged.get("source") or source, stale=True, last_error=error or "metric was not observed", ) merged.setdefault("observed_at", None) return merged def onchain_refresh_due( timestamp: Any, *, now: datetime | None = None, ttl_seconds: int = 6 * 60 * 60, ) -> bool: """Return whether the last successful on-chain observation exceeded its TTL.""" if not isinstance(timestamp, str) or not timestamp: return True try: observed = datetime.fromisoformat(timestamp.replace("Z", "+00:00")) if observed.tzinfo is None: observed = observed.replace(tzinfo=timezone.utc) except ValueError: return True current = now or datetime.now(timezone.utc) if current.tzinfo is None: current = current.replace(tzinfo=timezone.utc) return (current.astimezone(timezone.utc) - observed.astimezone(timezone.utc)).total_seconds() >= ttl_seconds