Ctrl K
ComunidadGuías…

scripts/fix_ruff.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""
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()