Ctrl K
ComunidadGuías…

scripts/p2f-batch24-cast-returns.py

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

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""Apply cast() wrappers for common no-any-return patterns in butler/ops."""
from __future__ import annotations

import re
import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]


def mypy_errors(path: Path) -> list[str]:
    r = subprocess.run(
        [sys.executable, "-m", "mypy", str(path), "--follow-imports=skip"],
        capture_output=True,
        text=True,
        cwd=ROOT,
    )
    return [ln for ln in (r.stdout + r.stderr).splitlines() if ": error:" in ln]


def ensure_typing(path: Path, src: str, names: set[str]) -> str:
    need = {n for n in names if n in src or f"{n}(" in src}
    if not need:
        return src
    if "from typing import" in src:
        m = re.search(r"from typing import ([^\n]+)", src)
        if m:
            existing = {x.strip() for x in m.group(1).split(",")}
            add = sorted(need - existing)
            if add:
                src = src.replace(m.group(0), m.group(0) + ", " + ", ".join(add), 1)
    else:
        src = f"from typing import {', '.join(sorted(need))}\n" + src
    if "from collections.abc import Callable" not in src and "Callable" in need:
        src = "from collections.abc import Callable\n" + src
    return src


def fix_no_any_return(path: Path, src: str, errors: list[str]) -> str:
    for err in errors:
        if "no-any-return" not in err:
            continue
        m = re.search(r":(\d+):", err)
        if not m:
            continue
        line_no = int(m.group(1))
        lines = src.splitlines()
        if line_no < 1 or line_no > len(lines):
            continue
        line = lines[line_no - 1]
        if "cast(" in line or "return " not in line:
            continue
        ret_m = re.search(r"return (.+)$", line.strip())
        if not ret_m:
            continue
        expr = ret_m.group(1)
        if "->" in err or "dict[str, Any]" in err:
            cast_type = "dict[str, Any]"
        elif "list[str]" in err:
            cast_type = "list[str]"
        elif "list[Any]" in err:
            cast_type = "list[Any]"
        elif "Path" in err:
            cast_type = "Path"
        elif "bool" in err:
            cast_type = "bool"
        elif "float" in err:
            cast_type = "float"
        elif "int" in err:
            cast_type = "int"
        elif "str" in err:
            cast_type = "str"
        elif "tuple[bool, str]" in err:
            cast_type = "tuple[bool, str]"
        elif "VariantResult" in err:
            cast_type = "VariantResult"
        elif "Literal" in err:
            cast_type = "ProfileName" if "ProfileName" in src else "str"
        else:
            continue
        indent = line[: len(line) - len(line.lstrip())]
        lines[line_no - 1] = f"{indent}return cast({cast_type}, {expr})"
        src = "\n".join(lines) + ("\n" if src.endswith("\n") else "")
    return src


def main() -> int:
    fixed = 0
    for p in sorted((ROOT / "butler/ops").glob("*.py")):
        if p.name.endswith("_ops.py"):
            continue
        errs = mypy_errors(p)
        if not errs:
            continue
        src = p.read_text(encoding="utf-8")
        new = fix_no_any_return(p, src, errs)
        if new != src:
            new = ensure_typing(p, new, {"cast", "Any"})
            p.write_text(new, encoding="utf-8")
            fixed += 1
    print(f"cast-wrapped returns in {fixed} files")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())