Skip to content

Commit d29edd1

Browse files
fix: resolve mypy, ruff, and bandit errors in API modules
- Add type ignore for fusion.__version__ import (attr-defined) - Fix ast.expr type annotation in codebase.py _get_attribute_name - Add 'from None/e' to exception re-raises (B904) - Use Annotated[Session, Depends()] pattern to fix B008 - Add type ignore for aiofiles import (import-untyped) - Add type ignore for Windows subprocess.CREATE_NEW_PROCESS_GROUP - Add type annotations for variables (Run | None, list[dict]) - Add nosec B110 comments for intentional try/except/pass patterns - Add nosec B607 comments for Windows system commands - Remove outdated test_run_gui.py (tests non-existent GUINotSupportedError) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 46e7121 commit d29edd1

6 files changed

Lines changed: 60 additions & 102 deletions

File tree

fusion/api/routes/codebase.py

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ def _get_module_description(path: Path) -> str | None:
5959
content = init_file.read_text()
6060
tree = ast.parse(content)
6161
return ast.get_docstring(tree)
62-
except Exception:
63-
pass
62+
except Exception: # nosec B110
63+
pass # Silently ignore unparseable files
6464
elif path.suffix == ".py":
6565
try:
6666
content = path.read_text()
6767
tree = ast.parse(content)
6868
return ast.get_docstring(tree)
69-
except Exception:
70-
pass
69+
except Exception: # nosec B110
70+
pass # Silently ignore unparseable files
7171
return None
7272

7373

@@ -125,9 +125,7 @@ def _count_modules(node: ModuleNode) -> tuple[int, int]:
125125
return modules, files
126126

127127

