diff --git a/litellm/models/model.py b/litellm/models/model.py index 209f26d4837..a0c840341ab 100644 --- a/litellm/models/model.py +++ b/litellm/models/model.py @@ -29,6 +29,8 @@ class LiteLLM_ProxyModelTable(LiteLLMPydanticObjectBase): @model_validator(mode="before") @classmethod def check_potential_json_str(cls, values): + if not isinstance(values, dict): + return values if isinstance(values.get("litellm_params"), str): try: values["litellm_params"] = json.loads(values["litellm_params"]) diff --git a/tests/e2e/management/test_model_tag_accessgroup_e2e.py b/tests/e2e/management/test_model_tag_accessgroup_e2e.py index e6a187ae105..63e51e4abcb 100644 --- a/tests/e2e/management/test_model_tag_accessgroup_e2e.py +++ b/tests/e2e/management/test_model_tag_accessgroup_e2e.py @@ -180,6 +180,12 @@ class ModelBlockBody(BaseModel): model_id: str +class ModelBlockResponse(BaseModel): + model_config = ConfigDict(protected_namespaces=()) + model_id: str + blocked: bool + + class ModelInfoBlockDetail(BaseModel): id: str | None = None blocked: bool | None = None @@ -245,11 +251,6 @@ class TestModelRoutes: def test_block_then_unblock_persists_to_model_info( self, client: ManagementClient, resources: ResourceManager ) -> None: - """The blocked flag's persistence is read back from /model/info, not from the - /model/block response: that route currently returns a non-2xx serialization - envelope even though the DB write lands, so the /model/info read-back is the - authoritative persistence contract and keeps this test valid once the - response shape is fixed.""" model_name = f"e2e-mgmt-model-block-{unique_marker()}" model_id = _create_db_model(client, resources, model_name) @@ -257,27 +258,25 @@ class TestModelRoutes: f"{model_name!r} already reports blocked in /model/info before /model/block ran" ) - _ = client.proxy.transport.send( - "/model/block", - headers=client.proxy.transport.master, - json=ModelBlockBody(model_id=model_id), - ) - _ = _poll( - client.proxy, - lambda: True if _model_blocked_flag(client, model_id) is True else None, - f"/model/info never reported {model_name!r} blocked after /model/block", - ) - - _ = client.proxy.transport.send( - "/model/unblock", - headers=client.proxy.transport.master, - json=ModelBlockBody(model_id=model_id), - ) - _ = _poll( - client.proxy, - lambda: True if _model_blocked_flag(client, model_id) is not True else None, - f"/model/info never cleared blocked for {model_name!r} after /model/unblock", - ) + for action, expected in (("block", True), ("unblock", False)): + response = unwrap( + client.proxy.transport.post( + f"/model/{action}", + headers=client.proxy.transport.master, + json=ModelBlockBody(model_id=model_id), + response_type=ModelBlockResponse, + ) + ) + assert response.model_id == model_id + assert response.blocked is expected + _ = _poll( + client.proxy, + lambda: True + if _model_blocked_flag(client, model_id) is expected + else None, + f"/model/info never reported blocked={expected} for {model_name!r} " + f"after /model/{action}", + ) class TestTagRoutes: diff --git a/tests/test_litellm/models/test_models.py b/tests/test_litellm/models/test_models.py index 669dba8e466..9ae9b732066 100644 --- a/tests/test_litellm/models/test_models.py +++ b/tests/test_litellm/models/test_models.py @@ -5,6 +5,7 @@ Tests for backend domain models. from datetime import datetime import pytest +from pydantic import BaseModel, TypeAdapter from litellm.models.access_group import LiteLLM_AccessGroupTable from litellm.models.budget import ( @@ -130,6 +131,33 @@ class TestModel: assert model.litellm_params == {"model": "gpt-4"} assert model.model_info == {"team_id": "t1"} + def test_response_type_adapter_accepts_pydantic_row(self): + class PrismaModelRow(BaseModel): + model_id: str + model_name: str + litellm_params: dict[str, str] + model_info: dict[str, str] | None = None + blocked: bool = False + + row = PrismaModelRow( + model_id="m1", + model_name="gpt-4", + litellm_params={"model": "gpt-4"}, + model_info={"team_id": "t1"}, + blocked=True, + ) + + model = TypeAdapter(LiteLLM_ProxyModelTable | None).validate_python( + row, + from_attributes=True, + ) + + assert model is not None + assert model.model_id == "m1" + assert model.litellm_params == {"model": "gpt-4"} + assert model.model_info == {"team_id": "t1"} + assert model.blocked is True + def test_team_helpers_none_when_no_model_info(self): model = LiteLLM_ProxyModelTable( model_id="m1", model_name="gpt-4", litellm_params={}, model_info=None