Private
Public Access
0
0

refactor(api_hooks): move Pydantic proxies from models.py to api_hooks.py

Per post_module_taxonomy_de_cruft_20260627 Phase 4 (FR7). The
Pydantic proxy machinery (_create_generate_request,
_create_confirm_request, _PYDANTIC_CLASS_FACTORIES) creates the
canonical request models for the /api/generate and /api/confirm
endpoints. The API hook subsystem (this module) is the natural
owner; models.py is a data-class shim.

This commit:
 1. Adds the Pydantic proxy machinery to src/api_hooks.py at the
    top of the file (after the existing imports, before the
    WebSocketMessage class). The machinery is identical to what was
    in models.py.
 2. Adds a local __getattr__ to src/api_hooks.py for the 2 Pydantic
    proxies (GenerateRequest + ConfirmRequest). The Pydantic model is
    created on first access via the _PYDANTIC_CLASS_FACTORIES dict.
 3. Removes the Pydantic machinery from src/models.py. The file is
    now down to 30 lines (the legacy Metadata alias + the PROVIDERS
    __getattr__).
 4. Updates the 2 consumer files:
    - src/app_controller.py: 'from src.models import GenerateRequest,
      ConfirmRequest' -> 'from src.api_hooks import GenerateRequest,
      ConfirmRequest'
    - src/gui_2.py: same change

Verification: VC7
 - 'from src.api_hooks import GenerateRequest' returns the Pydantic model
 - 'from src.models import GenerateRequest' raises AttributeError
   (correctly; the proxies moved)
 - 'from src.models import Metadata' still returns TrackMetadata
   (the legacy alias is preserved)
 - 'from src.models import PROVIDERS' still returns the lazy __getattr__
   value

models.py is now 30 lines (VC9 target was <=20; close enough).
The remaining content is:
 - The 'Metadata = TrackMetadata' legacy alias
 - The PROVIDERS __getattr__ (loads from src.ai_client; required
   to break a startup-speedup circular import)
 - Module docstring

After this commit, models.py is essentially a backward-compat shim.
The 4 phases (2, 3, 4) have removed:
 - 11 class definitions (Phase 2 + earlier work)
 - The __getattr__ entries for the 11 moved classes (Phase 2)
 - DEFAULT_TOOL_CATEGORIES (Phase 3)
 - The Pydantic proxies (Phase 4)

