scripts/p2f-batch24-cast-returns.py
Rama main · réplica de lectura del repositorio original.
#!/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())