Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/management_endpoints/management_v1/users.py: 77%

40 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:01 +0000

1"""`POST /management/v1/users/bulk` and `POST /management/v1/users/bulk_delete`.""" 

2 

3from typing import Annotated, Final 

4 

5from fastapi import APIRouter, Depends, Header 

6 

7from litellm._logging import verbose_proxy_logger 

8from litellm.proxy._types import CommonProxyErrors, UserAPIKeyAuth 

9from litellm.proxy.auth.user_api_key_auth import user_api_key_auth 

10from litellm.proxy.list_api.common import PROBLEM_TYPE_BASE, ManagementProblem, reject_unknown_query_params 

11from litellm.proxy.management_endpoints.management_v1.common import MANAGEMENT_V1_PREFIX 

12from litellm.proxy.management_helpers.bulk_user_creation import bulk_create_users 

13from litellm.proxy.management_helpers.bulk_user_deletion import bulk_delete_users 

14from litellm.proxy.management_helpers.utils import ( 

15 management_endpoint_wrapper, # pyright: ignore[reportUnknownVariableType] # legacy untyped decorator 

16) 

17from litellm.types.proxy.management_endpoints.internal_user_endpoints import ( 

18 BulkDeleteUserRequest, 

19 BulkDeleteUsersResponse, 

20 BulkNewUserRequest, 

21 BulkNewUserResponse, 

22) 

23from litellm.types.proxy.management_endpoints.management_v1 import ProblemDetail 

24 

25router: Final = APIRouter(prefix=MANAGEMENT_V1_PREFIX) 

26 

27 

28@router.post( 

29 "/users/bulk", 

30 tags=["Internal User management"], # mutable-ok: fastapi types tags as list[str | Enum] 

31 dependencies=(Depends(user_api_key_auth),), 

32 response_model=BulkNewUserResponse, 

33) 

34@management_endpoint_wrapper 

35async def bulk_create_users_route( 

36 data: BulkNewUserRequest, 

37 user_api_key_dict: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)], 

38) -> BulkNewUserResponse: 

39 """ 

40 Create up to 500 internal users in one request, optionally adding each one to teams. 

41 

42 Every entry in `users` takes the same fields as `/user/new`, with two differences: `auto_create_key` 

43 defaults to `false` (opt in per user to also get a virtual key back) and `send_invite_email` is not 

44 supported. Unknown fields are rejected with 422. Rows are validated together (duplicate ids or emails, 

45 unknown teams, roles the caller may not grant), inserted in one statement, and each referenced team is 

46 written once for all of its new members. 

47 

48 Rows fail independently: a bad row is reported in `data` with `success: false` and an `error`, and the 

49 other rows still get created. A user that was created but could not be added to one of its teams is 

50 reported with `success: true`, `teams` listing where they did land, and `error` naming the failed team. 

51 The whole request is refused with a 403 problem document only if creating the valid rows would exceed 

52 the license seat limit. 

53 

54 Example curl: 

55 ``` 

56 curl -X POST "http://localhost:4000/management/v1/users/bulk" \\ 

57 -H "Content-Type: application/json" \\ 

58 -H "Authorization: Bearer sk-1234" \\ 

59 -d '{ 

60 "users": [ 

61 {"user_email": "a@example.com", "user_role": "internal_user", "teams": ["team-1"]}, 

62 {"user_email": "b@example.com", "user_role": "internal_user", "auto_create_key": true} 

63 ] 

64 }' 

65 ``` 

66 

67 Returns `data` (one entry per input row, in order, with `user_id`, `user_email`, `success`, `teams`, 

68 `key`, `error`) and `meta` with `total_requested`, `created` and `failed`. 

69 """ 

70 try: 

71 from litellm.proxy.proxy_server import ( 

72 _license_check, # pyright: ignore[reportPrivateUsage] # same proxy license singleton /user/new reads 

73 litellm_proxy_admin_name, 

74 prisma_client, 

75 user_api_key_cache, 

76 ) 

77 

78 if prisma_client is None: 78 ↛ 79line 78 didn't jump to line 79 because the condition on line 78 was never true

79 raise ManagementProblem( 

80 ProblemDetail( 

81 type=f"{PROBLEM_TYPE_BASE}database-not-connected", 

82 title="Database not connected", 

83 status=503, 

84 detail=CommonProxyErrors.db_not_connected_error.value, 

85 ) 

86 ) 

