From 2a660cb8fbf6004d8a6addd9a85a9506f229cbf3 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 18:34:21 -0700 Subject: [PATCH] refactor(hermes_state): reuse utils.safe_json_loads, one tolerant topic reader, one registry path resolver --- hermes_state_registry.py | 24 ++++++++++++++---------- hermes_state_schema.py | 27 +++++++++++---------------- hermes_state_telegram.py | 13 +++++++------ 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/hermes_state_registry.py b/hermes_state_registry.py index 645c225adf..cb6276d1dc 100644 --- a/hermes_state_registry.py +++ b/hermes_state_registry.py @@ -75,6 +75,17 @@ def _teardown(db: "SessionDB") -> None: logger.debug("Error closing shared SessionDB", exc_info=True) +def _db_path_of(db: "SessionDB") -> Optional[Path]: + """``Path(db.db_path)`` or None when absent/unconvertible.""" + path = getattr(db, "db_path", None) + if path is None: + return None + try: + return Path(path) + except (TypeError, ValueError): + return None + + def _finish_opening(path: Path, opening: threading.Event) -> None: """Drop the per-path construction marker and wake waiters (caller holds _lock).""" if _opening.get(path) is opening: @@ -162,13 +173,9 @@ def release(db: "SessionDB") -> bool: with _lock: generation = _retired.get(key) if generation is None: - path = getattr(db, "db_path", None) + path = _db_path_of(db) if path is None: return False - try: - path = Path(path) - except (TypeError, ValueError): - return False generation = _generations.get(path) if generation is None or generation.db is not db: # Not shared (bare SessionDB()); the caller owns close(). @@ -179,12 +186,9 @@ def release(db: "SessionDB") -> bool: if generation.retired: _retired.pop(key, None) else: - path = getattr(db, "db_path", None) + path = _db_path_of(db) if path is not None: - try: - _generations.pop(Path(path), None) - except (TypeError, ValueError): - pass + _generations.pop(path, None) # Teardown OUTSIDE the lock: stopping the token writer, WAL checkpoint and read-pool # drain must not block acquisition for every other state.db. if needs_teardown: diff --git a/hermes_state_schema.py b/hermes_state_schema.py index 64f75ec7b4..506ba60710 100644 --- a/hermes_state_schema.py +++ b/hermes_state_schema.py @@ -18,6 +18,7 @@ from typing import Dict, List, Optional, Sequence from hermes_constants import get_hermes_home from hermes_startup_watchdog import report_startup_progress +from utils import safe_json_loads from hermes_state_common import ( DEFERRED_INDEX_SQL, FTS_CJK_STALE_KEY, FTS_REBUILD_DEFERRAL_KEY, FTS_STALE_KEY, FTS_SQL, FTS_STORAGE_VERSION, FTS_TRIGRAM_SQL, LEGACY_FTS_SQL, LEGACY_FTS_TRIGRAM_SQL, SCHEMA_SQL, @@ -356,12 +357,12 @@ class SessionSchemaMixin: row = cursor.execute( "SELECT value FROM state_meta WHERE key = ? LIMIT 1", (FTS_REBUILD_DEFERRAL_KEY,), ).fetchone() - if row: - parsed = json.loads(row[0]) - if isinstance(parsed, dict): - record = parsed - except (sqlite3.Error, TypeError, ValueError, json.JSONDecodeError): - record = {} + except sqlite3.Error: + row = None + if row: + parsed = safe_json_loads(row[0]) + if isinstance(parsed, dict): + record = parsed try: first_seen = float(record.get("first_seen", now)) attempts = int(record.get("attempts", 0)) + 1 @@ -570,17 +571,11 @@ class SessionSchemaMixin: from hermes_constants import get_hermes_home as _home cache_path = _home() / "cache" / "schema_columns.json" blob = json.loads(cache_path.read_text(encoding="utf-8")) - if ( - isinstance(blob, dict) - and blob.get("schema_hash") == schema_hash - and isinstance(blob.get("tables"), dict) + tables = blob.get("tables") if isinstance(blob, dict) and blob.get("schema_hash") == schema_hash else None + if isinstance(tables, dict) and all( + isinstance(cols, dict) and all(isinstance(v, str) for v in cols.values()) for cols in tables.values() ): - tables = blob["tables"] - if all( - isinstance(cols, dict) and all(isinstance(v, str) for v in cols.values()) - for cols in tables.values() - ): - return tables + return tables except Exception: pass # missing/corrupt cache → recompute below diff --git a/hermes_state_telegram.py b/hermes_state_telegram.py index 6f77fed620..f413943bc7 100644 --- a/hermes_state_telegram.py +++ b/hermes_state_telegram.py @@ -103,17 +103,18 @@ class SessionTelegramTopicsMixin: def _topic_read_one(self, sql: str, params, default=None): """``fetchone`` that treats an unmigrated table as *default*.""" - try: - return self._read_one(sql, params) - except sqlite3.OperationalError: - return default + return self._topic_read(self._read_one, sql, params, default) def _topic_read_all(self, sql: str, params) -> list: """``fetchall`` that treats an unmigrated table as no rows.""" + return self._topic_read(self._read_all, sql, params, []) + + @staticmethod + def _topic_read(reader, sql: str, params, default): try: - return self._read_all(sql, params) + return reader(sql, params) except sqlite3.OperationalError: - return [] + return default def apply_telegram_topic_migration(self) -> None: """Create Telegram DM topic-mode tables on explicit /topic opt-in. Deliberately NOT