Private
Public Access
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:
@@ -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 = []
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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})()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.")):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user