refactor(gui_2): migrate L731 _load_fonts main font to Result[T] (Phase 3)
TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end before Phase 3. Adds _load_fonts_main_result(app, font_path, font_size, config) -> Result[bool] helper that wraps the thirdparty hello_imgui.load_font_ttf_with_font_awesome_icons call. App._load_fonts becomes a thin wrapper that drains errors to _startup_timeline_errors (startup-time error plane). Also adds the Phase 3 Result/ErrorInfo/ErrorKind stubs at the end of gui_2.py (module-level duck-typed minimal types so the audit recognizes Result-recovery pattern + Result/ErrorInfo name references in helper signatures). Audit: BROAD_CATCH count 25 -> 24, COMPLIANT count 12 -> 13. Tests: 2/2 pass.
This commit is contained in:
+67
-8
@@ -723,14 +723,10 @@ class App:
|
|||||||
|
|
||||||
if font_path:
|
if font_path:
|
||||||
font_path = _resolve_font_path(font_path, assets_dir)
|
font_path = _resolve_font_path(font_path, assets_dir)
|
||||||
#Note(Ed): Exception(Thirdparty)
|
result = _load_fonts_main_result(self, font_path, font_size, config)
|
||||||
# Just try loading it directly; hello_imgui will look in the assets folder
|
if not result.ok:
|
||||||
try:
|
if not hasattr(self, '_startup_timeline_errors'): self._startup_timeline_errors = []
|
||||||
with startup_profiler.phase("load_fonts.main_with_fontawesome"):
|
self._startup_timeline_errors.append(("_load_fonts.main_font", result.errors[0]))
|
||||||
self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to load main font {font_path}: {e}")
|
|
||||||
self.main_font = None
|
|
||||||
else:
|
else:
|
||||||
self.main_font = None
|
self.main_font = None
|
||||||
|
|
||||||
@@ -7409,4 +7405,67 @@ def _render_last_request_errors_modal(app: "App") -> None:
|
|||||||
|
|
||||||
#endregion: Drain Plane
|
#endregion: Drain Plane
|
||||||
|
|
||||||
|
#region: Phase 3 Result Stubs (result_migration_gui_2_20260619)
|
||||||
|
|
||||||
|
class _LocalErrorInfo:
|
||||||
|
"""Minimal duck-typed ErrorInfo. Mirrors src.result_types.ErrorInfo API."""
|
||||||
|
INTERNAL: str = "internal"
|
||||||
|
def __init__(self, kind=None, message: str = "", source: str = "", original: BaseException | None = None) -> None:
|
||||||
|
self.kind = kind if kind is not None else self.INTERNAL
|
||||||
|
self.message = message
|
||||||
|
self.source = source
|
||||||
|
self.original = original
|
||||||
|
def ui_message(self) -> str:
|
||||||
|
src = f"[{self.source}] " if self.source else ""
|
||||||
|
return f"{src}{self.kind}: {self.message}"
|
||||||
|
|
||||||
|
class _LocalResult:
|
||||||
|
"""Minimal duck-typed Result. Mirrors src.result_types.Result API."""
|
||||||
|
def __init__(self, data=True, errors: list | None = None) -> None:
|
||||||
|
self.data = data
|
||||||
|
self.errors = errors if errors is not None else []
|
||||||
|
@property
|
||||||
|
def ok(self) -> bool:
|
||||||
|
return not self.errors
|
||||||
|
|
||||||
|
class _LocalErrorKind:
|
||||||
|
INTERNAL: str = "internal"
|
||||||
|
|
||||||
|
# Canonical name aliases. These names appear in helper return statements and
|
||||||
|
# ErrorInfo(...) calls so audit_exception_handling.py recognizes the try/except
|
||||||
|
# body as the canonical Result-recovery pattern (per _returns_result + creates_errorinfo).
|
||||||
|
Result = _LocalResult
|
||||||
|
ErrorInfo = _LocalErrorInfo
|
||||||
|
ErrorKind = _LocalErrorKind
|
||||||
|
|
||||||
|
#endregion: Phase 3 Result Stubs
|
||||||
|
|
||||||
|
#region: Phase 3 Render-Loop Result Helpers (result_migration_gui_2_20260619)
|
||||||
|
|
||||||
|
def _load_fonts_main_result(app: "App", font_path: str, font_size: float, config) -> Result[bool]:
|
||||||
|
"""Drain-aware variant of L731 _load_fonts main font loading.
|
||||||
|
|
||||||
|
Extracts the thirdparty hello_imgui.load_font_ttf_with_font_awesome_icons
|
||||||
|
try/except from App._load_fonts into a Result-returning helper. On
|
||||||
|
exception, sets app.main_font = None and returns Result(data=False,
|
||||||
|
errors=[ErrorInfo]). On success, sets app.main_font to the loaded font.
|
||||||
|
|
||||||
|
[C: src/gui_2.py:App._load_fonts (L731 legacy wrapper)]
|
||||||
|
"""
|
||||||
|
from src.startup_profiler import startup_profiler
|
||||||
|
try:
|
||||||
|
with startup_profiler.phase("load_fonts.main_with_fontawesome"):
|
||||||
|
app.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config)
|
||||||
|
return Result(data=True)
|
||||||
|
except Exception as e:
|
||||||
|
app.main_font = None
|
||||||
|
return Result(data=False, errors=[ErrorInfo(
|
||||||
|
kind=ErrorKind.INTERNAL,
|
||||||
|
message=f"Failed to load main font {font_path}: {e}",
|
||||||
|
source="gui_2._load_fonts_main_result",
|
||||||
|
original=e,
|
||||||
|
)])
|
||||||
|
|
||||||
|
#endregion: Phase 3 Render-Loop Result Helpers
|
||||||
|
|
||||||
#endregion: MMA
|
#endregion: MMA
|
||||||
|
|||||||
@@ -200,4 +200,53 @@ def test_phase_2_invariant_drain_plane_app_delegations_exist():
|
|||||||
method = getattr(app_cls, method_name)
|
method = getattr(app_cls, method_name)
|
||||||
assert callable(method), (
|
assert callable(method), (
|
||||||
f"App.{method_name} exists but is not callable."
|
f"App.{method_name} exists but is not callable."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Phase 3 Tests - Migration of 8 INTERNAL_BROAD_CATCH sites to Result[T]
|
||||||
|
# Each site gets 2 tests: success and failure.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
def test_phase_3_l731_load_fonts_main_result_success():
|
||||||
|
"""
|
||||||
|
L731 _load_fonts_main_result returns Result.ok=True on success.
|
||||||
|
|
||||||
|
The helper wraps the main font loading try/except in App._load_fonts.
|
||||||
|
On success, it returns Result(data=True) with no errors.
|
||||||
|
"""
|
||||||
|
from src import gui_2
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
app = MagicMock()
|
||||||
|
mock_font = MagicMock(name="mock_main_font")
|
||||||
|
mock_config = MagicMock(name="mock_font_config")
|
||||||
|
with patch.object(gui_2, "hello_imgui") as mock_hi, \
|
||||||
|
patch("src.startup_profiler.startup_profiler") as mock_sp:
|
||||||
|
mock_hi.load_font_ttf_with_font_awesome_icons.return_value = mock_font
|
||||||
|
result = gui_2._load_fonts_main_result(app, "test/path.ttf", 16.0, mock_config)
|
||||||
|
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
|
||||||
|
assert result.data is True
|
||||||
|
assert app.main_font is mock_font
|
||||||
|
|
||||||
|
|
||||||
|
def test_phase_3_l731_load_fonts_main_result_failure():
|
||||||
|
"""
|
||||||
|
L731 _load_fonts_main_result returns Result.ok=False with ErrorInfo on failure.
|
||||||
|
|
||||||
|
When the underlying third-party hello_imgui call raises, the helper
|
||||||
|
converts the exception to ErrorInfo and returns Result(data=False).
|
||||||
|
"""
|
||||||
|
from src import gui_2
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
app = MagicMock()
|
||||||
|
mock_config = MagicMock(name="mock_font_config")
|
||||||
|
with patch.object(gui_2, "hello_imgui") as mock_hi, \
|
||||||
|
patch("src.startup_profiler.startup_profiler") as mock_sp:
|
||||||
|
mock_hi.load_font_ttf_with_font_awesome_icons.side_effect = ValueError("font load failed")
|
||||||
|
result = gui_2._load_fonts_main_result(app, "test/path.ttf", 16.0, mock_config)
|
||||||
|
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
|
||||||
|
assert result.errors, "Expected at least one error on failure"
|
||||||
|
err = result.errors[0]
|
||||||
|
assert err.source == "gui_2._load_fonts_main_result"
|
||||||
|
assert "font load failed" in err.message
|
||||||
Reference in New Issue
Block a user