#!/usr/bin/env python3 """Load and validate every available GovOPlaN module manifest from source.""" from __future__ import annotations import argparse import importlib import json import sys import tomllib from pathlib import Path META_ROOT = Path(__file__).resolve().parents[2] def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--workspace-root", type=Path, default=None, help="Directory containing the GovOPlaN repositories. Defaults to repositories.json default_parent.", ) args = parser.parse_args() catalog = json.loads((META_ROOT / "repositories.json").read_text(encoding="utf-8")) workspace_root = (args.workspace_root or Path(catalog["default_parent"])).resolve() repositories = tuple(catalog["repositories"]) source_roots: list[Path] = [] manifest_sources: list[tuple[str, Path, Path]] = [] for repository in repositories: repository_root = workspace_root / repository["path"] source_root = repository_root / "src" if not source_root.is_dir(): continue source_roots.append(source_root) manifest_sources.extend( (repository["name"], source_root, manifest_path) for manifest_path in sorted(source_root.glob("*/backend/manifest.py")) ) if not manifest_sources: print(f"No module manifests found below {workspace_root}.", file=sys.stderr) return 1 core_source = workspace_root / "govoplan-core" / "src" ordered_source_roots = [core_source, *(root for root in source_roots if root != core_source)] sys.path[:0] = [str(root) for root in ordered_source_roots] from govoplan_core.core.modules import ModuleManifest # noqa: PLC0415 from govoplan_core.core.registry import PlatformRegistry, RegistryError # noqa: PLC0415 manifests: list[ModuleManifest] = [] errors: list[str] = [] for repository_name, source_root, manifest_path in manifest_sources: module_name = ".".join(manifest_path.relative_to(source_root).with_suffix("").parts) try: loaded_module = importlib.import_module(module_name) get_manifest = getattr(loaded_module, "get_manifest") manifest = get_manifest() except Exception as exc: # pragma: no cover - rendered as a check failure errors.append(f"{repository_name}: could not load {module_name}: {exc}") continue if not isinstance(manifest, ModuleManifest): errors.append(f"{repository_name}: {module_name}.get_manifest() did not return ModuleManifest") continue entry_points = _module_entry_points(manifest_path.parents[3] / "pyproject.toml") expected_target = f"{module_name}:get_manifest" actual_target = entry_points.get(manifest.id) if actual_target != expected_target: declared = ", ".join(f"{name}={target}" for name, target in sorted(entry_points.items())) or "none" errors.append( f"{repository_name}: module {manifest.id!r} must declare entry point " f"{manifest.id}={expected_target}; found {declared}" ) continue manifests.append(manifest) if errors: print("\n".join(errors), file=sys.stderr) return 1 registry = PlatformRegistry() try: for manifest in manifests: registry.register(manifest) snapshot = registry.validate() except RegistryError as exc: print(f"Manifest registry validation failed: {exc}", file=sys.stderr) return 1 print( f"Manifest registry check passed: {len(snapshot.manifests)} manifests " f"from {len(manifest_sources)} source files." ) return 0 def _module_entry_points(pyproject_path: Path) -> dict[str, str]: if not pyproject_path.is_file(): return {} pyproject = tomllib.loads(pyproject_path.read_text(encoding="utf-8")) values = pyproject.get("project", {}).get("entry-points", {}).get("govoplan.modules", {}) return {str(name): str(target) for name, target in values.items()} if __name__ == "__main__": raise SystemExit(main())