mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
Fix /project/update 500 when object_permission is set
prisma_client.jsonify_object serializes the nested object_permission dict into a JSON string, which update_project then passed directly to the Prisma object permission table, raising FieldNotFoundError. Route the update through handle_update_object_permission_common, the same helper used by /team/update and /key/update, which parses string JSON payloads and upserts the LiteLLM_ObjectPermissionTable row. Fixes #28983.
This commit is contained in:
parent
e15b37a18e
commit
31392baa35
2 changed files with 61 additions and 22 deletions
|
|
@ -20,6 +20,9 @@ from litellm._uuid import uuid
|
|||
from litellm.proxy._types import *
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.management_endpoints.common_utils import _set_object_metadata_field
|
||||
from litellm.proxy.management_helpers.object_permission_utils import (
|
||||
handle_update_object_permission_common,
|
||||
)
|
||||
from litellm.proxy.management_helpers.utils import (
|
||||
management_endpoint_wrapper,
|
||||
)
|
||||
|
|
@ -665,27 +668,13 @@ async def update_project( # noqa: PLR0915
|
|||
update_data.pop(field, None)
|
||||
|
||||
# Handle object permissions
|
||||
if "object_permission" in update_data:
|
||||
object_permission_data = update_data.pop("object_permission")
|
||||
if object_permission_data:
|
||||
if existing_project.object_permission_id:
|
||||
# Update existing permission
|
||||
await prisma_client.db.litellm_objectpermissiontable.update(
|
||||
where={
|
||||
"object_permission_id": existing_project.object_permission_id
|
||||
},
|
||||
data=object_permission_data,
|
||||
)
|
||||
else:
|
||||
# Create new permission
|
||||
created_permission = (
|
||||
await prisma_client.db.litellm_objectpermissiontable.create(
|
||||
data=object_permission_data,
|
||||
)
|
||||
)
|
||||
update_data["object_permission_id"] = (
|
||||
created_permission.object_permission_id
|
||||
)
|
||||
object_permission_id = await handle_update_object_permission_common(
|
||||
data_json=update_data,
|
||||
existing_object_permission_id=existing_project.object_permission_id,
|
||||
prisma_client=prisma_client,
|
||||
)
|
||||
if object_permission_id is not None:
|
||||
update_data["object_permission_id"] = object_permission_id
|
||||
|
||||
# Handle metadata fields
|
||||
for field in LiteLLM_ManagementEndpoint_MetadataFields:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Unit tests for the VERIA-55 fixes:
|
|||
member of.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
|
@ -193,3 +193,53 @@ async def test_assign_key_org_blocks_caller_with_no_memberships():
|
|||
prisma_client=prisma,
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /project/update — object_permission upsert
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_project_upserts_object_permission_as_dict():
|
||||
"""jsonify_object serializes the nested object_permission dict into a JSON
|
||||
string; passing that string straight to the Prisma table raised
|
||||
FieldNotFoundError. The shared helper must receive it and upsert a dict."""
|
||||
from litellm.proxy._types import (
|
||||
LiteLLM_ObjectPermissionBase,
|
||||
UpdateProjectRequest,
|
||||
)
|
||||
from litellm.proxy.utils import PrismaClient
|
||||
from litellm_enterprise.proxy.management_endpoints.project_endpoints import (
|
||||
update_project,
|
||||
)
|
||||
|
||||
prisma = MagicMock()
|
||||
prisma.jsonify_object = lambda data: PrismaClient.jsonify_object(prisma, data)
|
||||
prisma.db.litellm_projecttable.find_unique = AsyncMock(
|
||||
return_value=MagicMock(team_id=None, budget_id=None, object_permission_id=None)
|
||||
)
|
||||
prisma.db.litellm_projecttable.update = AsyncMock(return_value=MagicMock())
|
||||
perm_table = prisma.db.litellm_objectpermissiontable
|
||||
perm_table.find_unique = AsyncMock(return_value=None)
|
||||
perm_table.upsert = AsyncMock(return_value=MagicMock(object_permission_id="op-1"))
|
||||
|
||||
admin = UserAPIKeyAuth(user_id="root", user_role=LitellmUserRoles.PROXY_ADMIN.value)
|
||||
data = UpdateProjectRequest(
|
||||
project_id="proj-1",
|
||||
object_permission=LiteLLM_ObjectPermissionBase(mcp_servers=["server-1"]),
|
||||
)
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.prisma_client", prisma),
|
||||
patch("litellm.proxy.proxy_server.premium_user", True),
|
||||
):
|
||||
await update_project(
|
||||
data=data, http_request=MagicMock(), user_api_key_dict=admin
|
||||
)
|
||||
|
||||
perm_table.upsert.assert_awaited_once()
|
||||
upsert_data = perm_table.upsert.await_args.kwargs["data"]
|
||||
assert upsert_data["create"] == {"mcp_servers": ["server-1"]}
|
||||
project_update = prisma.db.litellm_projecttable.update.await_args.kwargs["data"]
|
||||
assert project_update["object_permission_id"] == "op-1"
|
||||
assert "object_permission" not in project_update
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue