Skip to content

Commit 5556f70

Browse files
committed
refactor: resolve CodeFlow code quality issues (35 to ~10 remaining)
Remove unused imports, convert lists to tuples, fix import ordering, narrow broad exceptions, extract methods to reduce duplication and complexity in analyzer.py, refiner.py, _json_utils.py
1 parent 7253993 commit 5556f70

5 files changed

Lines changed: 233 additions & 291 deletions

File tree

examples/quick_demo.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@
1515
from __future__ import annotations
1616

1717
import sqlite3
18-
import time
1918
from pathlib import Path
2019

2120
from rich.console import Console
2221
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn, TimeRemainingColumn
2322
from rich.table import Table
24-
from rich.text import Text
2523

2624
from sqlseed import fill
2725

@@ -170,21 +168,21 @@ def main():
170168
cursor = conn.execute("SELECT member_id, name, email, org_code FROM members LIMIT 3")
171169
rows = cursor.fetchall()
172170
member_table = Table()
173-
for col in ["member_id", "name", "email", "org_code"]:
171+
for col in ("member_id", "name", "email", "org_code"):
174172
member_table.add_column(col, style="cyan")
175173
for row in rows:
176-
member_table.add_row(*[str(row[c]) for c in ["member_id", "name", "email", "org_code"]])
174+
member_table.add_row(*[str(row[c]) for c in ("member_id", "name", "email", "org_code")])
177175
console.print(member_table)
178176

179177
# Preview tasks
180178
console.print("\n[bold green]tasks[/bold green] (3 rows):")
181179
cursor = conn.execute("SELECT task_id, project_id, title, priority, status FROM tasks LIMIT 3")
182180
rows = cursor.fetchall()
183181
task_table = Table()
184-
for col in ["task_id", "project_id", "title", "priority", "status"]:
182+
for col in ("task_id", "project_id", "title", "priority", "status"):
185183
task_table.add_column(col, style="cyan")
186184
for row in rows:
187-
task_table.add_row(*[str(row[c]) for c in ["task_id", "project_id", "title", "priority", "status"]])
185+
task_table.add_row(*[str(row[c]) for c in ("task_id", "project_id", "title", "priority", "status")])
188186
console.print(task_table)
189187

190188
conn.close()
@@ -197,7 +195,7 @@ def main():
197195
console.print(" • Smart column type matching")
198196
console.print(" • Foreign key integrity")
199197
console.print(" • Real-time progress tracking")
200-
console.print("\n[italic]Total time:[/italic] [bold]{:.2f} seconds[/bold]".format(total_time))
198+
console.print(f"\n[italic]Total time:[/italic] [bold]{total_time:.2f} seconds[/bold]")
201199

202200

203201
if __name__ == "__main__":

plugins/sqlseed-ai/src/sqlseed_ai/_json_utils.py

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,55 +6,66 @@
66

77

88
def parse_json_response(content: str) -> dict[str, Any]:
9+
"""Parse JSON from LLM response using 3-strategy fallback."""
910
cleaned = content.strip()
1011

11-
# Strategy 1: Direct parse (ideal case — model outputs raw JSON)
12+
return _try_direct_parse(cleaned) or _try_markdown_fence_parse(cleaned) or _try_raw_decode(cleaned) or {}
13+
14+
15+
def _try_direct_parse(content: str) -> dict[str, Any] | None:
16+
"""Strategy 1: Direct parse (ideal case — model outputs raw JSON)."""
17+
try:
18+
result = json.loads(content)
19+
if isinstance(result, dict):
20+
_sanitize_names(result)
21+
return result
22+
except json.JSONDecodeError:
23+
pass
24+
return None
25+
26+
27+
def _try_markdown_fence_parse(content: str) -> dict[str, Any] | None:
28+
"""Strategy 2: Strip markdown code fences (```json\\n{...}\\n```)."""
29+
open_idx = content.find("```")
30+
if open_idx < 0:
31+
return None
32+
after_open = content[open_idx + 3 :]
33+
nl_pos = after_open.find("\n")
34+
if nl_pos < 0:
35+
return None
36+
content_start = nl_pos + 1
37+
close_idx = after_open.find("```", content_start)
38+
if close_idx < 0:
39+
return None
40+
fence_content = after_open[content_start:close_idx].strip()
1241
try:
13-
result = json.loads(cleaned)
42+
result = json.loads(fence_content)
1443
if isinstance(result, dict):
1544
_sanitize_names(result)
1645
return result
1746
except json.JSONDecodeError:
1847
pass
48+
return None
1949

