Skip to content

Commit 5f1907e

Browse files
committed
chore: clean linter warnings + remove mypy continue-on-error (closes #29)
Polish-only pass against the acceptance criteria of issue #29. No production logic changed; behaviour is identical end-to-end (178/178 unittests pass, every endpoint smokes 200 with the same response sizes as master). What landed: * mypy now exits zero on the repo. Three small annotation fixes (scripts/export.py:54 `existing: dict = {}`, api/search.py:54 `combined: list = []`, utils/exclusion_rules.py:76 `tokens: list[str | tuple[str, str]] = []`), one inner-loop variable rename in scripts/export.py to dodge a `Assignment to variable "e" outside except: block` clash with the surrounding `except … as e:`, and an empty `scripts/__init__.py` so mypy stops finding the script under two module names. * `continue-on-error: true` removed from the mypy step in `.github/workflows/tests.yml`. Type errors now fail the job. Comment updated to explain why. * Ruff + flake8 clean: 11 auto-fixed (F401 unused imports, F541 empty f-strings, F841 unused local `db_path` in api/workspaces.py), 5 manual renames of ambiguous `l` → `ln` / `log` / `layout / entry`, one F811 fix in app.py (duplicate `import sys`), one E402 import moved to the top of api/workspaces.py, and `# noqa: E402` markers on the scripts/export.py imports that legitimately come after `sys.path.insert(0, …)`. * Bare `print()` error logging in api/composers.py (2), api/search.py (3) and api/workspaces.py (7) replaced with `_logger = logging.getLogger(__name__)` calls. Endpoint-level catches use `_logger.exception(...)` so the traceback survives; the two per-row catches (`CLI: could not read session …` and `Error parsing composer data for …`) use `_logger.warning(..., exc_info=True)` because they're per-iteration, not endpoint-level. Matches the convention already used in scripts/export.py:42 and utils/exclusion_rules.py:191. Verified locally: - python3 -m mypy --ignore-missing-imports --no-strict-optional . → Success: no issues found in 35 source files - ruff check api/ utils/ scripts/export.py app.py → All checks passed - flake8 --select=F,E9,W6 api/ utils/ scripts/export.py app.py → clean - python3 -m unittest discover tests → Ran 178 tests, OK - python3 app.py boots clean; GET / + /api/workspaces + /api/composers + /api/search?q=foo all return 200
1 parent 95d3140 commit 5f1907e

11 files changed

Lines changed: 73 additions & 71 deletions

File tree

.github/workflows/tests.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,8 @@ jobs:
7575
python -m pip install 'flask>=3.0' 'fpdf2>=2.7' 'mypy>=1.10'
7676
7777
- name: Run mypy
78-
# Transitional only (maintainer consensus): keeps CI green until `mypy` exits
79-
# zero on this repo — then delete this line so type errors fail the job.
80-
continue-on-error: true
78+
# No `continue-on-error` — mypy now exits zero on this repo (closes #29),
79+
# so type errors must fail the job from here on.
8180
run: mypy --ignore-missing-imports --no-strict-optional --pretty .
8281

8382
# ── Secret scan: gitleaks ─────────────────────────────────────────────────

api/composers.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import json
8+
import logging
89
import os
910
import sqlite3
1011
from contextlib import closing
@@ -15,6 +16,7 @@
1516
from utils.path_helpers import to_epoch_ms
1617

1718
bp = Blueprint("composers", __name__)
19+
_logger = logging.getLogger(__name__)
1820

1921

2022
def _read_json_file(path: str):
@@ -67,8 +69,8 @@ def list_composers():
6769
composers.sort(key=lambda c: to_epoch_ms(c.get("lastUpdatedAt")), reverse=True)
6870
return jsonify(composers)
6971

70-
except Exception as e:
71-
print(f"Failed to get composers: {e}")
72+
except Exception:
73+
_logger.exception("Failed to get composers")
7274
return jsonify({"error": "Failed to get composers"}), 500
7375

7476

@@ -122,6 +124,6 @@ def get_composer(composer_id):
122124

123125
return jsonify({"error": "Composer not found"}), 404
124126

125-
except Exception as e:
126-
print(f"Failed to get composer: {e}")
127+
except Exception:
128+
_logger.exception("Failed to get composer")
127129
return jsonify({"error": "Failed to get composer"}), 500

api/export_api.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import os
1010
import re
1111
import sqlite3
12-
import sys
1312
import zipfile
1413
from contextlib import closing
1514
from datetime import datetime
@@ -18,7 +17,7 @@
1817
from flask import Blueprint, Response, current_app, jsonify, request
1918

2019
from utils.workspace_path import resolve_workspace_path
21-
from utils.path_helpers import normalize_file_path, get_workspace_folder_paths, to_epoch_ms
20+
from utils.path_helpers import get_workspace_folder_paths, to_epoch_ms
2221
from utils.text_extract import extract_text_from_bubble
2322
from utils.tool_parser import parse_tool_call
2423
from utils.exclusion_rules import build_searchable_text, is_excluded_by_rules
@@ -390,15 +389,15 @@ def export_chats():
390389
status_str = f" ({tool_status})" if tool_status else ""
391390
md += f"> **Tool: {tool_summary}**{status_str}\n"
392391
if t.get("input"):
393-
md += f">\n> **INPUT:**\n> ```\n"
392+
md += ">\n> **INPUT:**\n> ```\n"
394393
for iline in str(t["input"]).split("\n"):
395394
md += f"> {iline}\n"
396-
md += f"> ```\n"
395+
md += "> ```\n"
397396
if t.get("output"):
398-
md += f">\n> **OUTPUT:**\n> ```\n"
397+
md += ">\n> **OUTPUT:**\n> ```\n"
399398
for oline in str(t["output"]).split("\n"):
400399
md += f"> {oline}\n"
401-
md += f"> ```\n"
400+
md += "> ```\n"
402401
md += "\n"
403402
md += "---\n\n"
404403

api/logs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def get_logs():
133133
except Exception:
134134
pass
135135

136-
logs.sort(key=lambda l: l.get("timestamp") or 0, reverse=True)
136+
logs.sort(key=lambda log: log.get("timestamp") or 0, reverse=True)
137137
return jsonify({"logs": logs})
138138

139139
except Exception as e:

api/search.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55

66
import json
7+
import logging
78
import os
89
import re
910
import sqlite3
@@ -15,11 +16,12 @@
1516

1617
from utils.exclusion_rules import build_searchable_text, is_excluded_by_rules
1718
from utils.workspace_path import resolve_workspace_path, get_cli_chats_path
18-
from utils.path_helpers import normalize_file_path, get_workspace_folder_paths, to_epoch_ms
19+
from utils.path_helpers import to_epoch_ms
1920
from utils.text_extract import extract_text_from_bubble
2021
from utils.cli_chat_reader import list_cli_projects, traverse_blobs, messages_to_bubbles
2122

2223
bp = Blueprint("search", __name__)
24+
_logger = logging.getLogger(__name__)
2325

2426

2527
def _json_dump_safe(value) -> str:
@@ -51,7 +53,7 @@ def _build_exclusion_searchable(
5153
metadata_parts: list[str] | None = None,
5254
) -> str:
5355
"""Build broad searchable text so exclusion rules cover visible output."""
54-
combined = []
56+
combined: list = []
5557
if content_parts:
5658
combined.extend(p for p in content_parts if p)
5759
if metadata_parts:
@@ -231,7 +233,7 @@ def search():
231233
# Derive title from first bubble
232234
for text in bubble_texts:
233235
if text:
234-
first_lines = [l for l in text.split("\n") if l.strip()]
236+
first_lines = [ln for ln in text.split("\n") if ln.strip()]
235237
if first_lines:
236238
title = first_lines[0][:100]
237239
break
@@ -250,8 +252,8 @@ def search():
250252
except Exception:
251253
pass
252254

253-
except Exception as e:
254-
print(f"Error searching global storage: {e}")
255+
except Exception:
256+
_logger.exception("Error searching global storage")
255257
finally:
256258
if conn is not None:
257259
conn.close()
@@ -435,8 +437,8 @@ def search():
435437
"type": "cli_agent",
436438
"source": "cli",
437439
})
438-
except Exception as e:
439-
print(f"Error searching CLI sessions: {e}")
440+
except Exception:
441+
_logger.exception("Error searching CLI sessions")
440442

