refactor(app_controller): migrate 5 callback sites to Result (batch 1)
Migrated 5 INTERNAL_BROAD_CATCH sites to the data-oriented Result[T] pattern:
1. _handle_custom_callback (L537)
- Narrowed: except Exception -> except (TypeError, ValueError, AttributeError, KeyError, IndexError, RuntimeError, OSError)
- Returns Result[None] via OK on success, Result(data=None, errors=[...]) on failure
- logging.debug added per Heuristic #19
2. _handle_click (L579)
- Narrowed: except Exception -> except (TypeError, ValueError, AttributeError, KeyError, IndexError, RuntimeError)
- Preserves the no-arg fallback (func()) behavior
- Returns Result[None] on success/failure
3. cb_load_prior_log inner (L2046) - bare except in json.dumps
- Narrowed: bare except -> except (TypeError, ValueError)
- Added logging.debug for tool_calls serialization failure
- Preserves the [TOOL CALLS PRESENT] fallback
4. cb_load_prior_log inner (L2068) - bare except in datetime parsing
- Narrowed: bare except -> except (ValueError, TypeError, KeyError, IndexError)
- Added logging.debug for first_ts parse failure
- Preserves the time.time() fallback
5. cb_load_prior_log outer (L2081) - except Exception
- Narrowed: except Exception -> except (OSError, IOError, json.JSONDecodeError, ValueError, TypeError, KeyError, AttributeError)
- Returns Result[None] with ErrorInfo; preserves the ai_status set + early return
- State mutations after the try block are still skipped on error (same as before)
Test impact: 5 new test_app_controller_result tests verify the contract.
tier-1-unit-core: 885 passed (was 883, +2 from earlier Phase 1); 1 expected
failure (test_app_controller_does_not_use_broad_except) will pass after
all 32 sites are migrated across Phases 2-4.
Refs: spec.md FR1, plan.md Task 2.2
Refs: 26e57577 (Phase 1 regression fix on the same file)
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import json
|
||||
import subprocess
|
||||
r = subprocess.run(['uv', 'run', 'python', 'scripts/audit_exception_handling.py', '--json'], capture_output=True, text=True)
|
||||
data = json.loads(r.stdout)
|
||||
app = [f for f in data['files'] if 'app_controller' in f.get('filename', '')][0]
|
||||
findings = app['findings']
|
||||
broad = [f for f in findings if f.get('category') == 'INTERNAL_BROAD_CATCH']
|
||||
# Batch 1: L539, L581, L2048, L2070, L2083
|
||||
for line_num in [539, 581, 2048, 2070, 2083]:
|
||||
matches = [f for f in broad if f.get('line') == line_num]
|
||||
if matches:
|
||||
f = matches[0]
|
||||
ctx = f.get('context', '')
|
||||
print(f'=== L{line_num} (category: {f.get("category", "")}, kind: {f.get("kind", "")}) ===')
|
||||
print(f' context: {ctx[:300]}')
|
||||
print()
|
||||
+39
-13
@@ -38,7 +38,7 @@ from src import shell_runner
|
||||
from src import theme_2 as theme
|
||||
from src import thinking_parser
|
||||
from src import tool_presets
|
||||
from src.result_types import Result, ErrorInfo, ErrorKind
|
||||
from src.result_types import Result, ErrorInfo, ErrorKind, OK
|
||||
|
||||
from src.context_presets import ContextPresetManager
|
||||
from src.file_cache import ASTParser
|
||||
@@ -529,17 +529,24 @@ def _handle_show_track_proposal(controller: 'AppController', task: dict):
|
||||
controller.proposed_tracks = task.get("payload", [])
|
||||
controller._show_track_proposal_modal = True
|
||||
|
||||
def _handle_custom_callback(controller: 'AppController', task: dict):
|
||||
def _handle_custom_callback(controller: 'AppController', task: dict) -> Result[None]:
|
||||
"""[SDM: AppController._handle_custom_callback]"""
|
||||
cb = task.get("callback")
|
||||
args = task.get("args", [])
|
||||
if callable(cb):
|
||||
try:
|
||||
cb(*args)
|
||||
except Exception as e:
|
||||
print(f"Error in direct custom callback: {e}")
|
||||
except (TypeError, ValueError, AttributeError, KeyError, IndexError, RuntimeError, OSError) as e:
|
||||
logging.getLogger(__name__).debug("custom callback error: %s", e, extra={"source": "app_controller._handle_custom_callback"})
|
||||
return Result(data=None, errors=[ErrorInfo(
|
||||
kind=ErrorKind.INTERNAL,
|
||||
message=str(e),
|
||||
source="app_controller._handle_custom_callback",
|
||||
original=e,
|
||||
)])
|
||||
elif cb in controller._predefined_callbacks:
|
||||
controller._predefined_callbacks[cb](*args)
|
||||
return OK
|
||||
|
||||
def _handle_set_value(controller: 'AppController', task: dict):
|
||||
"""[SDM: AppController._handle_set_value]"""
|
||||
@@ -562,7 +569,7 @@ def _handle_set_value(controller: 'AppController', task: dict):
|
||||
new_dict = {**current, key: value}
|
||||
setattr(controller, attr_name, new_dict)
|
||||
|
||||
def _handle_click(controller: 'AppController', task: dict):
|
||||
def _handle_click(controller: 'AppController', task: dict) -> Result[None]:
|
||||
"""[SDM: AppController._handle_click]"""
|
||||
item = task.get("item")
|
||||
user_data = task.get("user_data")
|
||||
@@ -578,8 +585,19 @@ def _handle_click(controller: 'AppController', task: dict):
|
||||
func(user_data=user_data)
|
||||
else:
|
||||
func()
|
||||
except Exception:
|
||||
func()
|
||||
return OK
|
||||
except (TypeError, ValueError, AttributeError, KeyError, IndexError, RuntimeError) as e:
|
||||
logging.getLogger(__name__).debug("click handler error, trying no-arg fallback: %s", e, extra={"source": "app_controller._handle_click"})
|
||||
try:
|
||||
func()
|
||||
except (TypeError, ValueError, AttributeError, KeyError, IndexError, RuntimeError) as e2:
|
||||
return Result(data=None, errors=[ErrorInfo(
|
||||
kind=ErrorKind.INTERNAL,
|
||||
message=str(e2),
|
||||
source="app_controller._handle_click",
|
||||
original=e2,
|
||||
)])
|
||||
return OK
|
||||
|
||||
def _handle_drag(controller: 'AppController', task: dict):
|
||||
"""[SDM: AppController._handle_drag]"""
|
||||
@@ -1999,7 +2017,7 @@ class AppController:
|
||||
if not old_call['result']:
|
||||
old_call['result'] = output
|
||||
break
|
||||
|
||||
|
||||
if kind == 'response' and 'usage' in payload:
|
||||
u = payload['usage']
|
||||
for k in ['input_tokens', 'output_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens', 'total_tokens']:
|
||||
@@ -2014,7 +2032,7 @@ class AppController:
|
||||
'output': u.get('output_tokens', 0) or 0,
|
||||
'model': entry.get('model', 'unknown')
|
||||
})
|
||||
|
||||
|
||||
if kind == "history_add":
|
||||
content = payload.get("content", payload.get("text", payload.get("message", "")))
|
||||
content = _resolve_log_ref(content, session_dir)
|
||||
@@ -2045,7 +2063,8 @@ class AppController:
|
||||
content += f"\n\n[TOOL CALLS]\n{tc_str}"
|
||||
else:
|
||||
content = f"[TOOL CALLS]\n{tc_str}"
|
||||
except:
|
||||
except (TypeError, ValueError) as e:
|
||||
logging.getLogger(__name__).debug("tool_calls json serialization failed: %s", e, extra={"source": "app_controller.cb_load_prior_log.tool_calls"})
|
||||
if content:
|
||||
content += f"\n\n[TOOL CALLS PRESENT]"
|
||||
else:
|
||||
@@ -2067,9 +2086,15 @@ class AppController:
|
||||
})
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
except Exception as e:
|
||||
except (OSError, IOError, json.JSONDecodeError, ValueError, TypeError, KeyError, AttributeError) as e:
|
||||
logging.getLogger(__name__).debug("log load error: %s", e, extra={"source": "app_controller.cb_load_prior_log"})
|
||||
self.ai_status = f"log load error: {e}"
|
||||
return
|
||||
return Result(data=None, errors=[ErrorInfo(
|
||||
kind=ErrorKind.INTERNAL,
|
||||
message=str(e),
|
||||
source="app_controller.cb_load_prior_log",
|
||||
original=e,
|
||||
)])
|
||||
|
||||
self.session_usage = new_usage
|
||||
self.mma_tier_usage = new_mma_usage
|
||||
@@ -2080,7 +2105,8 @@ class AppController:
|
||||
first_ts = new_token_history[0]['time']
|
||||
dt = datetime.datetime.strptime(first_ts, '%Y-%m-%dT%H:%M:%S')
|
||||
self._session_start_time = dt.timestamp()
|
||||
except:
|
||||
except (ValueError, TypeError, KeyError, IndexError) as e:
|
||||
logging.getLogger(__name__).debug("token_history first_ts parse failed: %s", e, extra={"source": "app_controller.cb_load_prior_log.token_history"})
|
||||
self._session_start_time = time.time()
|
||||
self.prior_session_entries = entries
|
||||
self.prior_disc_entries = disc_entries
|
||||
|
||||
Reference in New Issue
Block a user