fix(proxy): preserve access windows when editing models (#49316)

This commit is contained in:
tin-berri 2026-10-30 14:27:23 -07:00 • committed by GitHub
parent 7e37c20748
commit 35d3a1784b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 7 deletions

View file

@ -1515,14 +1515,10 @@ def update_db_model(
# convert to prisma compatible format
for key, value in merged_model_info.items():
if isinstance(value, datetime.datetime):
merged_model_info[key] = value.isoformat()
prisma_compatible_model_dict: Final = PrismaCompatibleUpdateDBModel(
model_name=merged_model_name,
litellm_params=json.dumps(merged_litellm_params),
model_info=json.dumps(merged_model_info),
model_info=TypeAdapter(dict[str, object]).dump_json(merged_model_info).decode(),
)
if updated_patch.blocked is not None:

View file

@ -4,8 +4,9 @@ import inspect
import json
import socket
from collections.abc import AsyncIterator, Iterator, Mapping
from datetime import datetime, time, timezone
from types import MappingProxyType, SimpleNamespace
from typing import Dict, Final, Optional
from typing import Dict, Final, Literal, Optional
from unittest.mock import AsyncMock, MagicMock, patch
import httpx
@ -46,7 +47,14 @@ from litellm.proxy.management_helpers.model_quota_policy import REQUIRE_BOTH_LIM
from litellm.proxy.utils import PrismaClient
from litellm.router import Router
from litellm.router_api import ApplyResult, ChangeSet
from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo, updateDeployment, updateLiteLLMParams
from litellm.types.router import (
Deployment,
LiteLLM_Params,
ModelAccessWindow,
ModelInfo,
updateDeployment,
updateLiteLLMParams,
)
async def _passthrough_row(update_data):
@ -4048,6 +4056,45 @@ class TestUpdateDBModelBlocked:
assert "blocked" not in result
@pytest.mark.parametrize("window_patch", ["omit", "echo", "replace", "clear"])
def test_model_edit_preserves_or_explicitly_changes_access_windows(
window_patch: Literal["omit", "echo", "replace", "clear"],
) -> None:
from litellm.proxy.management_endpoints.model_management_endpoints import update_db_model
saved_window: Final = ModelAccessWindow(
start=time(13), end=time(17), timezone="Asia/Almaty", team_ids=("team-window", "team-shared")
)
replacement_window: Final = ModelAccessWindow(
start=time(22, 0, 0, 123456), end=time(6, 0, 0, 654321),
timezone="America/New_York", team_ids=("team-other",),
)
created_at: Final = datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=timezone.utc)
requested_windows: Final = {
"omit": None,
"echo": (saved_window,),
"replace": (replacement_window,),
"clear": (),
}[window_patch]
expected_windows: Final = (saved_window,) if requested_windows is None else requested_windows
result: Final = update_db_model(
db_model=Deployment(
model_name="window-model",
litellm_params=LiteLLM_Params(model="openai/test-model"),
model_info=ModelInfo(id="window-deployment", access_windows=(saved_window,), created_at=created_at),
),
updated_patch=updateDeployment(
litellm_params=updateLiteLLMParams(max_parallel_requests=7),
model_info=None if requested_windows is None else ModelInfo(access_windows=requested_windows),
),
)
assert json.loads(result["litellm_params"])["max_parallel_requests"] == 7
saved_info: Final = ModelInfo.model_validate_json(result["model_info"])
assert saved_info.access_windows == expected_windows
assert saved_info.created_at == created_at
class TestUpdateDBModelKeepsLegacyDropParams:
def test_partial_patch_keeps_encrypted_string_drop_params(self, monkeypatch):
from litellm.proxy.common_utils.encrypt_decrypt_utils import decrypt_value_helper