Fix v1 user_info to preserve return type for OTEL logging

Use FastAPI Response parameter injection instead of JSONResponse to add
deprecation headers, preserving compatibility with management_endpoint_wrapper's
dict(result) call. Also add deprecation headers to admin early-return path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yuneng-jiang 2026-03-04 20:14:11 -08:00
parent 64fe6859a2
commit 7fc3f1cb44
2 changed files with 22 additions and 11 deletions

View file

@ -19,7 +19,6 @@ from typing import Any, Dict, List, Optional, Union, cast
import fastapi
from fastapi import APIRouter, Depends, Header, HTTPException, Request, status
from fastapi.responses import JSONResponse
import litellm
from litellm._logging import verbose_proxy_logger
@ -581,6 +580,7 @@ def get_user_id_from_request(request: Request) -> Optional[str]:
@management_endpoint_wrapper
async def user_info(
request: Request,
response: fastapi.Response,
user_id: Optional[str] = fastapi.Query(
default=None, description="User ID in the request parameters"
),
@ -616,6 +616,8 @@ async def user_info(
user_id is None
and user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN
):
response.headers["Deprecation"] = "true"
response.headers["Link"] = '</v2/user/info>; rel="successor-version"'
return await _get_user_info_for_proxy_admin(user_api_key_dict=user_api_key_dict)
elif user_id is None:
user_id = user_api_key_dict.user_id
@ -706,12 +708,9 @@ async def user_info(
user_id=user_id, user_info=_user_info, keys=returned_keys, teams=team_list
)
response = JSONResponse(content=response_data.model_dump(mode="json"))
response.headers["Deprecation"] = "true"
response.headers[
"Link"
] = '</v2/user/info>; rel="successor-version"'
return response
response.headers["Link"] = '</v2/user/info>; rel="successor-version"'
return response_data
except Exception as e:
verbose_proxy_logger.exception(
"litellm.proxy.proxy_server.user_info(): Exception occured - {}".format(

View file

@ -451,10 +451,14 @@ async def test_user_info_url_encoding_plus_character(mocker):
)
expected_user_id = "machine-user+alp-air-admin-b58-b@tempus.com"
from fastapi import Response
mock_response = Response()
response = await user_info(
user_id=decoded_user_id,
user_api_key_dict=mock_user_api_key_dict,
request=mock_request,
response=mock_response,
)
# Verify that the response contains the correct user data
@ -506,12 +510,16 @@ async def test_user_info_nonexistent_user(mocker):
# Call user_info function with a non-existent user_id
nonexistent_user_id = "nonexistent-user@example.com"
from fastapi import Response
mock_response = Response()
# Should raise ProxyException with 404 status code (HTTPException is converted by decorator)
with pytest.raises(ProxyException) as exc_info:
await user_info(
user_id=nonexistent_user_id,
user_api_key_dict=mock_user_api_key_dict,
request=mock_request,
response=mock_response,
)
# Verify the exception details
@ -1451,7 +1459,7 @@ async def test_user_info_v1_has_deprecation_header(mocker):
"""
Test that the old /user/info endpoint returns Deprecation headers.
"""
from fastapi import Request
from fastapi import Request, Response
from litellm.proxy._types import LiteLLM_UserTable, UserAPIKeyAuth
from litellm.proxy.management_endpoints.internal_user_endpoints import user_info
@ -1484,16 +1492,20 @@ async def test_user_info_v1_has_deprecation_header(mocker):
mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client)
mock_request = mocker.MagicMock(spec=Request)
mock_response = Response()
mock_user_api_key_dict = UserAPIKeyAuth(
user_id="test-user", user_role="proxy_admin"
)
response = await user_info(
result = await user_info(
user_id="test-user",
user_api_key_dict=mock_user_api_key_dict,
request=mock_request,
response=mock_response,
)
# The response should now be a JSONResponse with deprecation headers
assert response.headers.get("Deprecation") == "true"
assert "successor-version" in response.headers.get("Link", "")
# The result should be a UserInfoResponse (Pydantic model), not JSONResponse
assert result.user_id == "test-user"
# Deprecation headers should be set on the response object
assert mock_response.headers.get("Deprecation") == "true"
assert "successor-version" in mock_response.headers.get("Link", "")