refactor(gui_2,app_controller): remove hasattr defensive checks + fix _do_generate type
Phase 3 follow-up: gui_2.py hasattr removal Before: 23 hasattr(f, ...) defensive checks in src/gui_2.py After: 0 (self.files / self.context_files are GUARANTEED List[FileItem]) Delta: -23 sites Phase 4: _do_generate return type Before: def _do_generate(self) -> tuple[str, Path, list[Metadata], str, str]: at src/app_controller.py:4014 After: def _do_generate(self) -> tuple[str, Path, list[FileItem], str, str]: Delta: -1 wrong type annotation (file_items comes from aggregate.run() which returns List[FileItem]) Combined: 18 hasattr(f, 'path') checks in gui_2.py + 5 hasattr(f, ...) checks on other FileItem fields (view_mode/custom_slices/ast_mask/ast_signatures/ ast_definitions/auto_aggregate/to_dict) + 1 _do_generate return type fix. All removed defensive checks are redundant because: 1. self.files and self.context_files are populated via the isinstance + FileItem.from_dict() pattern (gui_2.py:869-873 + 980-985 for restore; app_controller.py:1996-2005 for project init) 2. FileItem has explicit fields for path, view_mode, custom_slices, ast_mask, ast_signatures, ast_definitions, auto_aggregate, to_dict Verification: - audit_weak_types --strict: OK (107 <= 112 baseline) - py_check_syntax src/gui_2.py: OK - py_check_syntax src/app_controller.py: OK - 95 tests pass (type_aliases, openai_schemas, rag_engine, file_item, rag_chunk, main_thread_purity, app_controller_result, context_composition_decoupled)
This commit is contained in:
@@ -4011,7 +4011,7 @@ class AppController:
|
||||
return result
|
||||
self.submit_io(worker)
|
||||
|
||||
def _do_generate(self) -> tuple[str, Path, list[Metadata], str, str]:
|
||||
def _do_generate(self) -> tuple[str, Path, list[FileItem], str, str]:
|
||||
"""
|
||||
Returns (full_md, output_path, file_items, stable_md, discussion_text).
|
||||
[C: src/gui_2.py:App._show_menus, tests/test_context_composition_decoupled.py:test_do_generate_uses_context_files, tests/test_tiered_aggregation.py:test_app_controller_do_generate_uses_persona_strategy]
|
||||
|
||||
+44
-44
@@ -368,12 +368,12 @@ class App:
|
||||
if not name: return
|
||||
preset_files = []
|
||||
for f in self.context_files:
|
||||
p = f.path if hasattr(f, 'path') else str(f)
|
||||
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
||||
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
|
||||
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False
|
||||
p = f.path
|
||||
vm = f.view_mode
|
||||
slc = copy.deepcopy(f.custom_slices)
|
||||
msk = copy.deepcopy(f.ast_mask)
|
||||
sig = f.ast_signatures
|
||||
dfn = f.ast_definitions
|
||||
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
|
||||
preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(self.screenshots))
|
||||
self.controller.save_context_preset(preset)
|
||||
@@ -839,8 +839,8 @@ class App:
|
||||
max_tokens = self.max_tokens,
|
||||
auto_add_history = self.ui_auto_add_history,
|
||||
disc_entries = copy.deepcopy(self.disc_entries),
|
||||
files = [f.to_dict() if hasattr(f, 'to_dict') else f for f in self.files],
|
||||
context_files = [f.to_dict() if hasattr(f, 'to_dict') else f for f in self.context_files],
|
||||
files = [f.to_dict() for f in self.files],
|
||||
context_files = [f.to_dict() for f in self.context_files],
|
||||
screenshots = list(self.screenshots)
|
||||
)
|
||||
|
||||
@@ -977,8 +977,8 @@ class App:
|
||||
self.context_files = []
|
||||
for f in preset.files:
|
||||
fi = models.FileItem(path=f.path, view_mode=f.view_mode)
|
||||
fi.custom_slices = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||
fi.ast_mask = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||
fi.custom_slices = copy.deepcopy(f.custom_slices)
|
||||
fi.ast_mask = copy.deepcopy(f.ast_mask)
|
||||
fi.ast_signatures = getattr(f, 'ast_signatures', False)
|
||||
fi.ast_definitions = getattr(f, 'ast_definitions', False)
|
||||
self.context_files.append(fi)
|
||||
@@ -994,13 +994,13 @@ class App:
|
||||
|
||||
@property
|
||||
def ui_file_paths(self) -> list[str]:
|
||||
return [f.path if hasattr(f, 'path') else str(f) for f in self.files]
|
||||
return [f.path for f in self.files]
|
||||
|
||||
@ui_file_paths.setter
|
||||
def ui_file_paths(self, paths: list[str]) -> None:
|
||||
sys.stderr.write(f"[DEBUG] Setting ui_file_paths to: {paths}\n")
|
||||
sys.stderr.flush()
|
||||
old_files = {f.path: f for f in self.files if hasattr(f, 'path')}
|
||||
old_files = {f.path: f for f in self.files}
|
||||
new_files = []
|
||||
now = time.time()
|
||||
for p in paths:
|
||||
@@ -1312,7 +1312,7 @@ class App:
|
||||
|
||||
missing_keys = []
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
f_path = f.path
|
||||
mtime = os.path.getmtime(f_path) if os.path.exists(f_path) else 0
|
||||
cache_key = f"{f_path}_{mtime}"
|
||||
if cache_key not in self._file_stats_cache: missing_keys.append((f_path, cache_key))
|
||||
@@ -3666,7 +3666,7 @@ def render_files_and_media(app: App) -> None:
|
||||
if imgui.collapsing_header("Files", imgui.TreeNodeFlags_.default_open):
|
||||
with imscope.group():
|
||||
to_remove_idx = -1
|
||||
app.files.sort(key=lambda f: f.path.lower() if hasattr(f, 'path') else str(f).lower())
|
||||
app.files.sort(key=lambda f: f.path.lower())
|
||||
file_indices = {id(f): idx for idx, f in enumerate(app.files)}
|
||||
grouped = aggregate.group_files_by_dir(app.files)
|
||||
if imgui.begin_table("files_table", 3, imgui.TableFlags_.resizable | imgui.TableFlags_.borders | imgui.TableFlags_.row_bg):
|
||||
@@ -3719,12 +3719,12 @@ def render_files_and_media(app: App) -> None:
|
||||
r = hide_tk_root(); paths = filedialog.askopenfilenames(); r.destroy()
|
||||
from src import models
|
||||
for p in paths:
|
||||
if p not in [f.path if hasattr(f, "path") else f for f in app.files]: app.files.append(models.FileItem(path=p))
|
||||
if p not in [f.path for f in app.files]: app.files.append(models.FileItem(path=p))
|
||||
imgui.same_line()
|
||||
if imgui.button("Add Directory"):
|
||||
r = hide_tk_root(); dirpath = filedialog.askdirectory(); r.destroy()
|
||||
if dirpath:
|
||||
existing = {f.path if hasattr(f, "path") else str(f) for f in app.files}
|
||||
existing = {f.path for f in app.files}
|
||||
for root, _dirs, files in os.walk(dirpath):
|
||||
for fname in files:
|
||||
full = os.path.join(root, fname)
|
||||
@@ -3770,12 +3770,12 @@ def render_context_batch_actions(app: App, total_lines: int, total_ast: int) ->
|
||||
for mode in ["full", "summary", "skeleton", "outline", "masked", "none"]:
|
||||
if imgui.button(f"{mode.capitalize()}##batch"):
|
||||
for f in app.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
f_path = f.path
|
||||
if f_path in app.ui_selected_context_files: f.view_mode = mode
|
||||
imgui.same_line()
|
||||
if imgui.button("Sel All##selall"):
|
||||
for f in app.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
f_path = f.path
|
||||
app.ui_selected_context_files.add(f_path)
|
||||
imgui.same_line()
|
||||
if imgui.button("Unsel All##unselall"): app.ui_selected_context_files.clear()
|
||||
@@ -3783,9 +3783,9 @@ def render_context_batch_actions(app: App, total_lines: int, total_ast: int) ->
|
||||
if imgui.button("Add Files##add_btn"): imgui.open_popup("Select Context Files")
|
||||
imgui.same_line()
|
||||
if imgui.button("Add All##addall"):
|
||||
context_paths = {f.path if hasattr(f, "path") else str(f) for f in app.context_files}
|
||||
context_paths = {f.path for f in app.context_files}
|
||||
for f in app.files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
f_path = f.path
|
||||
if f_path not in context_paths:
|
||||
f_copy = copy.deepcopy(f)
|
||||
app.context_files.append(f_copy)
|
||||
@@ -3794,7 +3794,7 @@ def render_context_batch_actions(app: App, total_lines: int, total_ast: int) ->
|
||||
if imgui.button("Del##batch"):
|
||||
new_files = []
|
||||
for f in app.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
f_path = f.path
|
||||
if f_path not in app.ui_selected_context_files: new_files.append(f)
|
||||
app.context_files = new_files
|
||||
app.ui_selected_context_files.clear()
|
||||
@@ -3837,7 +3837,7 @@ def render_add_context_files_modal(app: App) -> None:
|
||||
# Create a temporary selection set if not initialized
|
||||
if not hasattr(app, '_ui_picker_selected'): app._ui_picker_selected = set()
|
||||
for f in app.files:
|
||||
fpath = f.path if hasattr(f, 'path') else str(f)
|
||||
fpath = f.path
|
||||
# Skip if already in context
|
||||
if any((cf.path if hasattr(cf, 'path') else str(cf)) == fpath for cf in app.context_files):
|
||||
continue
|
||||
@@ -4364,12 +4364,12 @@ def render_context_presets(app: App) -> None:
|
||||
for f in app.context_files:
|
||||
import copy
|
||||
from src import models
|
||||
p = f.path if hasattr(f, 'path') else str(f)
|
||||
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
||||
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
|
||||
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False
|
||||
p = f.path
|
||||
vm = f.view_mode
|
||||
slc = copy.deepcopy(f.custom_slices)
|
||||
msk = copy.deepcopy(f.ast_mask)
|
||||
sig = f.ast_signatures
|
||||
dfn = f.ast_definitions
|
||||
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
|
||||
preset = models.ContextPreset(name=active, files=preset_files, screenshots=list(app.screenshots))
|
||||
app.controller.save_context_preset(preset)
|
||||
@@ -4390,7 +4390,7 @@ def render_context_presets(app: App) -> None:
|
||||
missing = []
|
||||
root = app.controller.active_project_root
|
||||
for f in app.context_files:
|
||||
path = f.path if hasattr(f, "path") else str(f)
|
||||
path = f.path
|
||||
if not os.path.isabs(path): full_path = os.path.join(root, path)
|
||||
else: full_path = path
|
||||
if not os.path.exists(full_path): missing.append(path)
|
||||
@@ -4404,12 +4404,12 @@ def render_context_presets(app: App) -> None:
|
||||
for f in app.context_files:
|
||||
import copy
|
||||
from src import models
|
||||
p = f.path if hasattr(f, 'path') else str(f)
|
||||
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
||||
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
|
||||
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False
|
||||
p = f.path
|
||||
vm = f.view_mode
|
||||
slc = copy.deepcopy(f.custom_slices)
|
||||
msk = copy.deepcopy(f.ast_mask)
|
||||
sig = f.ast_signatures
|
||||
dfn = f.ast_definitions
|
||||
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
|
||||
preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
|
||||
app.controller.save_context_preset(preset)
|
||||
@@ -4539,12 +4539,12 @@ def render_context_modals(app: App) -> None:
|
||||
for f in app.context_files:
|
||||
import copy
|
||||
from src import models
|
||||
p = f.path if hasattr(f, 'path') else str(f)
|
||||
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
||||
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
|
||||
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False
|
||||
p = f.path
|
||||
vm = f.view_mode
|
||||
slc = copy.deepcopy(f.custom_slices)
|
||||
msk = copy.deepcopy(f.ast_mask)
|
||||
sig = f.ast_signatures
|
||||
dfn = f.ast_definitions
|
||||
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
|
||||
preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
|
||||
app.controller.save_context_preset(preset)
|
||||
@@ -4562,9 +4562,9 @@ def render_context_modals(app: App) -> None:
|
||||
def _get_context_composition_state(app: App) -> tuple:
|
||||
files_state = []
|
||||
for f in app.context_files:
|
||||
p = f.path if hasattr(f, 'path') else str(f)
|
||||
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
||||
agg = f.auto_aggregate if hasattr(f, 'auto_aggregate') else False
|
||||
p = f.path
|
||||
vm = f.view_mode
|
||||
agg = f.auto_aggregate
|
||||
slc = tuple((s.get('start_line'), s.get('end_line'), s.get('tag'), s.get('comment')) for s in getattr(f, 'custom_slices', []))
|
||||
mask = tuple(sorted(getattr(f, 'ast_mask', {}).items()))
|
||||
files_state.append((p, vm, agg, slc, mask))
|
||||
|
||||
Reference in New Issue
Block a user