From 1fbd99d52cc280569ce117f4afc981a2dc48d5b7 Mon Sep 17 00:00:00 2001 From: agessaman Date: Tue, 3 Mar 2026 16:20:52 -0800 Subject: [PATCH] Add JSON serialization support for companion preferences - Introduced a new utility function, _to_json_safe, to ensure companion preferences are JSON-serializable, handling various data types including enums and dataclasses. - Updated the RepeaterCompanionBridge to use the new serialization method when saving preferences to SQLite. - Modified SQLiteHandler to ensure companion_hash is consistently converted to a string before database operations. - Enhanced error handling for preference persistence in the RepeaterCompanionBridge. --- repeater/companion/bridge.py | 23 ++++++++++++++++++++- repeater/data_acquisition/sqlite_handler.py | 3 ++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/repeater/companion/bridge.py b/repeater/companion/bridge.py index f24ddb1..812e77b 100644 --- a/repeater/companion/bridge.py +++ b/repeater/companion/bridge.py @@ -10,6 +10,7 @@ from __future__ import annotations import dataclasses import logging +from enum import Enum from typing import Any, Callable, Optional from pymc_core.companion import CompanionBridge @@ -17,6 +18,23 @@ from pymc_core.companion import CompanionBridge logger = logging.getLogger("RepeaterCompanionBridge") +def _to_json_safe(value: Any) -> Any: + """Convert a value to a JSON-serializable form (avoids TypeError from enums, bytes, etc.).""" + if value is None or isinstance(value, (bool, int, float, str)): + return value + if isinstance(value, Enum): + return value.value + if isinstance(value, bytes): + return value.hex() + if isinstance(value, (list, tuple)): + return [_to_json_safe(v) for v in value] + if isinstance(value, dict): + return {k: _to_json_safe(v) for k, v in value.items()} + if dataclasses.is_dataclass(value) and not isinstance(value, type): + return {f.name: _to_json_safe(getattr(value, f.name)) for f in dataclasses.fields(value)} + return value + + class RepeaterCompanionBridge(CompanionBridge): """CompanionBridge that persists and loads prefs (full NodePrefs) via SQLite JSON blob.""" @@ -57,7 +75,10 @@ class RepeaterCompanionBridge(CompanionBridge): return try: prefs_dict = dataclasses.asdict(self.prefs) - self._sqlite_handler.companion_save_prefs(self._companion_hash, prefs_dict) + prefs_safe = _to_json_safe(prefs_dict) + self._sqlite_handler.companion_save_prefs( + str(self._companion_hash), prefs_safe + ) except Exception as e: logger.warning("Failed to persist companion prefs: %s", e) diff --git a/repeater/data_acquisition/sqlite_handler.py b/repeater/data_acquisition/sqlite_handler.py index 4701252..b8aa4b0 100644 --- a/repeater/data_acquisition/sqlite_handler.py +++ b/repeater/data_acquisition/sqlite_handler.py @@ -1833,6 +1833,7 @@ class SQLiteHandler: """Persist prefs for a companion as JSON. Upserts by companion_hash.""" try: prefs_json = json.dumps(prefs) + key = str(companion_hash) if companion_hash is not None else "" with sqlite3.connect(self.sqlite_path) as conn: conn.execute( """ @@ -1840,7 +1841,7 @@ class SQLiteHandler: VALUES (?, ?) ON CONFLICT(companion_hash) DO UPDATE SET prefs_json = excluded.prefs_json """, - (companion_hash, prefs_json), + (key, prefs_json), ) conn.commit() return True