scripts/audit_entrypoints.py
Rama main · réplica de lectura del repositorio original.
"""Verify console-script targets resolve to real top-level callables in src/dm."""
from __future__ import annotations
import argparse
import ast
import tomllib
from pathlib import Path
from scripts.audit_common import print_failures, require_root
def _target_path(root: Path, module: str) -> Path:
parts = module.split(".")
if not parts or parts[0] != "dm" or any(not part.isidentifier() for part in parts):
raise ValueError(f"unsupported entrypoint module: {module}")
return root / "src" / Path(*parts).with_suffix(".py")
def audit(root: Path) -> list[str]:
root = require_root(root)
try:
data = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
except (OSError, UnicodeError, tomllib.TOMLDecodeError) as exc:
return [f"pyproject.toml: could not parse entrypoints ({exc})"]
scripts = data.get("project", {}).get("scripts", {})
if not isinstance(scripts, dict):
return ["pyproject.toml: [project.scripts] table is missing"]
failures: list[str] = []
for name, target in sorted(scripts.items()):
if not isinstance(target, str) or target.count(":") != 1:
failures.append(f"{name}: entrypoint must use module:function syntax"); continue
module, function = target.split(":", 1)
if not function.isidentifier():
failures.append(f"{name}: invalid entrypoint function {function!r}"); continue
try: path = _target_path(root, module)
except ValueError as exc: failures.append(f"{name}: {exc}"); continue
if not path.is_file():
failures.append(f"{name}: entrypoint module is missing: {path.relative_to(root)}"); continue
try: tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
except (OSError, UnicodeError, SyntaxError) as exc:
failures.append(f"{name}: could not parse entrypoint module ({exc})"); continue
if not any(isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == function for node in tree.body):
failures.append(f"{name}: top-level callable {function} is missing in {path.relative_to(root)}")
return failures
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__); parser.add_argument("root", nargs="?", default=".")
args = parser.parse_args(argv); return print_failures(audit(Path(args.root)))
if __name__ == "__main__": raise SystemExit(main())