mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
refactor(passthrough): address multipart review feedback
Build form_data_dict in one pass with groupby instead of rescanning form_items per field name, and assert on the files list directly in the boundary regression test so repeated field names are not collapsed by dict(). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ef0785881a
commit
eb15fe667d
2 changed files with 21 additions and 16 deletions
|
|
@ -6,6 +6,7 @@ import posixpath
|
|||
import traceback
|
||||
from base64 import b64encode
|
||||
from datetime import datetime
|
||||
from itertools import groupby
|
||||
from typing import Any, Dict, List, Mapping, Optional, Tuple, Union, cast
|
||||
from urllib.parse import urlencode, urlparse
|
||||
|
||||
|
|
@ -499,21 +500,23 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils):
|
|||
if isinstance(field_value, (StarletteUploadFile, UploadFile))
|
||||
]
|
||||
|
||||
field_names = tuple(
|
||||
dict.fromkeys(
|
||||
field_name
|
||||
for field_name, field_value in form_items
|
||||
if not isinstance(field_value, (StarletteUploadFile, UploadFile))
|
||||
)
|
||||
non_file_items = tuple(
|
||||
(field_name, field_value)
|
||||
for field_name, field_value in form_items
|
||||
if not isinstance(field_value, (StarletteUploadFile, UploadFile))
|
||||
)
|
||||
field_order = {
|
||||
field_name: index
|
||||
for index, field_name in enumerate(
|
||||
dict.fromkeys(field_name for field_name, _ in non_file_items)
|
||||
)
|
||||
}
|
||||
form_data_dict = {
|
||||
name: [
|
||||
field_value
|
||||
for field_name, field_value in form_items
|
||||
if field_name == name
|
||||
and not isinstance(field_value, (StarletteUploadFile, UploadFile))
|
||||
]
|
||||
for name in field_names
|
||||
field_name: [value for _, value in group]
|
||||
for field_name, group in groupby(
|
||||
sorted(non_file_items, key=lambda item: field_order[item[0]]),
|
||||
key=lambda item: item[0],
|
||||
)
|
||||
}
|
||||
|
||||
# Remove content-type header - httpx will set it correctly with the new boundary
|
||||
|
|
|
|||
|
|
@ -3276,8 +3276,10 @@ async def test_multipart_passthrough_preserves_boundary():
|
|||
async def mock_httpx_request(method, url, **kwargs):
|
||||
# Verify that files parameter is passed (not json)
|
||||
assert "files" in kwargs, "Files should be passed for multipart requests"
|
||||
files = dict(kwargs["files"])
|
||||
assert "file" in files, "File field should be in files"
|
||||
file_parts = [
|
||||
value for name, value in kwargs["files"] if name == "file"
|
||||
]
|
||||
assert len(file_parts) == 1, "File field should be in files"
|
||||
|
||||
# Verify content-type is NOT in headers (httpx will set it with correct boundary)
|
||||
headers = kwargs.get("headers", {})
|
||||
|
|
@ -3285,7 +3287,7 @@ async def test_multipart_passthrough_preserves_boundary():
|
|||
"content-type" not in headers
|
||||
), "content-type should be removed for multipart"
|
||||
|
||||
filename, content, content_type = files["file"]
|
||||
filename, content, content_type = file_parts[0]
|
||||
assert filename == "test.txt"
|
||||
assert content == b"test file content"
|
||||
assert content_type == "text/plain"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue