refactor(config): resolve env vars via in-place Pydantic walk
Replace the dump→resolve→model_validate roundtrip with a recursive walk
that substitutes ${VAR} in string values directly on BaseModel /
__pydantic_extra__ / dict / list nodes. Identity is preserved on any
subtree with no references, so the original Config instance is returned
unchanged when nothing needs resolving.
Side effects:
- exclude=True fields (e.g. DreamConfig.cron) now survive even when
other fields in the same config contain ${VAR} references, closing
the edge case left open by the previous fast-path-only fix.
- _has_env_refs is dropped (the walker short-circuits naturally).
- Added a regression test pairing cron with a resolved providers.groq
api_key to lock the coexistence case.
Made-with: Cursor
This commit is contained in:
@@ -102,3 +102,28 @@ class TestResolveConfig:
|
||||
assert resolved.agents.defaults.dream.describe_schedule() == (
|
||||
"cron 5 11 * * * (legacy)"
|
||||
)
|
||||
|
||||
def test_preserves_excluded_fields_with_env_refs(self, tmp_path, monkeypatch):
|
||||
"""Excluded fields must also survive when the config contains
|
||||
``${VAR}`` refs elsewhere. An in-place walk preserves the legacy
|
||||
``cron`` override even as unrelated string fields are substituted."""
|
||||
monkeypatch.setenv("TEST_API_KEY", "resolved-key")
|
||||
config_path = tmp_path / "config.json"
|
||||
config_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"agents": {"defaults": {"dream": {"cron": "5 11 * * *"}}},
|
||||
"providers": {"groq": {"apiKey": "${TEST_API_KEY}"}},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
raw = load_config(config_path)
|
||||
resolved = resolve_config_env_vars(raw)
|
||||
|
||||
assert resolved.providers.groq.api_key == "resolved-key"
|
||||
assert resolved.agents.defaults.dream.cron == "5 11 * * *"
|
||||
assert resolved.agents.defaults.dream.describe_schedule() == (
|
||||
"cron 5 11 * * * (legacy)"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user