mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(users): return precise conflicts without retrying them
This commit is contained in:
parent
5da3813d63
commit
dfe290a956
3 changed files with 60 additions and 2 deletions
|
|
@ -5559,6 +5559,16 @@ _LOCK_TEAMS_BY_ID_SQL: Final = (
|
|||
)
|
||||
|
||||
|
||||
def _user_unique_conflict_message(metadata: object, user_id: object) -> str:
|
||||
match metadata:
|
||||
case {"target": ["user_id"]}:
|
||||
return f"User with id {user_id} already exists"
|
||||
case {"target": ["sso_user_id"]}:
|
||||
return "User with this sso_user_id already exists"
|
||||
case _:
|
||||
return "User conflicts with an existing unique value"
|
||||
|
||||
|
||||
class PrismaClient:
|
||||
spend_log_transactions: list = []
|
||||
_spend_log_transactions_lock = asyncio.Lock()
|
||||
|
|
@ -6446,6 +6456,7 @@ class PrismaClient:
|
|||
max_tries=3, # maximum number of retries
|
||||
max_time=10, # maximum total time to retry for
|
||||
on_backoff=on_backoff, # specifying the function to call on backoff
|
||||
giveup=lambda exc: isinstance(exc, HTTPException) and exc.status_code == 409,
|
||||
)
|
||||
@log_db_metrics
|
||||
async def insert_data(
|
||||
|
|
@ -6504,7 +6515,7 @@ class PrismaClient:
|
|||
except UniqueViolationError as e:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail={"error": f"User with id {db_data['user_id']} already exists"},
|
||||
detail={"error": _user_unique_conflict_message(e.meta, db_data["user_id"])},
|
||||
) from e
|
||||
except Exception as e:
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -73,7 +73,10 @@ async def test_new_user_returns_conflict_when_id_is_taken_after_duplicate_check(
|
|||
table: Final = SimpleNamespace(
|
||||
find_first=AsyncMock(return_value=None),
|
||||
count=AsyncMock(return_value=0),
|
||||
create=AsyncMock(side_effect=UniqueViolationError({}, message="Unique constraint failed on user_id")),
|
||||
create=AsyncMock(side_effect=UniqueViolationError(
|
||||
{"user_facing_error": {"meta": {"target": ["user_id"]}}},
|
||||
message="Unique constraint failed on user_id",
|
||||
)),
|
||||
upsert=AsyncMock(return_value=LiteLLM_UserTable(user_id="employee-42")),
|
||||
)
|
||||
mocker.patch("prisma.Prisma", return_value=SimpleNamespace(litellm_usertable=table))
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ from types import SimpleNamespace
|
|||
from typing import Final
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from prisma.errors import UniqueViolationError
|
||||
|
||||
from litellm._service_logger import ServiceTypes
|
||||
from litellm.models.user import LiteLLM_UserTable
|
||||
|
|
@ -192,6 +194,48 @@ async def test_insert_data_team_serializes_members(prisma_client: PrismaClient)
|
|||
assert create_payload["team_id"] == "t1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("metadata", "message"),
|
||||
[
|
||||
({"target": ["user_id"]}, "User with id requested-user already exists"),
|
||||
({"target": ["sso_user_id"]}, "User with this sso_user_id already exists"),
|
||||
(None, "User conflicts with an existing unique value"),
|
||||
({"target": ["unknown_field"]}, "User conflicts with an existing unique value"),
|
||||
],
|
||||
)
|
||||
async def test_user_conflict_names_constraint_and_does_not_retry(
|
||||
prisma_client: PrismaClient,
|
||||
metadata: Mapping[str, object] | None,
|
||||
message: str,
|
||||
) -> None:
|
||||
create: Final = AsyncMock(side_effect=UniqueViolationError({"user_facing_error": {"meta": metadata}}))
|
||||
prisma_client.db.litellm_usertable.create = create
|
||||
with pytest.raises(HTTPException) as error:
|
||||
await prisma_client.insert_data(
|
||||
data={"user_id": "requested-user", "sso_user_id": "sso-subject"},
|
||||
table_name="user", allow_existing_user=False,
|
||||
)
|
||||
assert error.value.status_code == 409
|
||||
assert error.value.detail == {"error": message}
|
||||
assert create.await_count == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_insert_still_retries_transient_connection_failure(
|
||||
prisma_client: PrismaClient, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
row: Final = LiteLLM_UserTable(user_id="requested-user")
|
||||
create: Final = AsyncMock(side_effect=[httpx.ConnectError("connection unavailable"), row])
|
||||
prisma_client.db.litellm_usertable.create = create
|
||||
monkeypatch.setattr("asyncio.sleep", AsyncMock())
|
||||
result: Final = await prisma_client.insert_data(
|
||||
data={"user_id": row.user_id}, table_name="user", allow_existing_user=False
|
||||
)
|
||||
assert result is row
|
||||
assert create.await_count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_insert_data_user_organization_fk_raises_400(
|
||||
prisma_client: PrismaClient,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue