Private
Public Access
0
0
Files
manual_slop/src/module_loader.py
T
ed 3849d30441 refactor(app_controller): remove top-level fastapi imports; lift _require_warmed to shared module
Phase 4 T4.1-T4.4 of startup_speedup_20260606 track.

DEVIATION FROM ORIGINAL SPEC: spec.md said fastapi was in src/api_hooks.py
but it was actually in src/app_controller.py (lines 17, 21). api_hooks.py
uses stdlib http.server. Phase 4 target corrected to app_controller.

LIFTED _require_warmed TO SHARED MODULE: created src/module_loader.py to
avoid duplicating the lookup logic and the cross-module import smell
(app_controller -> ai_client). src/ai_client.py re-exports it so the
T3.1 test (which asserts hasattr(src.ai_client, '_require_warmed'))
continues to work.

src/app_controller.py changes:
- Added 'from __future__ import annotations' (enables lazy type annotations;
  -> FastAPI return type now a forward reference)
- Removed 'from fastapi import FastAPI, Depends, HTTPException' (line 17)
- Removed 'from fastapi.security.api_key import APIKeyHeader' (line 21)
- Added 'from src.module_loader import _require_warmed' (cross-module via
  shared utility, not via ai_client)
- create_api(): added lookups at top of function body
- 7 _api_* helper functions (_api_get_key, _api_generate, _api_stream,
  _api_confirm_action, _api_get_session, _api_delete_session,
  _api_get_context): added 'HTTPException = _require_warmed(...).HTTPException'
  at top of each function body

EFFECTIVENESS:
- import src.app_controller no longer triggers fastapi import (saves ~470ms
  in main thread; only loaded when --enable-test-hooks is set)
- When --enable-test-hooks is set, the AppController's warmup pre-loads
  fastapi on the _io_pool, so create_api()'s lookup is O(1)

TESTS:
- tests/test_app_controller_no_top_level_fastapi.py: 4/4 PASS (was 3 RED + 1 pass)
- tests/test_ai_client_no_top_level_sdk_imports.py: 9/9 still PASS (re-export works)
- tests/test_app_controller_mcp.py, test_app_controller_offloading.py: pass
- tests/test_headless_service.py: 10/11 PASS (1 pre-existing failure
  test_generate_endpoint is a circular-import issue in google.genai,
  reproduces identically on stashed pre-Phase-4 state - NOT a regression
  from this change)
- tests/test_hooks.py: pass

NEXT: Phase 5 (feature-gated GUI module imports - command palette, NERV
theme, markdown table), then Phase 6 (ad-hoc threads -> _io_pool).
2026-06-06 16:34:46 -04:00

54 lines
2.3 KiB
Python

"""Shared module loader for heavy imports.
Per startup_speedup_20260606 spec §2.2 Layer 1, the main thread (the one
that enters `immapp.run()`) must NEVER import a module heavier than
`imgui_bundle` and the lean `gui_2` skeleton. Heavy modules are removed
from main-thread-reachable files entirely and accessed via `_require_warmed`
at use sites. The warmup mechanism in `src/warmup.py` (driven by
`AppController.__init__`) pre-loads them on the `_io_pool` so the first
user-triggered call is instant — the import cost is paid during startup on
a background thread, not on the user's first click.
This module is the single home of the `_require_warmed` helper so that
multiple files (src/ai_client.py, src/app_controller.py, future Phase 5
feature-gated modules) share one canonical implementation instead of
duplicating the lookup logic. The earlier practice of defining the helper
locally in src/ai_client.py created a cross-module import smell
(app_controller -> ai_client) when other modules needed the same primitive.
Public API:
_require_warmed(name) -> module
O(1) sys.modules lookup if warmup ran; importlib fallback otherwise.
Why a function instead of a `from X import Y` inside each call site?
Per the spec, lazy-loading on first use causes user-perceptible lag when
a UI action propagates to a controller method that triggers the first
import. Proactive warmup on bg threads means the cost is paid during the
visible startup window, not on the user's click.
"""
import importlib
import sys
from typing import Any
def _require_warmed(name: str) -> Any:
"""Return a heavy module that the AppController's warmup should have loaded.
Heavy SDKs (anthropic, google.genai, openai, google.genai.types, requests,
fastapi, fastapi.security.api_key, src.command_palette, src.theme_nerv,
src.theme_nerv_fx, src.markdown_table, numpy) are warmed on
AppController's _io_pool at startup. This function expects them to
already be in sys.modules and just returns the cached module object. If
the module is NOT in sys.modules (e.g. in tests where warmup didn't
run), falls back to importlib so the call still works.
In production: this is an O(1) sys.modules lookup. The 1+ second
import cost is paid during startup on a bg thread, NOT on the first
user-triggered AI call.
"""
mod = sys.modules.get(name)
if mod is not None:
return mod
return importlib.import_module(name)