[Fix] PATCH /update/ui_settings now merges with existing record instead of overwriting

Previously, model_dump(exclude_none=True) included all bool fields (since
False != None), causing a partial PATCH to overwrite every other setting to
its default. Fix uses exclude_unset=True and reads the existing DB record
before merging, giving proper PATCH semantics.

This was a pre-existing bug but is fixed here since we're touching this code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yuneng-jiang
2026-03-05 15:49:40 -08:00
co-authored by Claude Opus 4.6
parent 79817ff796
commit 05d2ccdf56
2 changed files with 17 additions and 2 deletions
@@ -1061,13 +1061,26 @@ async def update_ui_settings(
},
)
settings_dict = settings.model_dump(exclude_none=True)
# Only include fields the caller actually sent (not Pydantic defaults).
settings_dict = settings.model_dump(exclude_unset=True)
# Enforce allowlist and drop anything unexpected
ui_settings = {
incoming = {
k: v for k, v in settings_dict.items() if k in ALLOWED_UI_SETTINGS_FIELDS
}
# Merge with existing persisted settings so a partial PATCH doesn't
# overwrite fields the caller didn't send.
existing: dict = {}
db_existing = await prisma_client.db.litellm_uisettings.find_unique(
where={"id": "ui_settings"}
)
if db_existing and db_existing.ui_settings:
raw = db_existing.ui_settings
existing = json.loads(raw) if isinstance(raw, str) else dict(raw)
ui_settings = {**existing, **incoming}
await prisma_client.db.litellm_uisettings.upsert(
where={"id": "ui_settings"},
data={
@@ -867,6 +867,7 @@ class TestProxySettingEndpoints:
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True)
mock_prisma = MagicMock()
mock_prisma.db.litellm_uisettings.upsert = AsyncMock()
mock_prisma.db.litellm_uisettings.find_unique = AsyncMock(return_value=None)
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
payload = {"disable_model_add_for_internal_users": True}
@@ -908,6 +909,7 @@ class TestProxySettingEndpoints:
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True)
mock_prisma = MagicMock()
mock_prisma.db.litellm_uisettings.upsert = AsyncMock()
mock_prisma.db.litellm_uisettings.find_unique = AsyncMock(return_value=None)
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
payload = {