441443
# Sort by timestamp descending
442444
def _ts(r):
@@ -451,6 +453,6 @@ def _ts(r):
451453

452454
return jsonify({"results": results})
453455

454-
except Exception as e:
455-
print(f"Search failed: {e}")
456+
except Exception:
457+
_logger.exception("Search failed")
456458
return jsonify({"error": "Search failed", "results": []}), 500

api/workspaces.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from __future__ import annotations
99

1010
import json
11+
import logging
1112
import os
1213
import re
1314
import sqlite3
@@ -32,9 +33,11 @@
3233
to_epoch_ms,
3334
)
3435
from utils.text_extract import extract_text_from_bubble, format_tool_action
36+
from utils.tool_parser import parse_tool_call as _parse_tool_call
3537
from utils.exclusion_rules import build_searchable_text, is_excluded_by_rules
3638

3739
bp = Blueprint("workspaces", __name__)
40+
_logger = logging.getLogger(__name__)
3841

3942

4043
def _get_workspace_display_name(workspace_path: str, workspace_id: str) -> str:
@@ -663,7 +666,6 @@ def list_workspaces():
663666
primary = group[0]
664667
all_ws_ids = [e["name"] for e in group]
665668

666-
db_path = os.path.join(workspace_path, primary["name"], "state.vscdb")
667669
try:
668670
mtime = max(
669671
os.path.getmtime(os.path.join(workspace_path, e["name"], "state.vscdb"))
@@ -758,14 +760,14 @@ def list_workspaces():
758760
),
759761
"source": "cli",
760762
})
761-
except Exception as e:
762-
print(f"Failed to load CLI projects: {e}")
763+
except Exception:
764+
_logger.exception("Failed to load CLI projects")
763765

