mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-01 06:21:17 +00:00
fix(config): preserve excluded fields in resolve_config_env_vars
`resolve_config_env_vars` unconditionally dumped the config via `model_dump(mode="json")` and revalidated it, which silently dropped any field declared with `exclude=True` (e.g. `DreamConfig.cron` — introduced by the Dream rename refactor in #2717). Result: `agents.defaults.dream.cron` was never honored at runtime — the gateway always fell back to the default `every 2h` schedule even when `cron` was set in config.json. Fix: skip the roundtrip entirely when the config has no `${VAR}` references. Env-var interpolation still works unchanged when refs exist; the legacy `cron` override now survives the common case of fully-resolved config. Regression test covers the bug path.
This commit is contained in:
parent
239e91a4d6
commit
c9a21d96d8
@ -78,6 +78,9 @@ def save_config(config: Config, config_path: Path | None = None) -> None:
|
|||||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
_ENV_REF_PATTERN = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
||||||
|
|
||||||
|
|
||||||
def resolve_config_env_vars(config: Config) -> Config:
|
def resolve_config_env_vars(config: Config) -> Config:
|
||||||
"""Return a copy of *config* with ``${VAR}`` env-var references resolved.
|
"""Return a copy of *config* with ``${VAR}`` env-var references resolved.
|
||||||
|
|
||||||
@ -85,14 +88,28 @@ def resolve_config_env_vars(config: Config) -> Config:
|
|||||||
Raises :class:`ValueError` if a referenced variable is not set.
|
Raises :class:`ValueError` if a referenced variable is not set.
|
||||||
"""
|
"""
|
||||||
data = config.model_dump(mode="json", by_alias=True)
|
data = config.model_dump(mode="json", by_alias=True)
|
||||||
|
if not _has_env_refs(data):
|
||||||
|
# Skip the dump→revalidate roundtrip so fields with ``exclude=True`` survive.
|
||||||
|
return config
|
||||||
data = _resolve_env_vars(data)
|
data = _resolve_env_vars(data)
|
||||||
return Config.model_validate(data)
|
return Config.model_validate(data)
|
||||||
|
|
||||||
|
|
||||||
|
def _has_env_refs(obj: object) -> bool:
|
||||||
|
"""Return True if any string value contains a ``${VAR}`` reference."""
|
||||||
|
if isinstance(obj, str):
|
||||||
|
return bool(_ENV_REF_PATTERN.search(obj))
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return any(_has_env_refs(v) for v in obj.values())
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return any(_has_env_refs(v) for v in obj)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _resolve_env_vars(obj: object) -> object:
|
def _resolve_env_vars(obj: object) -> object:
|
||||||
"""Recursively resolve ``${VAR}`` patterns in string values."""
|
"""Recursively resolve ``${VAR}`` patterns in string values."""
|
||||||
if isinstance(obj, str):
|
if isinstance(obj, str):
|
||||||
return re.sub(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}", _env_replace, obj)
|
return _ENV_REF_PATTERN.sub(_env_replace, obj)
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
return {k: _resolve_env_vars(v) for k, v in obj.items()}
|
return {k: _resolve_env_vars(v) for k, v in obj.items()}
|
||||||
if isinstance(obj, list):
|
if isinstance(obj, list):
|
||||||
|
|||||||
@ -80,3 +80,25 @@ class TestResolveConfig:
|
|||||||
|
|
||||||
saved = json.loads(config_path.read_text(encoding="utf-8"))
|
saved = json.loads(config_path.read_text(encoding="utf-8"))
|
||||||
assert saved["channels"]["telegram"]["token"] == "${MY_TOKEN}"
|
assert saved["channels"]["telegram"]["token"] == "${MY_TOKEN}"
|
||||||
|
|
||||||
|
def test_preserves_excluded_fields_when_no_env_refs(self, tmp_path):
|
||||||
|
"""Regression: fields with ``exclude=True`` (e.g. DreamConfig.cron)
|
||||||
|
must survive ``resolve_config_env_vars`` when the config has no
|
||||||
|
``${VAR}`` references. Previously the unconditional dump→revalidate
|
||||||
|
roundtrip silently dropped them."""
|
||||||
|
config_path = tmp_path / "config.json"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{"agents": {"defaults": {"dream": {"cron": "5 11 * * *"}}}}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
raw = load_config(config_path)
|
||||||
|
assert raw.agents.defaults.dream.cron == "5 11 * * *"
|
||||||
|
|
||||||
|
resolved = resolve_config_env_vars(raw)
|
||||||
|
assert resolved.agents.defaults.dream.cron == "5 11 * * *"
|
||||||
|
assert resolved.agents.defaults.dream.describe_schedule() == (
|
||||||
|
"cron 5 11 * * * (legacy)"
|
||||||
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user