20-
# Strategy 2: Strip markdown code fences
21-
# Handles: ```json\n{...}\n``` or ```\n{...}\n```
22-
# Split on fence markers instead of using backtracking-prone regex with DOTALL
23-
open_idx = cleaned.find("```")
24-
if open_idx >= 0:
25-
# Skip the opening fence and optional language tag
26-
after_open = cleaned[open_idx + 3 :]
27-
# Skip "json" or other language identifier on the same line
28-
nl_pos = after_open.find("\n")
29-
if nl_pos >= 0:
30-
content_start = nl_pos + 1
31-
# Find closing fence
32-
close_idx = after_open.find("```", content_start)
33-
if close_idx >= 0:
34-
fence_content = after_open[content_start:close_idx].strip()
35-
try:
36-
result = json.loads(fence_content)
37-
if isinstance(result, dict):
38-
_sanitize_names(result)
39-
return result
40-
except json.JSONDecodeError:
41-
pass
4250

43-
# Strategy 3: Find first '{' and use json.JSONDecoder.raw_decode()
44-
# Handles: explanatory text before/after JSON, no code fences
45-
# raw_decode() correctly handles braces inside JSON strings
46-
first_brace = cleaned.find("{")
47-
if first_brace >= 0:
48-
try:
49-
decoder = json.JSONDecoder()
50-
result, _ = decoder.raw_decode(cleaned, idx=first_brace)
51-
if isinstance(result, dict):
52-
_sanitize_names(result)
53-
return result
54-
except json.JSONDecodeError:
55-
pass
51+
def _try_raw_decode(content: str) -> dict[str, Any] | None:
52+
"""Strategy 3: Find first '{' and use json.JSONDecoder.raw_decode().
5653
57-
return {}
54+
Handles explanatory text before/after JSON without code fences.
55+
raw_decode() correctly handles braces inside JSON strings.
56+
"""
57+
first_brace = content.find("{")
58+
if first_brace < 0:
59+
return None
60+
try:
61+
decoder = json.JSONDecoder()
62+
result, _ = decoder.raw_decode(content, idx=first_brace)
63+
if isinstance(result, dict):
64+
_sanitize_names(result)
65+
return result
66+
except json.JSONDecodeError:
67+
pass
68+
return None
5869

5970

6071
def _sanitize_names(data: dict[str, Any]) -> None:

plugins/sqlseed-ai/src/sqlseed_ai/analyzer.py

