Files
2026-07-13 13:05:14 +08:00

137 lines
4.8 KiB
Python

"""CI helpers for isolated Python example projects.
An "isolated" example is one that has its own uv project (separate pyproject.toml and uv.lock)
because its dependency closure conflicts with the workspace .venv (e.g. LeRobot requiring Python >=3.12).
Each such example opts in by setting `[tool.rerun-example] isolated = true` in its pyproject.toml.
Fails on the first non-zero exit.
"""
from __future__ import annotations
import argparse
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any
import tomli
def discover_isolated(examples_dir: Path) -> list[Path]:
found: list[Path] = []
for pyproject in sorted(examples_dir.glob("*/pyproject.toml")):
with pyproject.open("rb") as f:
data = tomli.load(f)
if data.get("tool", {}).get("rerun-example", {}).get("isolated"):
found.append(pyproject.parent)
return found
def _format_override(override: dict[str, Any]) -> str:
modules = override["module"]
if isinstance(modules, str):
modules = [modules]
lines = [f"[mypy-{','.join(modules)}]"]
for key, value in override.items():
if key == "module":
continue
if isinstance(value, bool):
value = "true" if value else "false"
lines.append(f"{key} = {value}")
return "\n".join(lines)
def build_merged_config(base_ini: Path, pyproject: Path) -> str:
with pyproject.open("rb") as f:
data = tomli.load(f)
overrides = data.get("tool", {}).get("mypy", {}).get("overrides", [])
sections = [base_ini.read_text().rstrip()]
sections.extend(_format_override(o) for o in overrides)
return "\n\n".join(sections) + "\n"
def _announce(projects: list[Path], repo_root: Path, verb: str) -> None:
print(f"{verb} {len(projects)} isolated example(s):")
for project in projects:
print(f" - {project.relative_to(repo_root)}")
def cmd_lint(examples_dir: Path, repo_root: Path) -> int:
shared_base = examples_dir / "_isolated" / "mypy.ini"
projects = discover_isolated(examples_dir)
if not projects:
print("No isolated examples found — nothing to lint.", file=sys.stderr)
return 1
_announce(projects, repo_root, "Linting")
for project in projects:
print(f"\n=== {project.relative_to(repo_root)} ===", flush=True)
subprocess.run(["uv", "sync"], cwd=project, check=True)
# rerun-dev-fixup is not in pyproject.toml (uv 0.7.x resolves all groups
# unconditionally — a path-only package would block standalone --no-sources).
# Install it explicitly here when running inside the monorepo.
shim_dir = (project / "../../../rerun_py/rerun_dev_fixup").resolve()
if shim_dir.exists():
subprocess.run(["uv", "pip", "install", str(shim_dir)], cwd=project, check=True)
merged = build_merged_config(shared_base, project / "pyproject.toml")
with tempfile.NamedTemporaryFile(
mode="w",
suffix=".ini",
prefix=f"mypy-{project.name}-",
delete=False,
) as tmp:
tmp.write(merged)
tmp_path = tmp.name
try:
subprocess.run(
["uv", "run", "mypy", "--config-file", tmp_path, "."],
cwd=project,
check=True,
)
finally:
Path(tmp_path).unlink(missing_ok=True)
return 0
def cmd_check_lock(examples_dir: Path, repo_root: Path) -> int:
projects = discover_isolated(examples_dir)
if not projects:
print("No isolated examples found — nothing to check.", file=sys.stderr)
return 1
_announce(projects, repo_root, "Checking lockfile of")
for project in projects:
print(f"\n=== {project.relative_to(repo_root)} ===", flush=True)
# Mirrors the root `uv-lock-check` pixi task: `--dry-run` surfaces
# what would change (useful diagnostic), `--check` fails the CI step
# if the lockfile is out of date.
subprocess.run(["uv", "lock", "--dry-run"], cwd=project, check=True)
subprocess.run(["uv", "lock", "--check"], cwd=project, check=True)
return 0
def main() -> int:
repo_root = Path(__file__).resolve().parents[2]
examples_dir = repo_root / "examples" / "python"
parser = argparse.ArgumentParser(description=__doc__)
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("lint", help="mypy-check every isolated example")
sub.add_parser("check-lock", help="verify every isolated example's uv.lock is up to date")
args = parser.parse_args()
if args.command == "lint":
return cmd_lint(examples_dir, repo_root)
if args.command == "check-lock":
return cmd_check_lock(examples_dir, repo_root)
parser.error(f"unknown command: {args.command}")
return 2
if __name__ == "__main__":
sys.exit(main())