128-
def _parse_python_file(path: Path) -> tuple[
129-
list[ClassInfo], list[FunctionInfo], list[str], str | None
130-
]:
128+
def _parse_python_file(path: Path) -> tuple[list[ClassInfo], list[FunctionInfo], list[str], str | None]:
131129
"""Parse a Python file and extract classes, functions, and imports."""
132130
content = path.read_text()
133131
tree = ast.parse(content)
@@ -193,8 +191,8 @@ def _parse_python_file(path: Path) -> tuple[
193191

194192
def _get_attribute_name(node: ast.Attribute) -> str:
195193
"""Get the full name of an attribute access."""
196-
parts = []
197-
current = node
194+
parts: list[str] = []
195+
current: ast.expr = node
198196
while isinstance(current, ast.Attribute):
199197
parts.append(current.attr)
200198
current = current.value
@@ -288,7 +286,7 @@ def get_file_content(path: str) -> FileContent:
288286
if not str(file_path).startswith(str(fusion_root.resolve())):
289287
raise HTTPException(status_code=403, detail="Access denied")
290288
except Exception:
291-
raise HTTPException(status_code=400, detail="Invalid path")
289+
raise HTTPException(status_code=400, detail="Invalid path") from None
292290

293291
if not file_path.exists():
294292
raise HTTPException(status_code=404, detail=f"File not found: {path}")
@@ -325,9 +323,9 @@ def get_file_content(path: str) -> FileContent:
325323
if suffix == ".py":
326324
try:
327325
classes, functions, imports, docstring = _parse_python_file(file_path)
328-
except SyntaxError:
326+
except SyntaxError: # nosec B110
329327
pass # Invalid Python syntax, just return content
330-
except Exception:
328+
except Exception: # nosec B110
331329
pass # Other parsing errors
332330

333331
return FileContent(
@@ -373,12 +371,14 @@ def search_codebase(q: str, limit: int = 20) -> list[dict]:
373371

374372
# Match file name
375373
if query in file.lower():
376-
results.append({
377-
"type": "file",
378-
"name": file,
379-
"path": rel_path,
380-
"match": "filename",
381-
})
374+
results.append(
375+
{
376+
"type": "file",
377+
"name": file,
378+
"path": rel_path,
379+
"match": "filename",
380+
}
381+
)
382382

383383
# For Python files, also search classes and functions
384384
if file.endswith(".py") and len(results) < limit:
@@ -389,24 +389,28 @@ def search_codebase(q: str, limit: int = 20) -> list[dict]:
389389
for node in ast.iter_child_nodes(tree):
390390
if isinstance(node, ast.ClassDef):
391391
if query in node.name.lower():
392-
results.append({
393-
"type": "class",
394-
"name": node.name,
395-
"path": rel_path,
396-
"line": node.lineno,
397-
"match": "class",
398-
})
392+
results.append(
393+
{
394+
"type": "class",
395+
"name": node.name,
396+
"path": rel_path,
397+
"line": node.lineno,
398+
"match": "class",
399+
}
400+
)
399401
elif isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
400402
if query in node.name.lower():
401-
results.append({
402-
"type": "function",
403-
"name": node.name,
404-
"path": rel_path,
405-
"line": node.lineno,
406-
"match": "function",
407-
})
408-
except Exception:
409-
pass
403+
results.append(
404+
{
405+
"type": "function",
406+
"name": node.name,
407+
"path": rel_path,
408+
"line": node.lineno,
409+
"match": "function",
410+
}
411+
)
412+
except Exception: # nosec B110
413+
pass # Skip files that can't be parsed
410414

411415
if len(results) >= limit:
412416
break

fusion/api/routes/runs.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
Provides CRUD operations for simulation runs and log streaming.
55
"""
66

7+
from typing import Annotated
8+
79
from fastapi import APIRouter, Depends, HTTPException, Query, status
810
from sqlalchemy.orm import Session
911
from sse_starlette.sse import EventSourceResponse
@@ -14,6 +16,9 @@
1416
from ..services.progress_watcher import stream_progress
1517
from ..services.run_manager import RunManager, stream_run_logs
1618

19+
# Type alias for dependency injection
20+
DBSession = Annotated[Session, Depends(get_db)]
21+
1722
router = APIRouter()
1823

1924

@@ -52,7 +57,7 @@ def _run_to_response(run: Run) -> RunResponse:
5257
@router.post("", response_model=RunResponse, status_code=status.HTTP_201_CREATED)
5358
def create_run(
5459
data: RunCreate,
55-
db: Session = Depends(get_db),
60+
db: DBSession,
5661
) -> RunResponse:
5762
"""
5863
Create and start a new simulation run.
@@ -65,17 +70,17 @@ def create_run(
6570
try:
6671
run = manager.create_run(data)
6772
except ValueError as e:
68-
raise HTTPException(status_code=400, detail=str(e))
73+
raise HTTPException(status_code=400, detail=str(e)) from e
6974

7075
return _run_to_response(run)
7176

7277

7378
@router.get("", response_model=RunListResponse)
7479
def list_runs(
80+
db: DBSession,
7581
status_filter: str | None = Query(None, alias="status"),
7682
limit: int = Query(50, ge=1, le=100),
7783
offset: int = Query(0, ge=0),
78-
db: Session = Depends(get_db),
7984
) -> RunListResponse:
8085
"""
8186
List all runs with optional filtering.
@@ -104,7 +109,7 @@ def list_runs(
104109

105110

106111
@router.get("/{run_id}", response_model=RunResponse)
107-
def get_run(run_id: str, db: Session = Depends(get_db)) -> RunResponse:
112+
def get_run(run_id: str, db: DBSession) -> RunResponse:
108113
"""
109114
Get details for a specific run.
110115
@@ -119,7 +124,7 @@ def get_run(run_id: str, db: Session = Depends(get_db)) -> RunResponse:
119124

120125

121126
@router.delete("/{run_id}", response_model=RunResponse)
122-
def cancel_run(run_id: str, db: Session = Depends(get_db)) -> RunResponse:
127+
def cancel_run(run_id: str, db: DBSession) -> RunResponse:
123128
"""
124129
Cancel a running job or delete a completed one.
125130
@@ -137,8 +142,8 @@ def cancel_run(run_id: str, db: Session = Depends(get_db)) -> RunResponse:
137142
@router.get("/{run_id}/logs")
138143
async def stream_logs(
139144
run_id: str,
145+
db: DBSession,
140146
from_start: bool = Query(True, description="Whether to send existing content"),
141-
db: Session = Depends(get_db),
142147
) -> EventSourceResponse:
143148
"""
144149
Stream logs via Server-Sent Events.
@@ -158,7 +163,7 @@ async def stream_logs(
158163
@router.get("/{run_id}/progress")
159164
async def stream_run_progress(
160165
run_id: str,
161-
db: Session = Depends(get_db),
166+
db: DBSession,
162167
) -> EventSourceResponse:
163168
"""
164169
Stream progress events via Server-Sent Events.

fusion/api/routes/system.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def get_version() -> dict:
2525
Returns the current API version and FUSION version.
2626
"""
2727
try:
28-
from fusion import __version__ as fusion_version
28+
from fusion import __version__ as fusion_version # type: ignore[attr-defined]
2929
except ImportError:
3030
fusion_version = "unknown"
3131

fusion/api/services/progress_watcher.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from collections.abc import AsyncGenerator
1111
from pathlib import Path
1212

13-
import aiofiles
13+
import aiofiles # type: ignore[import-untyped]
1414

1515
from ..config import settings
1616
from ..db.database import SessionLocal
@@ -121,7 +121,7 @@ def parse_progress_file(progress_path: Path) -> list[dict]:
121121
:param progress_path: Path to the progress.jsonl file.
122122
:returns: List of progress event dictionaries.
123123
"""
124-
events = []
124+
events: list[dict] = []
125125
if not progress_path.exists():
126126
return events
127127

fusion/api/services/run_manager.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from datetime import datetime
2424
from pathlib import Path
2525

26-
import aiofiles
26+
import aiofiles # type: ignore[import-untyped]
2727
from sqlalchemy.orm import Session
2828

2929
from ..config import settings
@@ -92,7 +92,8 @@ def get_run(self, run_id: str) -> Run | None:
9292
:param run_id: The run identifier.
9393
:returns: The Run object or None if not found.
9494
"""
95-
return self.db.query(Run).filter(Run.id == run_id).first()
95+
result: Run | None = self.db.query(Run).filter(Run.id == run_id).first()
96+
return result
9697

9798
def cancel_or_delete(self, run_id: str) -> Run | None:
9899
"""
@@ -101,7 +102,7 @@ def cancel_or_delete(self, run_id: str) -> Run | None:
101102
:param run_id: The run identifier.
102103
:returns: The Run object or None if not found.
103104
"""
104-
run = self.db.query(Run).filter(Run.id == run_id).first()
105+
run: Run | None = self.db.query(Run).filter(Run.id == run_id).first()
105106
if not run:
106107
return None
107108

@@ -152,7 +153,7 @@ def _start_process(self, run: Run, config_path: Path) -> None:
152153
cmd,
153154
stdout=log_file,
154155
stderr=subprocess.STDOUT,
155-
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
156+
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, # type: ignore[attr-defined]
156157
)
157158
run.pgid = None # Windows doesn't use pgid
158159
else:
@@ -183,7 +184,7 @@ def _kill_process(self, run: Run) -> None:
183184
try:
184185
if platform.system() == "Windows":
185186
# Windows: use taskkill to kill process tree
186-
subprocess.run(
187+
subprocess.run( # nosec B607
187188
["taskkill", "/T", "/F", "/PID", str(run.pid)],
188189
capture_output=True,
189190
)
@@ -262,7 +263,7 @@ def _is_process_alive(pid: int | None, pgid: int | None) -> bool:
262263
if platform.system() == "Windows":
263264
# Windows: check if process exists
264265
try:
265-
subprocess.run(
266+
subprocess.run( # nosec B607
266267
["tasklist", "/FI", f"PID eq {pid}"],
267268
capture_output=True,
268269
check=True,

fusion/cli/tests/test_run_gui.py

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)