From 8aa4beff5fd5d19bdb2bff7c3dfd8ba1ceea0d56 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 24 Jul 2025 16:42:28 -0700 Subject: [PATCH] fix(internal_user_endpoints.py): delete member from team table on /user/delete (#12926) removes user from team when user is deleted --- .../internal_user_endpoints.py | 36 ++++++++++++++ .../management_endpoints/team_endpoints.py | 48 ++++++++++++------- litellm/router.py | 12 ++--- 3 files changed, 70 insertions(+), 26 deletions(-) diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 808033b070..c059b4b58e 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -1403,6 +1403,9 @@ async def delete_user( Parameters: - user_ids: List[str] - The list of user id's to be deleted. """ + from litellm.proxy.management_endpoints.team_endpoints import ( + _cleanup_members_with_roles, + ) from litellm.proxy.proxy_server import ( create_audit_log_for_update, litellm_proxy_admin_name, @@ -1451,6 +1454,34 @@ async def delete_user( ) ) + ## CLEANUP MEMBERS_WITH_ROLES + fetch_all_teams = await prisma_client.db.litellm_teamtable.find_many( + where={"team_id": {"in": user_row.teams}} + ) + teams_to_update = [] + for team in fetch_all_teams: + is_member_in_team, new_team_members = _cleanup_members_with_roles( + existing_team_row=LiteLLM_TeamTable(**team.model_dump()), + data=TeamMemberDeleteRequest( + team_id=team.team_id, + user_id=user_row.user_id, + user_email=user_row.user_email, + ), + ) + if is_member_in_team: + _db_new_team_members: List[dict] = [ + m.model_dump() for m in new_team_members + ] + team.members_with_roles = json.dumps(_db_new_team_members) + teams_to_update.append(team) + + ## update teams + + for team in teams_to_update: + await prisma_client.db.litellm_teamtable.update( + where={"team_id": team.team_id}, + data={"members_with_roles": team.members_with_roles}, + ) # End of Audit logging ## DELETE ASSOCIATED KEYS @@ -1468,6 +1499,11 @@ async def delete_user( where={"user_id": {"in": data.user_ids}} ) + ## DELETE ASSOCIATED TEAM MEMBERSHIPS + await prisma_client.db.litellm_teammembership.delete_many( + where={"user_id": {"in": data.user_ids}} + ) + ## DELETE USERS deleted_users = await prisma_client.db.litellm_usertable.delete_many( where={"user_id": {"in": data.user_ids}} diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index 0806d98774..4867a4c595 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -1274,6 +1274,32 @@ async def team_member_add( ) +def _cleanup_members_with_roles( + existing_team_row: LiteLLM_TeamTable, + data: TeamMemberDeleteRequest, +) -> Tuple[bool, List[Member]]: + """Cleanup members_with_roles list for a team.""" + is_member_in_team = False + new_team_members: List[Member] = [] + for m in existing_team_row.members_with_roles: + if ( + data.user_id is not None + and m.user_id is not None + and data.user_id == m.user_id + ): + is_member_in_team = True + continue + elif ( + data.user_email is not None + and m.user_email is not None + and data.user_email == m.user_email + ): + is_member_in_team = True + continue + new_team_members.append(m) + return is_member_in_team, new_team_members + + @router.post( "/team/member_delete", tags=["team management"], @@ -1346,24 +1372,10 @@ async def team_member_delete( ) ## DELETE MEMBER FROM TEAM - is_member_in_team = False - new_team_members: List[Member] = [] - for m in existing_team_row.members_with_roles: - if ( - data.user_id is not None - and m.user_id is not None - and data.user_id == m.user_id - ): - is_member_in_team = True - continue - elif ( - data.user_email is not None - and m.user_email is not None - and data.user_email == m.user_email - ): - is_member_in_team = True - continue - new_team_members.append(m) + is_member_in_team, new_team_members = _cleanup_members_with_roles( + existing_team_row=existing_team_row, + data=data, + ) if not is_member_in_team: raise HTTPException(status_code=400, detail={"error": "User not found in team"}) diff --git a/litellm/router.py b/litellm/router.py index 8eeb6e2417..437cf37c09 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -3370,9 +3370,7 @@ class Router: ): def sync_wrapper( - custom_llm_provider: Optional[ - str - ] = None, + custom_llm_provider: Optional[str] = None, client: Optional[Any] = None, **kwargs, ): @@ -3384,9 +3382,7 @@ class Router: # Handle asynchronous call types async def async_wrapper( - custom_llm_provider: Optional[ - str - ] = None, + custom_llm_provider: Optional[str] = None, client: Optional[Any] = None, **kwargs, ): @@ -3451,7 +3447,7 @@ class Router: ) return async_wrapper - + async def _init_vector_store_api_endpoints( self, original_function: Callable, @@ -5021,7 +5017,7 @@ class Router: return deployment except Exception as e: if self.ignore_invalid_deployments: - verbose_router_logger.warning( + verbose_router_logger.debug( f"Error upserting deployment: {e}, ignoring and continuing with other deployments." ) return None