`ImageVersion` and `ImageMatrix` both contain Jinja2 rendering logic and filesystem operations alongside model validation. Their `render_files()` implementations are nearly identical — the same Jinja2 environment setup, the same template-walk logic, the same error-aggregation pattern, and the same file-write calls — duplicated across both classes.
Testing `ImageVersion` rendering currently requires constructing a full model hierarchy with parent pointers and a real filesystem. That makes model validation tests expensive and tightly coupled to I/O.
Extract a `TemplateRenderer` service that accepts structured inputs (template values, template paths, output paths) and produces file artifacts. Both `ImageVersion` and `ImageMatrix` delegate to it, eliminating the duplication and allowing model validation tests to run without a real filesystem.
Files: `config/image/version.py`, `config/image/matrix.py`, `config/config.py`, `config/templating/`
`ImageVersion` and `ImageMatrix` both contain Jinja2 rendering logic and filesystem operations alongside model validation. Their `render_files()` implementations are nearly identical — the same Jinja2 environment setup, the same template-walk logic, the same error-aggregation pattern, and the same file-write calls — duplicated across both classes.
Testing `ImageVersion` rendering currently requires constructing a full model hierarchy with parent pointers and a real filesystem. That makes model validation tests expensive and tightly coupled to I/O.
Extract a `TemplateRenderer` service that accepts structured inputs (template values, template paths, output paths) and produces file artifacts. Both `ImageVersion` and `ImageMatrix` delegate to it, eliminating the duplication and allowing model validation tests to run without a real filesystem.
Files: `config/image/version.py`, `config/image/matrix.py`, `config/config.py`, `config/templating/`