Skip to content

Commit 5ee3317

Browse files
committed
feat(versiebeheer): build_sources.py - manifest-gedreven multi-versie build (Fase 1a)
Nieuwe orchestrator die per (type, version) uit sources/manifest.yaml dezelfde validate->enrich-transform als run_all.py draait en sources/generated/<type>/<version>.json produceert, plus een runtime sources/generated/manifest.json (de SourceManifest die de apps consumeren). Additief: de platte output en de 6 buildsites blijven ongewijzigd; het inhaken van de buildsites is een aparte, CI-rakende vervolgstap. Python 57 tests groen.
1 parent 2934642 commit 5ee3317

2 files changed

Lines changed: 158 additions & 0 deletions

File tree

script/build_sources.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
"""Manifest-driven multi-version source build.
3+
4+
Reads sources/manifest.yaml and, for every (type, version) entry, runs the same
5+
validate -> enrich transform as run_all.py to produce
6+
``sources/generated/<type>/<version>.json``, then writes
7+
``sources/generated/manifest.json`` -- the runtime registry the apps consume
8+
(mirrors schemas/source-manifest.v1.schema.json / the SourceManifest TS type).
9+
10+
The legacy flat output (PreScanDPIA.json/DPIA.json/IAMA.json) is produced separately
11+
by run_all.py and is unchanged; wiring the build sites to this orchestrator is a
12+
deliberate follow-up step (it touches CI and the container build).
13+
"""
14+
15+
from __future__ import annotations
16+
17+
import argparse
18+
import json
19+
import logging
20+
from pathlib import Path
21+
22+
from definition_enricher import DefinitionEnricher
23+
from manifest import load_manifest
24+
from schema_validator import SchemaValidator
25+
26+
logger = logging.getLogger(__name__)
27+
28+
29+
def build_sources(
30+
manifest_path: Path,
31+
sources_dir: Path,
32+
schema_path: Path,
33+
generated_dir: Path,
34+
script_dir: Path | None = None,
35+
) -> Path:
36+
"""Build every (type, version) declared in the manifest into a nested enriched JSON,
37+
then write the runtime manifest. Returns the runtime manifest path.
38+
39+
Raises ValueError when a source fails schema validation -- the build must not emit a
40+
registry pointing at half-valid artefacts.
41+
"""
42+
script_dir = script_dir or Path(__file__).parent
43+
manifest = load_manifest(manifest_path)
44+
validator = SchemaValidator(script_dir)
45+
enricher = DefinitionEnricher(script_dir)
46+
47+
for type_name, type_def in manifest["types"].items():
48+
begrippen_yaml = sources_dir / type_def["begrippenkader"]
49+
once_per_page = type_def.get("enrichOncePerPage", False)
50+
for version in type_def["versions"]:
51+
source = sources_dir / version["file"]
52+
is_valid, errors, _validated = validator.validate_yaml(source, schema_path)
53+
if not is_valid:
54+
raise ValueError(
55+
f"{type_name} {version['version']}: schema validation failed: {errors}"
56+
)
57+
output = generated_dir / type_name / f"{version['version']}.json"
58+
enricher.enrich_and_export(source, begrippen_yaml, output, once_per_page=once_per_page)
59+
logger.info("Built %s %s -> %s", type_name, version["version"], output)
60+
61+
runtime_path = generated_dir / "manifest.json"
62+
runtime_path.parent.mkdir(parents=True, exist_ok=True)
63+
runtime_path.write_text(
64+
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
65+
)
66+
logger.info("Wrote runtime manifest %s", runtime_path)
67+
return runtime_path
68+
69+
70+
def main() -> None:
71+
parser = argparse.ArgumentParser(description="Manifest-driven multi-version source build")
72+
parser.add_argument(
73+
"--manifest", type=Path, required=True, help="Path to sources/manifest.yaml"
74+
)
75+
parser.add_argument(
76+
"--sources-dir", type=Path, required=True, help="Directory holding the source YAMLs"
77+
)
78+
parser.add_argument(
79+
"--schema",
80+
type=Path,
81+
required=True,
82+
help="assessment-definition schema to validate every source against",
83+
)
84+
parser.add_argument(
85+
"--generated-dir", type=Path, required=True, help="Output directory for the generated JSONs"
86+
)
87+
args = parser.parse_args()
88+
build_sources(args.manifest, args.sources_dir, args.schema, args.generated_dir)
89+
90+
91+
if __name__ == "__main__":
92+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
93+
main()

script/tests/test_build_sources.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""Tests for the manifest-driven multi-version source build (build_sources.py)."""
2+
3+
import json
4+
from pathlib import Path
5+
6+
import pytest
7+
from build_sources import build_sources
8+
from manifest import load_manifest
9+
10+
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
11+
MANIFEST_PATH = REPO_ROOT / "sources" / "manifest.yaml"
12+
SCHEMA_PATH = REPO_ROOT / "schemas" / "assessment-definition.v2.schema.json"
13+
SOURCES_DIR = REPO_ROOT / "sources"
14+
15+
16+
def test_builds_one_enriched_json_per_manifest_version(tmp_path):
17+
generated = tmp_path / "generated"
18+
build_sources(MANIFEST_PATH, SOURCES_DIR, SCHEMA_PATH, generated)
19+
20+
manifest = load_manifest(MANIFEST_PATH)
21+
for type_name, type_def in manifest["types"].items():
22+
for version in type_def["versions"]:
23+
out = generated / type_name / f"{version['version']}.json"
24+
assert out.exists(), f"missing build output {out}"
25+
data = json.loads(out.read_text(encoding="utf-8"))
26+
assert data["version"] == version["version"]
27+
assert "tasks" in data
28+
29+
30+
def test_emits_a_runtime_manifest_mirroring_the_source(tmp_path):
31+
generated = tmp_path / "generated"
32+
build_sources(MANIFEST_PATH, SOURCES_DIR, SCHEMA_PATH, generated)
33+
34+
runtime = json.loads((generated / "manifest.json").read_text(encoding="utf-8"))
35+
source = load_manifest(MANIFEST_PATH)
36+
assert runtime["schemaVersion"] == source["schemaVersion"]
37+
assert set(runtime["types"]) == set(source["types"])
38+
for type_name, type_def in source["types"].items():
39+
assert runtime["types"][type_name]["latestOfficial"] == type_def["latestOfficial"]
40+
assert [v["version"] for v in runtime["types"][type_name]["versions"]] == [
41+
v["version"] for v in type_def["versions"]
42+
]
43+
44+
45+
def test_raises_when_a_source_fails_schema_validation(tmp_path):
46+
sources = tmp_path / "src"
47+
sources.mkdir()
48+
# Missing the required urn/version/tasks -> definition-schema validation fails.
49+
(sources / "bad.yaml").write_text("name: Broken\n", encoding="utf-8")
50+
(sources / "begrippen.yaml").write_text("begrippen: []\n", encoding="utf-8")
51+
manifest_file = sources / "manifest.yaml"
52+
manifest_file.write_text(
53+
"schemaVersion: 1\n"
54+
"types:\n"
55+
" dpia:\n"
56+
" latestOfficial: '1.0'\n"
57+
" begrippenkader: begrippen.yaml\n"
58+
" versions:\n"
59+
" - version: '1.0'\n"
60+
" channel: official\n"
61+
" file: bad.yaml\n",
62+
encoding="utf-8",
63+
)
64+
with pytest.raises(ValueError, match="schema validation failed"):
65+
build_sources(manifest_file, sources, SCHEMA_PATH, tmp_path / "out")

0 commit comments

Comments
 (0)