764766
projects.sort(key=lambda p: p["lastModified"], reverse=True)
765767
return jsonify(projects)
766768

767-
except Exception as e:
768-
print(f"Failed to get workspaces: {e}")
769+
except Exception:
770+
_logger.exception("Failed to get workspaces")
769771
return jsonify({"error": "Failed to get workspaces"}), 500
770772

771773

@@ -839,16 +841,15 @@ def get_workspace(workspace_id):
839841
"lastModified": datetime.fromtimestamp(mtime, tz=timezone.utc).isoformat(),
840842
})
841843

842-
except Exception as e:
843-
print(f"Failed to get workspace: {e}")
844+
except Exception:
845+
_logger.exception("Failed to get workspace")
844846
return jsonify({"error": "Failed to get workspace"}), 500
845847

846848

847849
# ---------------------------------------------------------------------------
848850
# GET /api/workspaces/<id>/tabs
849851
# ---------------------------------------------------------------------------
850852

851-
from utils.tool_parser import parse_tool_call as _parse_tool_call
852853

853854

854855
def _get_cli_workspace_tabs(workspace_id: str):
@@ -872,8 +873,8 @@ def _get_cli_workspace_tabs(workspace_id: str):
872873

873874
try:
874875
messages = traverse_blobs(session["db_path"])
875-
except Exception as e:
876-
print(f"CLI: could not read session {session_id}: {e}")
876+
except Exception:
877+
_logger.warning("CLI: could not read session %s", session_id, exc_info=True)
877878
continue
878879

879880
bubbles = messages_to_bubbles(messages, created_ms)
@@ -885,7 +886,7 @@ def _get_cli_workspace_tabs(workspace_id: str):
885886
if not title or title.startswith("New Agent"):
886887
for b in bubbles:
887888
if b["type"] == "user" and b.get("text"):
888-
first_lines = [l for l in b["text"].split("\n") if l.strip()]
889+
first_lines = [ln for ln in b["text"].split("\n") if ln.strip()]
889890
if first_lines:
890891
title = first_lines[0][:100]
891892
if len(title) == 100:
@@ -937,8 +938,8 @@ def _get_cli_workspace_tabs(workspace_id: str):
937938
tabs.sort(key=lambda t: t.get("timestamp") or 0, reverse=True)
938939
return jsonify({"tabs": tabs})
939940

940-
except Exception as e:
941-
print(f"Failed to get CLI workspace tabs: {e}")
941+
except Exception:
942+
_logger.exception("Failed to get CLI workspace tabs")
942943
return jsonify({"error": "Failed to get CLI workspace tabs"}), 500
943944

944945

@@ -1251,7 +1252,7 @@ def get_workspace_tabs(workspace_id):
12511252
if not cd.get("name") and bubbles:
12521253
first_msg = bubbles[0].get("text", "")
12531254
if first_msg:
1254-
first_lines = [l for l in first_msg.split("\n") if l.strip()]
1255+
first_lines = [ln for ln in first_msg.split("\n") if ln.strip()]
12551256
if first_lines:
12561257
title = first_lines[0][:100]
12571258
if len(title) == 100:
@@ -1394,14 +1395,14 @@ def get_workspace_tabs(workspace_id):
13941395

13951396
response["tabs"].append(tab)
13961397

1397-
except Exception as e:
1398-
print(f"Error parsing composer data for {composer_id}: {e}")
1398+
except Exception:
1399+
_logger.warning("Error parsing composer data for %s", composer_id, exc_info=True)
13991400

14001401
# Sort tabs by timestamp descending (newest first)
14011402
response["tabs"].sort(key=lambda t: t.get("timestamp") or 0, reverse=True)
14021403

14031404
return jsonify(response)
14041405

1405-
except Exception as e:
1406-
print(f"Failed to get workspace tabs: {e}")
1406+
except Exception:
1407+
_logger.exception("Failed to get workspace tabs")
14071408
return jsonify({"error": "Failed to get workspace tabs"}), 500

app.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ def favicon():
9090

9191
if __name__ == "__main__":
9292
import argparse
93-
import sys
9493

9594
parser = argparse.ArgumentParser(description="Cursor Chat Browser (Python)")
9695
parser.add_argument("--port", type=int, default=3000)

scripts/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)