Ctrl K
ComunidadGuías…

scripts/p3k_all_completeness_check.py

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

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""P3-K: audit __all__ completeness for shim and barrel files."""

from __future__ import annotations

import ast
import pathlib


def _has_deprecation_warning(text: str) -> bool:
    return "DeprecationWarning" in text


def _analyze_file(path: pathlib.Path) -> dict[str, list[str]] | None:
    try:
        text = path.read_text(encoding="utf-8", errors="replace")
    except OSError:
        return None

    try:
        tree = ast.parse(text)
    except SyntaxError:
        return None

    all_values: list[str] = []
    exported_names: list[str] = []

    for node in ast.walk(tree):
        if isinstance(node, ast.Assign):
            for target in node.targets:
                if isinstance(target, ast.Name) and target.id == "__all__":
                    try:
                        if isinstance(node.value, ast.List):
                            all_values = [
                                elt.value for elt in node.value.elts
                                if isinstance(elt, ast.Constant) and isinstance(elt.value, str)
                            ]
                        elif isinstance(node.value, ast.Constant) and isinstance(node.value.value, list):
                            all_values = [str(v) for v in node.value.value]
                    except (AttributeError, ValueError):
                        pass

        if isinstance(node, ast.FunctionDef):
            if not node.name.startswith("_"):
                exported_names.append(node.name)
        elif isinstance(node, ast.AsyncFunctionDef):
            if not node.name.startswith("_"):
                exported_names.append(node.name)
        elif isinstance(node, ast.ClassDef):
            if not node.name.startswith("_"):
                exported_names.append(node.name)

    return {
        "path": str(path),
        "has_all": bool(all_values),
        "all_values": all_values,
        "exported_names": exported_names,
        "missing_in_all": sorted(set(exported_names) - set(all_values)),
        "extra_in_all": sorted(set(all_values) - set(exported_names)),
        "is_shim": _has_deprecation_warning(text),
    }


def main() -> int:
    root = pathlib.Path("butler")
    results: list[dict[str, list[str]]] = []

    for path in sorted(root.rglob("*.py")):
        if "__pycache__" in path.parts:
            continue

        is_barrel = path.name == "__init__.py"
        try:
            text = path.read_text(encoding="utf-8", errors="replace")
        except OSError:
            continue
        is_shim = _has_deprecation_warning(text)

        if not is_barrel and not is_shim:
            continue

        result = _analyze_file(path)
        if result:
            results.append(result)

    missing_count = sum(len(r["missing_in_all"]) for r in results)
    extra_count = sum(len(r["extra_in_all"]) for r in results)

    print("=== P3-K __all__ completeness report ===")
    print(f"Barrel + shim files: {len(results)}")
    print(f"Missing in __all__: {missing_count}")
    print(f"Extra in __all__: {extra_count}")
    print()

    print("Barrel/shim files with incomplete __all__ (missing names):")
    for r in sorted(results, key=lambda x: x["path"]):
        if r["missing_in_all"]:
            label = "[shim]" if r["is_shim"] else "[barrel]"
            print(f"  {label} {r['path']}:")
            for name in r["missing_in_all"]:
                print(f"    - {name}")
    print()

    print("Barrel/shim files with extra names in __all__ (should remove):")
    for r in sorted(results, key=lambda x: x["path"]):
        if r["extra_in_all"]:
            label = "[shim]" if r["is_shim"] else "[barrel]"
            print(f"  {label} {r['path']}:")
            for name in r["extra_in_all"]:
                print(f"    - {name}")

    if missing_count > 0 or extra_count > 0:
        print(f"\nWARNING: {missing_count} missing + {extra_count} extra names found.")
        return 0
    return 0


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