fix(proxy): read local config.yaml as utf-8 regardless of OS locale

ProxyConfig loaded the local config file with the locale default encoding,
which crashes or silently corrupts UTF-8 configs on Windows (cp1252). The
S3 and GCS config loaders already decode UTF-8 explicitly; this brings the
local path in line. Includes are covered via _load_yaml_file.
This commit is contained in:
Luke J 2026-07-22 22:25:05 +12:00
parent f1f0a0bacd
commit 770b0da0b6
2 changed files with 34 additions and 2 deletions

View file

@ -3804,7 +3804,7 @@ class ProxyConfig:
Load and parse a YAML file
"""
try:
with open(file_path, "r") as file:
with open(file_path, "r", encoding="utf-8") as file:
return yaml.safe_load(file) or {}
except Exception as e:
raise Exception(f"Error loading yaml file {file_path}: {str(e)}")
@ -3825,7 +3825,7 @@ class ProxyConfig:
# Load existing config
## Yaml
if os.path.exists(f"{file_path}"):
with open(f"{file_path}", "r") as config_file:
with open(f"{file_path}", "r", encoding="utf-8") as config_file:
config = yaml.safe_load(config_file)
elif file_path is not None:
raise Exception(f"Config file not found: {file_path}")

View file

@ -8,6 +8,7 @@ Pins covered:
from __future__ import annotations
import builtins
import json
import os
from types import SimpleNamespace
@ -325,6 +326,24 @@ def test_ProxyConfig__load_yaml_file_raises_on_missing_file():
pc._load_yaml_file("/no/such/file.yaml")
def _open_with_ascii_default(real_open):
def opener(file, mode="r", *args, encoding=None, **kwargs):
if "b" not in mode and encoding is None:
encoding = "ascii"
return real_open(file, mode, *args, encoding=encoding, **kwargs)
return opener
def test_ProxyConfig__load_yaml_file_reads_utf8_under_non_utf8_locale(tmp_path, monkeypatch):
f = tmp_path / "c.yaml"
f.write_text('general_settings:\n alerting_args:\n daily_report_note: "Pākīhi café"\n', encoding="utf-8")
monkeypatch.setattr(builtins, "open", _open_with_ascii_default(open))
pc = ProxyConfig()
result = pc._load_yaml_file(str(f))
assert result == {"general_settings": {"alerting_args": {"daily_report_note": "Pākīhi café"}}}
# ---------------------------------------------------------------------------
# ProxyConfig._get_config_from_file
# ---------------------------------------------------------------------------
@ -350,6 +369,19 @@ async def test_ProxyConfig__get_config_from_file_missing_path_raises():
await pc._get_config_from_file(config_file_path="/no/such/file.yaml")
@pytest.mark.asyncio
async def test_ProxyConfig__get_config_from_file_reads_utf8_under_non_utf8_locale(tmp_path, monkeypatch):
f = tmp_path / "c.yaml"
f.write_text(
'model_list:\n - model_name: "summariser bāsic"\n litellm_params:\n model: gpt-4o\n',
encoding="utf-8",
)
monkeypatch.setattr(builtins, "open", _open_with_ascii_default(open))
pc = ProxyConfig()
result = await pc._get_config_from_file(config_file_path=str(f))
assert result == {"model_list": [{"model_name": "summariser bāsic", "litellm_params": {"model": "gpt-4o"}}]}
# ---------------------------------------------------------------------------
# ProxyConfig._process_includes
# ---------------------------------------------------------------------------