scripts/fix_ruff.py
Rama main · réplica de lectura del repositorio original.
"""
fix_ruff.py v4 — Fixes what `ruff --fix` cannot handle automatically:
E722 bare `except:` -> `except Exception:`
E402 module-level import not at top of file
Run AFTER:
python -m ruff check tests/integration/ --fix
Usage:
python fix_ruff.py
"""
import re
import subprocess
import sys
# ---------------------------------------------------------------------------
# E722 — bare except
# These are the violations still outstanding after ruff --fix
# ---------------------------------------------------------------------------
E722_TARGETS: dict[str, list[int]] = {
"tests/integration/raw_fc_tg.py": [108, 213],
"tests/integration/raw_fc_hermes_detection_tg.py": [126, 340],
"tests/integration/raw_fc_hb_2.py": [237, 283],
"tests/integration/raw_fc_hb.py": [212, 258],
"tests/integration/project_web_browser.py": [161],
"tests/integration/unified_inference_test.py": [75],
}
# ---------------------------------------------------------------------------
# E402 — import not at top of file
#
# Strategy per file:
# "hoist" — move the offending import(s) above the non-import statement
# that triggered E402
# "remove" — the import is also F401 (unused), so just delete it
# ---------------------------------------------------------------------------
E402_TARGETS: dict[str, str] = {
# `import os` comes after `dotenv.load_dotenv()` and is also unused (F401)
# ruff --fix removes it; nothing left to do — listed for documentation only.
# "tests/integration/config_orc_prompt.py": "remove",
# `import dotenv` comes after `load_dotenv()` and is also unused (F401)
# ruff --fix removes it; nothing left to do.
# "tests/integration/create_assistant_with_platform_tools.py": "remove",
# `from config_orc_fc import config` comes after load_dotenv()
"tests/integration/vector_store_pipeline.py": "hoist",
# `from urllib.parse import urlparse` is buried mid-file
"tests/integration/user_erasure_test.py": "hoist",
}
SEARCH_WINDOW = 20
BARE_EXCEPT = re.compile(r"^(\s*)except\s*:\s*$")
IMPORT_LINE = re.compile(r"^\s*(import |from \S+ import )")
# ---------------------------------------------------------------------------
# E722 helpers
# ---------------------------------------------------------------------------
def find_bare_except(lines: list[str], hint: int) -> int | None:
lo = max(0, hint - SEARCH_WINDOW)
hi = min(len(lines), hint + SEARCH_WINDOW + 1)
for i in range(lo, hi):
if BARE_EXCEPT.match(lines[i]):
return i
# whole-file fallback — pick closest
candidates = [item for item, line in enumerate(lines) if BARE_EXCEPT.match(line)]
return min(candidates, key=lambda item: abs(item - hint)) if candidates else None
def fix_e722_file(path: str, hints: list[int]) -> int:
with open(path, "r", encoding="utf-8") as fh:
lines = fh.readlines()
changed = 0
# process highest line-number first so indices stay stable
for hint in sorted(hints, reverse=True):
idx = find_bare_except(lines, hint - 1)
if idx is None:
print(f" !! E722 not found near line {hint} in {path}")
continue
m = BARE_EXCEPT.match(lines[idx])
fixed = f"{m.group(1)}except Exception:\n"
if fixed == lines[idx]:
print(f" -- :{idx+1} already fixed")
continue
print(f" [E722] :{idx+1} {lines[idx].rstrip()!r} -> {fixed.rstrip()!r}")
lines[idx] = fixed
changed += 1
if changed:
with open(path, "w", encoding="utf-8") as fh:
fh.writelines(lines)
return changed
# ---------------------------------------------------------------------------
# E402 helpers
# ---------------------------------------------------------------------------
def hoist_imports(path: str) -> int:
"""
Move any import lines that appear after non-import, non-comment,
non-blank lines to just below the last 'legitimate' top-of-file import.
Simple heuristic — safe for the two files flagged here.
"""
with open(path, "r", encoding="utf-8") as fh:
lines = fh.readlines()
# Split into header imports and the rest
header: list[str] = []
rest: list[str] = []
in_header = True
for line in lines:
stripped = line.strip()
if in_header:
if stripped == "" or stripped.startswith("#") or IMPORT_LINE.match(line):
header.append(line)
else:
in_header = False
rest.append(line)
else:
rest.append(line)
# Pull any stray import lines out of `rest` and collect them
stray_imports: list[str] = []
clean_rest: list[str] = []
for line in rest:
if IMPORT_LINE.match(line):
stray_imports.append(line)
print(f" [E402 hoisted] {line.rstrip()!r}")
else:
clean_rest.append(line)
if not stray_imports:
print(f" -- no stray imports found in {path}")
return 0
# Insert stray imports right after the header block
new_lines = header + stray_imports + clean_rest
with open(path, "w", encoding="utf-8") as fh:
fh.writelines(new_lines)
return len(stray_imports)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def run_ruff_fix() -> None:
print("\n" + "=" * 60)
print(" Step 1: python -m ruff check tests/integration/ --fix")
print("=" * 60)
subprocess.run(
[sys.executable, "-m", "ruff", "check", "tests/integration/", "--fix"],
capture_output=False,
)
def run_ruff_check() -> int:
print("\n" + "=" * 60)
print(" Final check: python -m ruff check tests/integration/")
print("=" * 60)
result = subprocess.run(
[sys.executable, "-m", "ruff", "check", "tests/integration/"],
capture_output=False,
)
return result.returncode
def main() -> None:
print("=" * 60)
print(" Ruff auto-fixer v4 (post --fix cleanup)")
print("=" * 60)
# Step 1: let ruff handle the 49 auto-fixable violations
run_ruff_fix()
# Step 2: fix E722
print("\n" + "─" * 60)
print(" Step 2: E722 bare except")
print("─" * 60)
total_e722 = 0
for path, hints in E722_TARGETS.items():
print(f"\n-> {path}")
try:
total_e722 += fix_e722_file(path, hints)
except FileNotFoundError:
print(f" !! not found — skipping")
print(f"\n E722 fixes applied: {total_e722}")
# Step 3: fix E402
print("\n" + "─" * 60)
print(" Step 3: E402 import order")
print("─" * 60)
total_e402 = 0
for path, strategy in E402_TARGETS.items():
print(f"\n-> {path} [{strategy}]")
try:
if strategy == "hoist":
total_e402 += hoist_imports(path)
# "remove" cases are handled by ruff --fix in step 1
except FileNotFoundError:
print(f" !! not found — skipping")
print(f"\n E402 fixes applied: {total_e402}")
# Step 4: final verification
rc = run_ruff_check()
if rc == 0:
print("\n ✅ All clean!")
else:
print("\n !! Some violations remain — check output above.")
print(" The invalid-syntax errors in raw_fc_hermes_detection_tg.py")
print(" require manual repair (restore headers dict via git checkout).")
if __name__ == "__main__":
main()