fix(config): invalidate fields with missing env refs

This commit is contained in:
Xubin Ren
2026-07-21 17:35:16 +08:00
parent 4cfc99f4b3
commit b46e7f4377
2 changed files with 33 additions and 6 deletions
+9 -6
View File
@@ -121,15 +121,18 @@ def resolve_env_refs(value: str) -> str:
"""Resolve ``${VAR}`` references in a single string, leniently.
Unlike :func:`resolve_config_env_vars` (which walks a whole ``Config`` and
raises on a missing variable), this resolves one value and substitutes an
empty string for any unset reference. It is meant for individual, lazily
consumed secret fields — e.g. a transcription provider's ``api_key`` — so a
missing variable degrades to "not configured" instead of sending the literal
``${VAR}`` text to the provider. Non-string input is returned unchanged.
raises on a missing variable), this resolves one value and returns an empty
string if any reference is unset. It is meant for individual, lazily consumed
fields — e.g. a transcription provider's ``api_key`` or ``api_base`` — so a
missing variable degrades to "not configured" instead of producing a partial
value. Non-string input is returned unchanged.
"""
if not isinstance(value, str):
return value
return _ENV_REF_PATTERN.sub(lambda m: os.environ.get(m.group(1), ""), value)
names = _ENV_REF_PATTERN.findall(value)
if any(name not in os.environ for name in names):
return ""
return _ENV_REF_PATTERN.sub(lambda m: os.environ[m.group(1)], value)
def _resolve_in_place(obj: Any) -> Any:
+24
View File
@@ -185,6 +185,18 @@ def test_resolver_env_ref_missing_var_degrades_to_not_configured() -> None:
assert resolved.configured is False
def test_resolver_missing_embedded_api_key_ref_degrades_to_not_configured() -> None:
config = Config()
config.transcription.provider = "groq"
config.providers.groq.api_key = "Bearer ${MISSING_GROQ_KEY}"
with patch.dict(os.environ, {}, clear=True):
resolved = resolve_transcription_config(config)
assert not resolved.api_key
assert resolved.configured is False
def test_resolver_interpolates_env_ref_in_api_base() -> None:
config = Config()
config.transcription.provider = "groq"
@@ -197,6 +209,18 @@ def test_resolver_interpolates_env_ref_in_api_base() -> None:
assert resolved.api_base == "https://groq.example/v1"
def test_resolver_missing_embedded_api_base_ref_uses_provider_default() -> None:
config = Config()
config.transcription.provider = "groq"
config.providers.groq.api_key = "gsk-test"
config.providers.groq.api_base = "https://${MISSING_GROQ_HOST}/openai/v1"
with patch.dict(os.environ, {}, clear=True):
resolved = resolve_transcription_config(config)
assert resolved.api_base == "https://api.groq.com/openai/v1"
def test_resolver_supports_xiaomi_mimo_transcription_provider() -> None:
config = Config()
config.transcription.provider = "xiaomi_mimo"