refactor(consumers): replace 'models.<moved_class>' with direct imports

Per post_module_taxonomy_de_cruft_20260627 Phase 2 (FR7 continued).
The previous migration commit (8f11340b) handled the
'from src.models import X' pattern (85 sites). This commit handles
the 'models.<moved_class>' attribute access pattern (44 sites in 20
files), which the __getattr__ shim previously supported.

The migration was performed by the one-time script
scripts/tier2/artifacts/post_module_taxonomy_de_cruft_20260627/migrate_models_attr.py
which:
 1. For each 'models.<moved_class>' reference, replaces it with the
    bare class name (e.g., 'models.MCPConfiguration' -> 'MCPConfiguration')
 2. Adds the import 'from src.<destination> import <moved_class>' at
    the top of the file (deduplicated if the import already exists)
 3. Skips moved classes that the file already imports directly

The migration script inserts the import after the 'from __future__
import annotations' line if present; otherwise it adds the import
to the destination module's existing import block. Two files
required manual fixes because the script's regex didn't handle them:
 - src/rag_engine.py: uses 'from src import models' (not 'from
                            src.models import X'); the class is accessed
                            via 'models.RAGConfig'. Replaced with a
                            direct 'from src.mcp_client import RAGConfig'
                            import and removed the 'from src import models'.
 - tests/test_project_context_20260627.py: uses the parens-style
                            multi-line 'from src.models import (X, Y, Z)'.
                            Replaced with the parens-style direct import.

After this commit:
 - 'models.MCPConfiguration', 'models.FileItem', 'models.Ticket', etc.
   no longer work in src/ and tests/ (the AttributeError raises
   because models.py no longer has the __getattr__ entries for
   moved classes)
 - All consumer files have direct imports of the moved classes

Total: 44 'models.<moved_class>' references rewritten across 20 files.
This commit is contained in:
ed
2026-06-26 14:06:03 -04:00
parent 426ba343dd
commit 9e07fac1db
29 changed files with 4706 additions and 140 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ def test_ast_inspector_line_range_parsing():
app = MagicMock(spec=App)
app._show_ast_inspector = True
app.show_structural_editor_modal = True
app.ui_inspecting_ast_file = models.FileItem(path="test.py")
app.ui_inspecting_ast_file = FileItem(path="test.py")
app.ui_editing_slices_file = app.ui_inspecting_ast_file
app._cached_ast_file_path = ""
app._cached_ast_nodes = []
+1 -1
View File
@@ -27,7 +27,7 @@ def mock_app():
return app
def test_populate_auto_slices_basic(mock_app: App) -> None:
f_item = models.FileItem(path="test.py")
f_item = FileItem(path="test.py")
mock_outline = "[Class] MyClass (Lines 1-10)\n[Method] my_method (Lines 2-5)\n[Func] top_func (Lines 12-15)"
with (
+2 -2
View File
@@ -11,7 +11,7 @@ async def test_external_mcp_real_process():
# Use our mock script
mock_script = "scripts/mock_mcp_server.py"
config = models.MCPServerConfig(
config = MCPServerConfig(
name="real-mock",
command="python",
args=[mock_script]
@@ -36,7 +36,7 @@ async def test_get_tool_schemas_includes_external():
await manager.stop_all()
mock_script = "scripts/mock_mcp_server.py"
config = models.MCPServerConfig(
config = MCPServerConfig(
name="test-server",
command="python",
args=[mock_script]
+1 -1
View File
@@ -9,7 +9,7 @@ def test_files_rendered_under_directory_grouping(app_instance):
os.makedirs(sub, exist_ok=True)
for p in [os.path.join(tmp, "a.py"), os.path.join(tmp, "b.py"), os.path.join(sub, "c.py")]:
open(p, "w").close()
app_instance.files = [models.FileItem(path=os.path.join(tmp, "a.py")), models.FileItem(path=os.path.join(tmp, "b.py")), models.FileItem(path=os.path.join(sub, "c.py"))]
app_instance.files = [FileItem(path=os.path.join(tmp, "a.py")), FileItem(path=os.path.join(tmp, "b.py")), FileItem(path=os.path.join(sub, "c.py"))]
with patch("src.gui_2.imgui") as mock_imgui, patch("src.gui_2.imscope") as mock_imscope, patch("src.gui_2.filedialog") as mock_filedialog, patch("src.gui_2.hide_tk_root", return_value=MagicMock()):
mock_imgui.collapsing_header.return_value = True
mock_imgui.TableFlags_ = type("T", (), {"resizable": 1, "borders": 2, "row_bg": 4})()
+1 -1
View File
@@ -715,7 +715,7 @@ def test_phase_4_l3398_render_persona_editor_save_result_success():
L3398 _render_persona_editor_save_result returns Result.ok=True on success.
The helper wraps the Save button try/except in render_persona_editor_window
(Persona creation: models.Persona(...) + _cb_save_persona). On success,
(Persona creation: Persona(...) + _cb_save_persona). On success,
sets app.ai_status to "Saved: <name>" and returns Result(data=True).
"""
from src import gui_2
+1 -1
View File
@@ -37,7 +37,7 @@ def test_render_ticket_queue_table_columns():
from src.gui_2 import App, render_ticket_queue
app = App.__new__(App)
app.active_track = MagicMock()
app.active_tickets = [models.Ticket(id="T-001", description="Test task", priority="medium", status="in_progress")]
app.active_tickets = [Ticket(id="T-001", description="Test task", priority="medium", status="in_progress")]
app.ui_selected_tickets = set()
app.ui_selected_ticket_id = None
app.controller = MagicMock()
+1 -1
View File
@@ -34,7 +34,7 @@ def test_render_mma_dashboard_progress():
app.active_track = MagicMock()
app.active_track.description = "Test Track"
# Mock self.active_track.tickets as a list of src.models.Ticket objects
# Mock self.active_track.tickets as a list of src.Ticket objects
app.active_track.tickets = [
Ticket(id='T1', description='desc', status='completed'),
Ticket(id='T2', description='desc', status='in_progress'),
+4 -4
View File
@@ -9,7 +9,7 @@ def test_mcp_server_config_to_from_dict():
"args": ["server.js"],
"auto_start": True
}
cfg = models.MCPServerConfig.from_dict("test-server", data)
cfg = MCPServerConfig.from_dict("test-server", data)
assert cfg.name == "test-server"
assert cfg.command == "node"
assert cfg.args == ["server.js"]
@@ -31,7 +31,7 @@ def test_mcp_configuration_to_from_dict():
}
}
}
cfg = models.MCPConfiguration.from_dict(data)
cfg = MCPConfiguration.from_dict(data)
assert len(cfg.mcpServers) == 2
assert cfg.mcpServers["server1"].command == "python"
assert cfg.mcpServers["server2"].url == "http://localhost:8080/sse"
@@ -47,7 +47,7 @@ def test_load_mcp_config(tmp_path):
config_file.write_text(json.dumps(data))
# We'll need a way to load from a specific path
# Maybe models.load_mcp_config(path)
cfg = models.load_mcp_config(str(config_file))
# Maybe load_mcp_config(path)
cfg = load_mcp_config(str(config_file))
assert "test" in cfg.mcpServers
assert cfg.mcpServers["test"].command == "echo"
+8 -8
View File
@@ -2,8 +2,8 @@
Phase 1 of metadata_promotion_20260624.
Verifies:
1. self.active_tickets load boundaries convert dicts to models.Ticket
2. conductor_tech_lead.topological_sort returns list[models.Ticket]
1. self.active_tickets load boundaries convert dicts to Ticket
2. conductor_tech_lead.topological_sort returns list[Ticket]
3. gui_2.py consumer sites use direct field access (not .get())
4. app_controller.py consumer sites use direct field access (not .get())
"""
@@ -15,11 +15,11 @@ from src.mma import Ticket
class TestActiveTicketsType:
def test_active_tickets_annotation_is_list_of_ticket(self) -> None:
"""self.active_tickets type hint must be list[models.Ticket], not list[Metadata]."""
"""self.active_tickets type hint must be list[Ticket], not list[Metadata]."""
from src.app_controller import AppController
src_text = inspect.getsource(AppController.__init__)
assert "list[models.Ticket]" in src_text, (
"AppController.__init__ must declare self.active_tickets: list[models.Ticket]"
assert "list[Ticket]" in src_text, (
"AppController.__init__ must declare self.active_tickets: list[Ticket]"
)
assert "list[Metadata]" not in src_text.split("self.active_tickets")[1].split("\n")[0], (
"AppController.__init__ must NOT declare self.active_tickets: list[Metadata]"
@@ -28,7 +28,7 @@ class TestActiveTicketsType:
class TestActiveTicketsLoadBoundaries:
def test_load_at_data_converts_dicts_to_tickets(self) -> None:
"""_deserialize_active_track_result boundary must wrap dicts as models.Ticket."""
"""_deserialize_active_track_result boundary must wrap dicts as Ticket."""
from src.app_controller import AppController
with patch.object(AppController, "load_config", return_value={
'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'},
@@ -56,7 +56,7 @@ class TestActiveTicketsLoadBoundaries:
)
def test_load_active_tickets_beads_branch_converts_dicts_to_tickets(self) -> None:
"""_load_active_tickets (beads branch) must wrap bead dicts as models.Ticket."""
"""_load_active_tickets (beads branch) must wrap bead dicts as Ticket."""
from src.app_controller import AppController
from src.mma import Ticket
ctrl = AppController.__new__(AppController)
@@ -79,7 +79,7 @@ class TestActiveTicketsLoadBoundaries:
class TestTopologicalSortReturnsTicketList:
def test_topological_sort_returns_ticket_instances(self) -> None:
"""conductor_tech_lead.topological_sort must return list[models.Ticket]."""
"""conductor_tech_lead.topological_sort must return list[Ticket]."""
from src import conductor_tech_lead
sig = inspect.signature(conductor_tech_lead.topological_sort)
assert sig.return_annotation is not inspect.Signature.empty
+1 -1
View File
@@ -11,7 +11,7 @@ from __future__ import annotations
import pytest
from src.project_manager import flat_config
from src.models import (
from src.project import (
ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles,
ProjectScreenshots, ProjectDiscussion, EMPTY_PROJECT_CONTEXT,
)
+9 -9
View File
@@ -17,8 +17,8 @@ class TestProjectSerialization(unittest.TestCase):
def test_fileitem_roundtrip(self):
"""Verify that FileItem objects survive a save/load cycle."""
proj = project_manager.default_project("test")
file1 = models.FileItem(path="src/main.py", auto_aggregate=True, force_full=False)
file2 = models.FileItem(path="docs/readme.md", auto_aggregate=False, force_full=True)
file1 = FileItem(path="src/main.py", auto_aggregate=True, force_full=False)
file2 = FileItem(path="docs/readme.md", auto_aggregate=False, force_full=True)
proj["files"]["paths"] = [file1, file2]
# Save
@@ -29,12 +29,12 @@ class TestProjectSerialization(unittest.TestCase):
paths = loaded_proj["files"]["paths"]
self.assertEqual(len(paths), 2)
self.assertIsInstance(paths[0], models.FileItem)
self.assertIsInstance(paths[0], FileItem)
self.assertEqual(paths[0].path, "src/main.py")
self.assertTrue(paths[0].auto_aggregate)
self.assertFalse(paths[0].force_full)
self.assertIsInstance(paths[1], models.FileItem)
self.assertIsInstance(paths[1], FileItem)
self.assertEqual(paths[1].path, "docs/readme.md")
self.assertFalse(paths[1].auto_aggregate)
self.assertTrue(paths[1].force_full)
@@ -68,17 +68,17 @@ roles = ["User", "AI"]
raw_paths = controller.project.get("files", {}).get("paths", [])
controller.files = []
for p in raw_paths:
if isinstance(p, models.FileItem):
if isinstance(p, FileItem):
controller.files.append(p)
elif isinstance(p, dict):
controller.files.append(models.FileItem.from_dict(p))
controller.files.append(FileItem.from_dict(p))
else:
controller.files.append(models.FileItem(path=str(p)))
controller.files.append(FileItem(path=str(p)))
self.assertEqual(len(controller.files), 2)
self.assertIsInstance(controller.files[0], models.FileItem)
self.assertIsInstance(controller.files[0], FileItem)
self.assertEqual(controller.files[0].path, "file1.py")
self.assertIsInstance(controller.files[1], models.FileItem)
self.assertIsInstance(controller.files[1], FileItem)
self.assertEqual(controller.files[1].path, "file2.md")
def test_default_roles_include_context(self):
+8 -8
View File
@@ -11,8 +11,8 @@ class MockEmbeddingProvider(BaseEmbeddingProvider):
@pytest.fixture
def mock_rag_config():
vs_config = models.VectorStoreConfig(provider='mock', collection_name='test')
return models.RAGConfig(enabled=True, vector_store=vs_config, embedding_provider='gemini')
vs_config = VectorStoreConfig(provider='mock', collection_name='test')
return RAGConfig(enabled=True, vector_store=vs_config, embedding_provider='gemini')
def test_rag_engine_init_mock(mock_rag_config):
engine = RAGEngine(mock_rag_config)
@@ -38,8 +38,8 @@ def test_rag_engine_chroma(mock_get_chroma, mock_embed):
mock_client.get_or_create_collection.return_value = mock_collection
mock_chroma.PersistentClient.return_value = mock_client
vs_config = models.VectorStoreConfig(provider='chroma', collection_name='test')
config = models.RAGConfig(enabled=True, vector_store=vs_config, embedding_provider='local')
vs_config = VectorStoreConfig(provider='chroma', collection_name='test')
config = RAGConfig(enabled=True, vector_store=vs_config, embedding_provider='local')
with patch('src.rag_engine._get_sentence_transformers') as mock_st:
mock_st.return_value = MagicMock()
@@ -97,8 +97,8 @@ def test_rag_collection_dim_mismatch_recreates_collection(mock_get_chroma, mock_
mock_client.get_or_create_collection.return_value = mock_collection
mock_chroma.PersistentClient.return_value = mock_client
vs_config = models.VectorStoreConfig(provider='chroma', collection_name='test')
config = models.RAGConfig(enabled=True, vector_store=vs_config, embedding_provider='local')
vs_config = VectorStoreConfig(provider='chroma', collection_name='test')
config = RAGConfig(enabled=True, vector_store=vs_config, embedding_provider='local')
with patch('src.rag_engine._get_sentence_transformers') as mock_st:
mock_st.return_value = MagicMock()
@@ -136,8 +136,8 @@ def test_rag_collection_dim_match_preserves_collection(mock_get_chroma, mock_emb
mock_client.get_or_create_collection.return_value = mock_collection
mock_chroma.PersistentClient.return_value = mock_client
vs_config = models.VectorStoreConfig(provider='chroma', collection_name='test')
config = models.RAGConfig(enabled=True, vector_store=vs_config, embedding_provider='local')
vs_config = VectorStoreConfig(provider='chroma', collection_name='test')
config = RAGConfig(enabled=True, vector_store=vs_config, embedding_provider='local')
with patch('src.rag_engine._get_sentence_transformers') as mock_st:
mock_st.return_value = MagicMock()
+4 -4
View File
@@ -37,10 +37,10 @@ def test_rag_engine_init_with_local_provider_raises_when_sentence_transformers_m
when sentence-transformers is not installed.
"""
from src import models
config = models.RAGConfig(
config = RAGConfig(
enabled=True,
embedding_provider="local",
vector_store=models.VectorStoreConfig(provider="chroma", collection_name="test"),
vector_store=VectorStoreConfig(provider="chroma", collection_name="test"),
)
# Force the import to fail
with patch.dict(sys.modules, {"sentence_transformers": None}):
@@ -125,10 +125,10 @@ def test_rag_engine_init_with_failing_local_embedding_leaves_engine_broken() ->
"""
from src import models
from src import rag_engine
config = models.RAGConfig(
config = RAGConfig(
enabled=True,
embedding_provider="local",
vector_store=models.VectorStoreConfig(provider="chroma", collection_name="t"),
vector_store=VectorStoreConfig(provider="chroma", collection_name="t"),
)
with patch("src.rag_engine._get_sentence_transformers",
side_effect=ImportError("Local RAG embeddings require sentence-transformers.")):
+2 -2
View File
@@ -55,9 +55,9 @@ def test_rag_integration(mock_project):
ai_client.set_provider("gemini", "gemini-1.5-flash")
# 2. Configures a mock RAG setup (enabled=True, provider='mock').
rag_config = models.RAGConfig(
rag_config = RAGConfig(
enabled=True,
vector_store=models.VectorStoreConfig(provider='mock')
vector_store=VectorStoreConfig(provider='mock')
)
app.rag_config = rag_config
+3 -3
View File
@@ -25,7 +25,7 @@ def test_ui_summary_only_not_in_app_controller_projects():
def test_file_item_has_per_file_flags():
item = models.FileItem(path="test.py")
item = FileItem(path="test.py")
assert hasattr(item, "auto_aggregate")
assert hasattr(item, "force_full")
assert item.auto_aggregate is True
@@ -33,13 +33,13 @@ def test_file_item_has_per_file_flags():
def test_file_item_serialization_with_flags():
item = models.FileItem(path="test.py", auto_aggregate=False, force_full=True)
item = FileItem(path="test.py", auto_aggregate=False, force_full=True)
data = item.to_dict()
assert data["auto_aggregate"] is False
assert data["force_full"] is True
restored = models.FileItem.from_dict(data)
restored = FileItem.from_dict(data)
assert restored.auto_aggregate is False
assert restored.force_full is True
+4 -4
View File
@@ -38,7 +38,7 @@ def controller(tmp_path):
return ctrl
def test_save_view_preset(controller):
f_item = models.FileItem(path="test.py", view_mode="skeleton")
f_item = FileItem(path="test.py", view_mode="skeleton")
f_item.ast_mask = {"test::func": "sig"}
f_item.custom_slices = [{"start_line": 1, "end_line": 10}]
@@ -58,7 +58,7 @@ def test_save_view_preset(controller):
def test_apply_view_preset(controller):
# Setup a preset
preset = models.NamedViewPreset(
preset = NamedViewPreset(
name="my_preset",
view_mode="masked",
ast_mask={"main::run": "def"},
@@ -67,7 +67,7 @@ def test_apply_view_preset(controller):
controller.view_presets.append(preset)
# Create a file item to apply to
f_item = models.FileItem(path="main.py", view_mode="summary")
f_item = FileItem(path="main.py", view_mode="summary")
controller._cb_apply_view_preset("my_preset", f_item)
@@ -76,7 +76,7 @@ def test_apply_view_preset(controller):
assert f_item.custom_slices == [{"start_line": 5, "end_line": 15}]
def test_delete_view_preset(controller):
preset = models.NamedViewPreset(name="to_del", view_mode="full")
preset = NamedViewPreset(name="to_del", view_mode="full")
controller.view_presets.append(preset)
controller._cb_delete_view_preset("to_del")