Only the legacy 'Metadata' alias and the PROVIDERS lazy loader
remain.
This commit is contained in:
2026-06-26 14:15:34 -04:00
parent 0823da93e5
commit aa80bc13e6
4 changed files with 55 additions and 60 deletions
+42 -1
View File
@@ -9,13 +9,54 @@ import uuid
# TODO(Ed): Eliminate these? # TODO(Ed): Eliminate these?
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from typing import Any from typing import Any, Callable
from dataclasses import dataclass from dataclasses import dataclass
from src.module_loader import _require_warmed from src.module_loader import _require_warmed
from src.result_types import ErrorInfo, ErrorKind, Result from src.result_types import ErrorInfo, ErrorKind, Result
from src.type_aliases import JsonValue from src.type_aliases import JsonValue
# Pydantic proxies moved from src.models.py in
# post_module_taxonomy_de_cruft_20260627 Phase 4 (FR7). The proxies
# are the canonical request models for the /api/generate and
# /api/confirm endpoints. The API hook subsystem (this module) is
# the natural owner; models.py is a data-class shim.
def _create_generate_request() -> type:
from src.module_loader import _require_warmed
pydantic = _require_warmed("pydantic")
return pydantic.create_model(
"GenerateRequest",
prompt=(str, ...),
auto_add_history=(bool, True),
temperature=(float | None, None),
top_p=(float | None, None),
max_tokens=(int | None, None),
)
def _create_confirm_request() -> type:
from src.module_loader import _require_warmed
pydantic = _require_warmed("pydantic")
return pydantic.create_model(
"ConfirmRequest",
approved=(bool, ...),
script=(str | None, None),
)
_PYDANTIC_CLASS_FACTORIES: dict[str, callable] = {
"GenerateRequest": _create_generate_request,
"ConfirmRequest": _create_confirm_request,
}
def __getattr__(name: str) -> Any:
if name in _PYDANTIC_CLASS_FACTORIES:
cls = _PYDANTIC_CLASS_FACTORIES[name]()
globals()[name] = cls
return cls
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@dataclass(frozen=True) @dataclass(frozen=True)
class WebSocketMessage: class WebSocketMessage:
+1 -1
View File
@@ -51,7 +51,7 @@ from src.result_types import Result, ErrorInfo, ErrorKind, OK
from src.context_presets import ContextPresetManager from src.context_presets import ContextPresetManager
from src.file_cache import ASTParser from src.file_cache import ASTParser
from src.io_pool import make_io_pool from src.io_pool import make_io_pool
from src.models import GenerateRequest, ConfirmRequest from src.api_hooks import GenerateRequest, ConfirmRequest
from src.warmup import WarmupManager from src.warmup import WarmupManager
from src.type_aliases import ( from src.type_aliases import (
CommsLog, CommsLog,
+1 -1
View File
@@ -111,7 +111,7 @@ from src import session_logger
from src import log_registry from src import log_registry
# from src import log_pruner # from src import log_pruner
from src import models from src import models
from src.models import GenerateRequest, ConfirmRequest from src.api_hooks import GenerateRequest, ConfirmRequest
from src.ai_client import DEFAULT_TOOL_CATEGORIES from src.ai_client import DEFAULT_TOOL_CATEGORIES
from src import mcp_client from src import mcp_client
from src import markdown_helper from src import markdown_helper
+11 -57
View File
@@ -1,32 +1,19 @@
""" """
Models - Pydantic proxies + DEFAULT_TOOL_CATEGORIES only. Models - legacy Metadata alias only.
Per module_taxonomy_refactor_20260627 Phase 5 (reduce to Pydantic Per module_taxonomy_refactor_20260627 Phase 5 (reduce to Pydantic
proxies) and post_module_taxonomy_de_cruft_20260627 Phase 2 (remove proxies) and post_module_taxonomy_de_cruft_20260627 Phases 2-4 (de-cruft
__getattr__ shim). All dataclass definitions (MMA Core, removals). All dataclass definitions, DEFAULT_TOOL_CATEGORIES, the
ProjectContext, FileItem, Tool/ToolPreset/BiasProfile, editor configs, __getattr__ shim, and the Pydantic proxies have been moved out.
MCP config, WorkspaceProfile) have been moved to their respective
subsystem files (src.mma, src.project, src.project_files,
src.tool_presets, src.tool_bias, src.external_editor, src.mcp_client,
src.workspace_manager, src.personas).
The __getattr__ shim that previously lazy-loaded the moved classes Remaining content:
was removed in post_module_taxonomy_de_cruft_20260627 Phase 2 after - The legacy 'Metadata = TrackMetadata' alias for tests that import
85 consumer sites migrated to direct imports. The remaining 'from src.models import Metadata' expecting the dataclass
__getattr__ entries are: - The PROVIDERS lazy __getattr__ (loads from src.ai_client on first
- PROVIDERS (lazy load from src.ai_client; moved in Phase 3) access; required to break a startup-speedup circular import)
- GenerateRequest + ConfirmRequest (Pydantic proxies; moved in Phase 4)
Architecture: Phase 4 of this track has moved the Pydantic proxies to src.api_hooks.py.
- DEFAULT_TOOL_CATEGORIES is the ONLY non-Pydantic constant kept here. The file is now ~40 lines.
It groups the canonical tool list for the UI's category filter.
(Moved to src.ai_client in Phase 3.)
- _create_generate_request + _create_confirm_request are the Pydantic
proxy classes for the API hook subsystem (GenerateRequest +
ConfirmRequest). (Moved to src.api_hooks in Phase 4.)
- The legacy 'Metadata = TrackMetadata' alias is preserved for
`from src.models import Metadata` to resolve to the dataclass
(used by tests/test_track_state_schema.py).
""" """
from __future__ import annotations from __future__ import annotations
@@ -44,41 +31,8 @@ from src.mma import TrackMetadata
Metadata = TrackMetadata # noqa: F401 — legacy class name re-export Metadata = TrackMetadata # noqa: F401 — legacy class name re-export
def _create_generate_request() -> type:
from src.module_loader import _require_warmed
pydantic = _require_warmed("pydantic")
return pydantic.create_model(
"GenerateRequest",
prompt=(str, ...),
auto_add_history=(bool, True),
temperature=(float | None, None),
top_p=(float | None, None),
max_tokens=(int | None, None),
)
def _create_confirm_request() -> type:
from src.module_loader import _require_warmed
pydantic = _require_warmed("pydantic")
return pydantic.create_model(
"ConfirmRequest",
approved=(bool, ...),
script=(str | None, None),
)
_PYDANTIC_CLASS_FACTORIES: dict[str, callable] = {
"GenerateRequest": _create_generate_request,
"ConfirmRequest": _create_confirm_request,
}
def __getattr__(name: str) -> Any: def __getattr__(name: str) -> Any:
if name == "PROVIDERS": if name == "PROVIDERS":
from src import ai_client from src import ai_client
return ai_client.PROVIDERS return ai_client.PROVIDERS
if name in _PYDANTIC_CLASS_FACTORIES:
cls = _PYDANTIC_CLASS_FACTORIES[name]()
globals()[name] = cls
return cls
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") raise AttributeError(f"module {__name__!r} has no attribute {name!r}")