87 

88 return await bulk_create_users( 

89 users=data.users, 

90 user_api_key_dict=user_api_key_dict, 

91 prisma_client=prisma_client, 

92 license_check=_license_check, 

93 litellm_proxy_admin_name=litellm_proxy_admin_name, 

94 user_api_key_cache=user_api_key_cache, 

95 ) 

96 

97 except ManagementProblem: 

98 raise 

99 except Exception: # noqa: BLE001 # a driver error answers as a problem document, not the OpenAI error shape 

100 verbose_proxy_logger.exception("/management/v1/users/bulk: Exception occurred") 

101 raise ManagementProblem( 

102 ProblemDetail( 

103 type=f"{PROBLEM_TYPE_BASE}internal-server-error", 

104 title="Internal server error", 

105 status=500, 

106 detail="Failed to create users.", 

107 ) 

108 ) 

109 

110 

111@router.post( 

112 "/users/bulk_delete", 

113 tags=["Internal User management"], # mutable-ok: FastAPI types `tags` as list[str], not Sequence 

114 dependencies=(Depends(user_api_key_auth), Depends(reject_unknown_query_params)), 

115 response_model=BulkDeleteUsersResponse, 

116) 

117@management_endpoint_wrapper 

118async def bulk_delete_users_action( 

119 data: BulkDeleteUserRequest, 

120 user_api_key_dict: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)], 

121 litellm_changed_by: Annotated[ 

122 str | None, 

123 Header(description="Who the caller is acting for; recorded on the audit log entries this call writes."), 

124 ] = None, 

125) -> BulkDeleteUsersResponse: 

126 """ 

127 Delete up to 500 users in one call, taking each out of every team it belongs to. 

128 Same authorization as `/user/delete`: proxy admins may delete anyone, org admins 

129 only users inside organizations they administer. Unknown body fields are a 422. 

130 

131 `data` holds one result per requested `user_id`, in request order. A row is 

132 `success: false` with an `error` when the id is unknown, repeated in the request, 

133 or outside the caller's scope. Rows that pass those checks are deleted together, 

134 in one transaction, so either all of them go or none does. 

135 

136 Example curl: 

137 ``` 

138 curl --location 'http://0.0.0.0:4000/management/v1/users/bulk_delete' \ 

139 --header 'Authorization: Bearer sk-1234' \ 

140 --header 'Content-Type: application/json' \ 

141 --data '{"user_ids": ["user-1", "user-2"]}' 

142 ``` 

143 """ 

144 try: 

145 from litellm.proxy.proxy_server import ( 

146 litellm_proxy_admin_name, 

147 prisma_client, 

148 proxy_logging_obj, 

149 user_api_key_cache, 

150 ) 

151 

152 if prisma_client is None: 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true

153 raise ManagementProblem( 

154 ProblemDetail( 

155 type=f"{PROBLEM_TYPE_BASE}database-not-connected", 

156 title="Database not connected", 

157 status=503, 

158 detail=CommonProxyErrors.db_not_connected_error.value, 

159 ) 

160 ) 

161 

162 results: Final = await bulk_delete_users( 

163 data=data, 

164 user_api_key_dict=user_api_key_dict, 

165 prisma_client=prisma_client, 

166 user_api_key_cache=user_api_key_cache, 

167 proxy_logging_obj=proxy_logging_obj, 

168 litellm_proxy_admin_name=litellm_proxy_admin_name, 

169 litellm_changed_by=litellm_changed_by, 

170 ) 

171 return BulkDeleteUsersResponse(data=results) 

172 

173 except ManagementProblem: 

174 raise 

175 except Exception as e: # noqa: BLE001 # a driver error answers as a problem document, not the OpenAI error shape 

176 verbose_proxy_logger.exception( 

177 "litellm.proxy.management_endpoints.management_v1.users.bulk_delete_users_action(): Exception occured - %s", 

178 e, 

179 ) 

180 raise ManagementProblem( 

181 ProblemDetail( 

182 type=f"{PROBLEM_TYPE_BASE}internal-server-error", 

183 title="Internal server error", 

184 status=500, 

185 detail="Failed to delete users.", 

186 ) 

187 )