Building Valo: Jinja2 Config Loading — When YAML Needs Superpowers
Environment variables hardcoded in YAML files? That's a maintenance nightmare waiting to happen.
Today we implemented Valo's configuration loader using Jinja2 templating — the exact pattern Anthropic's Sentinel uses for production AI workflows.
The Problem: Static YAML Isn't Enough
Standard YAML configs force you into multiple files (config-dev.yml, config-staging.yml, config-production.yml), each duplicating structure and differing only in values.
Any structural change means updating all three. Miss one? Production breaks.
The Solution: Template First, Parse Second
Jinja2 + YAML gives you dynamic configuration with static structure. One file, multiple environments, no duplication:
aws:
region: ${ AWS_REGION }
account: ${ AWS_ACCOUNT }
database:
host: ${ DB_HOST }
port: 5432Implementation: TDD All The Way
We wrote 14 tests before a single line of implementation. The loading flow:
- Read YAML file as text
- Process with Jinja2 (variable substitution)
- Parse rendered text as YAML
- Return Python dict
The YAML Quirk
Unquoted empty values in YAML become null. To preserve empty strings, use quotes:
# This becomes null
optional: ${ MISSING_VAR }
# This becomes ""
optional: "${ MISSING_VAR }"We updated our tests to reflect actual YAML behavior rather than fighting the spec.
Quality Metrics
- 52 tests passing (14 new config tests + 38 previous)
- 93% overall coverage (config loader: 85%)
- Zero mypy errors (strict mode)
- Zero ruff errors
Testing first. Quality always. No compromises.