Lines changed: 20 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -298,17 +298,28 @@ def _find_local_fallback_model(
298298

299299
return None
300300

301-
def call_llm(self, messages: list[dict[str, str]]) -> dict[str, Any]:
301+
def _ensure_config(self) -> None:
302+
"""Initialize and validate AIConfig if not already done."""
302303
if self._config is None:
303304
self._config = AIConfig.from_env()
304305
self._config.resolve_model()
305306
if not self._config.resolve_api_key():
306307
raise ValueError("AI API key not configured")
307308

308-
current_model = self._config.model
309+
def _call_with_fallback(
310+
self,
311+
call_fn: Callable[[str], dict[str, Any]],
312+
) -> dict[str, Any]:
313+
"""Execute an LLM call with model fallback on timeout/connection errors.
314+
315+
Args:
316+
call_fn: A callable that takes a model name and returns the LLM result.
317+
"""
318+
assert self._config is not None # ensured by _ensure_config()
319+
current_model: str = self._config.model or ""
309320
for attempt in range(_MAX_FALLBACK_ATTEMPTS):
310321
try:
311-
return self._call_llm_once(messages, model=current_model)
322+
return call_fn(current_model)
312323
except (APITimeoutError, APIConnectionError) as e:
313324
logger.warning(
314325
"LLM API call timed out or connection failed",
@@ -344,6 +355,10 @@ def call_llm(self, messages: list[dict[str, str]]) -> dict[str, Any]:
344355

345356
raise RuntimeError(f"LLM API call failed after {_MAX_FALLBACK_ATTEMPTS} fallback attempts")
346357

358+
def call_llm(self, messages: list[dict[str, str]]) -> dict[str, Any]:
359+
self._ensure_config()
360+
return self._call_with_fallback(lambda model: self._call_llm_once(messages, model=model))
361+
347362
def call_llm_streaming(
348363
self,
349364
messages: list[dict[str, str]],
@@ -360,50 +375,8 @@ def call_llm_streaming(
360375
- "parsing": parsing the response JSON
361376
- "done": analysis complete, info={"tokens": int, "model": str}
362377
"""
363-
if self._config is None:
364-
self._config = AIConfig.from_env()
365-
self._config.resolve_model()
366-
if not self._config.resolve_api_key():
367-
raise ValueError("AI API key not configured")
368-
369-
current_model = self._config.model
370-
for attempt in range(_MAX_FALLBACK_ATTEMPTS):
371-
try:
372-
return self._call_llm_streaming_once(messages, on_progress, model=current_model)
373-
except (APITimeoutError, APIConnectionError) as e:
374-
logger.warning(
375-
"LLM API call timed out or connection failed",
376-
model=current_model,
377-
error=str(e)[:200],
378-
attempt=attempt + 1,
379-
)
380-
381-
next_model = select_next_gemma_model(current_model or "", backend=self._config.backend)
382-
if next_model is None:
383-
raise RuntimeError(
384-
f"LLM API call failed after trying {attempt + 1} model(s). "
385-
f"Last error (model={current_model}): {e}"
386-
) from e
387-
388-
# For local backends, verify the fallback model is actually available.
389-
if self._config.backend in (AIBackend.LM_STUDIO, AIBackend.OLLAMA):
390-
actual_model = self._find_local_fallback_model(current_model, next_model)
391-
if actual_model is None:
392-
raise RuntimeError(
393-
f"No other model available on local backend besides {current_model}. "
394-
f"Consider using a smaller model or increasing --timeout. "
395-
f"Last error: {e}"
396-
) from e
397-
next_model = actual_model
398-
399-
logger.warning(
400-
"Falling back to next Gemma 4 model",
401-
from_model=current_model,
402-
to_model=next_model,
403-
)
404-
current_model = next_model
405-
406-
raise RuntimeError(f"LLM API call failed after {_MAX_FALLBACK_ATTEMPTS} fallback attempts")
378+
self._ensure_config()
379+
return self._call_with_fallback(lambda model: self._call_llm_streaming_once(messages, on_progress, model=model))
407380

408381
@staticmethod
409382
def _is_reasoning_model_id(model_id: str | None) -> bool:

plugins/sqlseed-ai/src/sqlseed_ai/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def _detect_all_local_models(self) -> list[str]:
216216
models = [m.get("id") for m in data.get("data", []) if m.get("id")]
217217
self._all_models_cache = (time.monotonic(), models)
218218
return models
219-
except Exception:
219+
except (OSError, ValueError, KeyError):
220220
logger.debug("Could not auto-detect local models", backend=self.backend.value)
221221

222222
# Cache negative result too, to avoid hammering a dead endpoint

0 commit comments

Comments
 (0)