a2d16541d00b7e10085b2872d96873025ec55eee
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
88fc42bbc0 |
fix(ai_client): use parent package lookup to fix google.genai circular import
The conftest pre-warm workaround added earlier was a TEST INFRASTRUCTURE
patch that did not address the actual problem. The real issue is in the
lazy-import pattern: `_require_warmed("google.genai.types")` triggers
google-genai's broken __init__.py chain in fresh pytest processes.
Per the Phase 3 spec, the correct pattern is:
genai = _require_warmed("google.genai")
types = genai.types
The PARENT package import completes the chain once. Then `.types`
is just an attribute access on the loaded module. No new import
needed at the leaf.
ROOT CAUSE: google-genai's __init__.py does
from .client import Client -> from ._api_client import BaseApiClient
which transitively does `from .types import HttpOptions`. When
google.genai.types is being loaded for the first time, types.py
executes `from ._operations_converters import (...)`. If anything
in that chain triggers the parent __init__.py, the relative
`from .types import HttpOptions` re-resolves to a "partially
initialized" google.genai.types in sys.modules and raises ImportError.
By importing `google.genai` directly (the parent), the entire
__init__.py chain runs to completion BEFORE we ever look up `.types`.
Subsequent access is just attribute lookup, no import.
FIXES (7 sites in src/ai_client.py):
- _gemini_tool_declaration (L651)
- _send_anthropic (L1170)
- _send_gemini (L1422)
- run_tier4_analysis (L2360)
- run_tier4_patch_generation (L2410)
- run_subagent_summarization (L2568)
- run_discussion_compression (L2616)
All changed from `types = _require_warmed("google.genai.types")`
to:
genai = _require_warmed("google.genai")
types = genai.types
ALSO REMOVED:
- conftest.py pre-warm of google.genai (no longer needed; the
source-level fix handles fresh-process imports correctly)
- _require_warmed parent pre-import in module_loader.py (no longer
needed; the convention is to pass top-level package names)
ALSO KEPT (real bug fix from earlier):
- _ensure_gemini_client UnboundLocalError: moved Client() construction
inside the `if _gemini_client is None:` block so `creds` is in scope.
- test_discussion_compression.py: test now mocks _require_warmed
to return a fake requests module with .post() (Phase 3 removed
the top-level `import requests` from ai_client.py).
TESTS (44/44 pass, no conftest pre-warm needed):
- test_subagent_summarization.py: 3/3
- test_tool_access_exclusion.py: 4/4
- test_tier4_interceptor.py: 7/7 (incl. test_gemini_provider_passes_qa_callback_to_run_script)
- test_gui2_mcp.py: 1/1 (test_mcp_tool_call_is_dispatched)
- test_gui_updates.py: 3/3 (incl. test_telemetry_data_updates_correctly)
- test_headless_service.py: 11/11 (incl. test_generate_endpoint)
- test_project_switch_persona_preset.py: 9/9 (incl. test_api_generate_blocked_while_stale)
- test_discussion_compression.py: 4/4 (incl. test_discussion_compression_deepseek)
- test_ai_cache_tracking.py: 2/2 (incl. test_gemini_cache_tracking)
ARCHITECTURAL NOTE: This is the PROPER fix per the Phase 3 spec.
The earlier conftest pre-warm was a workaround that masked the
issue. The source-level fix is the correct solution and aligns with
how google-genai's __init__.py chain expects to be loaded.
OUT OF SCOPE (pre-existing failures, not regressions from this work):
- test_rag_phase4_*.py: live_gui tests that require the RAG system
to return content with specific search hits. Pre-existing.
- test_project_switch_persona_preset.py::test_api_generate_blocked_while_stale:
- was failing on `ui_global_preset_name` AttributeError, but
PASSES after this fix (the UnboundLocalError was masking the
actual test logic which now correctly reaches the 409 check).
|
||
|
|
8c4791d03f |
fix(ai_client,module_loader): pre-existing bugs surfaced by Phase 3 refactor
Three test failures identified by the batched test suite, all rooted
in the Phase 3 lazy-import refactor of src/ai_client.py.
FIX 1: UnboundLocalError in _ensure_gemini_client
- _ensure_gemini_client had a latent bug: creds was assigned inside
`if _gemini_client is None:` but used on the next line. When the
client was already cached, the assignment was skipped and the next
line raised UnboundLocalError. Moved the Client() construction
inside the if block to match creds' scope.
- This affected test_ai_cache_tracking.py and (downstream)
test_gui_updates.py::test_telemetry_data_updates_correctly.
FIX 2: Phase 3 removed top-level `import requests` from ai_client.py.
- test_discussion_compression.py::test_discussion_compression_deepseek
did `patch("src.ai_client.requests.post", ...)` which no longer works.
- Updated the test to mock _require_warmed to return a fake requests
module with `.post()`, matching the new lazy-import pattern.
FIX 3: _require_warmed could not import dotted names like `google.genai.types`
- The google-genai library has a self-referential __init__.py that
does `from .client import Client` which transitively does
`from .types import HttpOptions`. Importing `google.genai.types`
FIRST (before the parent package is fully loaded) hit a "partially
initialized module" circular import.
- Enhanced _require_warmed to pre-import parent packages for dotted
names: walks `name.split(".")` and imports each parent (if not in
sys.modules) before the leaf import. O(n) extra imports per call
on first use; subsequent calls are O(1) sys.modules hit.
TESTS:
- test_ai_cache_tracking.py: 2/2 PASS
- test_discussion_compression.py: 4/4 PASS
- 29/29 PASS across the sampled test files that were failing
(test_subagent_summarization, test_tool_access_exclusion,
test_tier4_interceptor, test_gui2_mcp, test_gui_updates,
test_headless_service)
ARCHITECTURAL NOTE: The _require_warmed enhancement is a small
but important robustness fix. The google-genai library's
__init__.py chain is a known source of fragility; the parent-
pre-import pattern is the recommended workaround.
|
||
|
|
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). |