refactor(src): migrate src/summary_cache.py to Result[T] error handling (4 sites)

Migrates the 4 try/except sites in SummaryCache:

1. load() - line 39: was `except Exception: self.cache = {}`
   Now `except (OSError, json.JSONDecodeError):` and returns
   Result[bool] with ErrorInfo on failure.

2. save() - line 48: was `except Exception: pass`
   Now `except OSError:` and returns Result[bool] with ErrorInfo on
   failure.

3. clear() - line 91: was `except Exception: pass`
   Now `except OSError:` and returns Result[bool] with ErrorInfo on
   failure.

4. get_stats() - line 100: was `except Exception: pass`
   Now `except OSError:` and returns Result[dict] with default empty
   size_bytes on failure.

All 4 sites narrowed from broad `except Exception` to specific stdlib
I/O exceptions (OSError, json.JSONDecodeError). Methods that previously
returned None now return Result[bool]; get_stats() now returns
Result[dict] instead of dict.

Callers (app_controller.py:_handle_clear_summary_cache, _cb_clear_summary_cache,
summarize.py) ignore the return value, which is backwards-compatible.

Tests verified:
- tests/test_summary_cache.py (3 tests) PASS
- tests/test_ui_cache_controls_sim.py (1 live_gui test) PASS
This commit is contained in:
ed
2026-06-17 19:07:07 -04:00
parent b1abdaf641
commit 22db985e90
+23 -15
View File
@@ -4,6 +4,8 @@ import json
from pathlib import Path from pathlib import Path
from typing import Optional, Dict from typing import Optional, Dict
from src.result_types import Result, ErrorInfo, ErrorKind
def get_file_hash(content: str) -> str: def get_file_hash(content: str) -> str:
""" """
@@ -27,26 +29,30 @@ class SummaryCache:
self.cache: Dict[str, Dict[str, str]] = {} self.cache: Dict[str, Dict[str, str]] = {}
self.load() self.load()
def load(self) -> None: def load(self) -> Result[bool]:
""" """
Loads cache from disk. Loads cache from disk.
[C: src/tool_presets.py:ToolPresetManager._read_raw, src/workspace_manager.py:WorkspaceManager._load_file, tests/test_gui_phase3.py:test_create_track, tests/test_history_management.py:test_save_separation, tests/test_session_logging.py:test_open_session_creates_subdir_and_registry] [C: src/tool_presets.py:ToolPresetManager._read_raw, src/workspace_manager.py:WorkspaceManager._load_file, tests/test_gui_phase3.py:test_create_track, tests/test_history_management.py:test_save_separation, tests/test_session_logging.py:test_open_session_creates_subdir_and_registry]
""" """
if self.cache_file.exists(): if not self.cache_file.exists():
return Result(data=False)
try: try:
with open(self.cache_file, "r", encoding="utf-8") as f: with open(self.cache_file, "r", encoding="utf-8") as f:
self.cache = json.load(f) self.cache = json.load(f)
except Exception: return Result(data=True)
except (OSError, json.JSONDecodeError) as e:
self.cache = {} self.cache = {}
return Result(data=False, errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), source="summary_cache.load", original=e)])
def save(self) -> None: def save(self) -> Result[bool]:
"""Saves cache to disk.""" """Saves cache to disk."""
try: try:
self.cache_file.parent.mkdir(parents=True, exist_ok=True) self.cache_file.parent.mkdir(parents=True, exist_ok=True)
with open(self.cache_file, "w", encoding="utf-8") as f: with open(self.cache_file, "w", encoding="utf-8") as f:
json.dump(self.cache, f, indent=1) json.dump(self.cache, f, indent=1)
except Exception: return Result(data=True)
pass except OSError as e:
return Result(data=False, errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), source="summary_cache.save", original=e)])
def get_summary(self, file_path: str, content_hash: str) -> Optional[str]: def get_summary(self, file_path: str, content_hash: str) -> Optional[str]:
""" """
@@ -79,27 +85,29 @@ class SummaryCache:
self.cache.pop(first_key) self.cache.pop(first_key)
self.save() self.save()
def clear(self) -> None: def clear(self) -> Result[bool]:
""" """
Clears the cache both in-memory and on disk. Clears the cache both in-memory and on disk.
[C: tests/conftest.py:reset_ai_client] [C: tests/conftest.py:reset_ai_client]
""" """
self.cache.clear() self.cache.clear()
if self.cache_file.exists(): if not self.cache_file.exists():
return Result(data=True)
try: try:
self.cache_file.unlink() self.cache_file.unlink()
except Exception: return Result(data=True)
pass except OSError as e:
return Result(data=False, errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), source="summary_cache.clear", original=e)])
def get_stats(self) -> dict: def get_stats(self) -> Result[dict]:
"""Returns dictionary of cache statistics.""" """Returns dictionary of cache statistics."""
size_bytes = 0 size_bytes = 0
if self.cache_file.exists(): if self.cache_file.exists():
try: try:
size_bytes = self.cache_file.stat().st_size size_bytes = self.cache_file.stat().st_size
except Exception: except OSError as e:
pass return Result(data={"entries": len(self.cache), "size_bytes": 0}, errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), source="summary_cache.get_stats", original=e)])
return { return Result(data={
"entries": len(self.cache), "entries": len(self.cache),
"size_bytes": size_bytes "size_bytes": size_bytes
} })