Skip to content

Commit a8e830f

Browse files
refactor: extract data/content preparation into helper function
Create _prepare_request_data_and_content() helper to DRY up the logic for routing data/content parameters correctly in httpx requests. This helper prevents httpx DeprecationWarnings (which cause memory leaks) by moving bytes/str from data= to content= parameter while keeping dict/Mapping in data= parameter. Applied the helper consistently across all HTTP methods in both AsyncHTTPHandler and HTTPHandler classes: - post(), put(), patch(), delete() - single_connection_post_request() Related to: b850ed1
1 parent b850ed1 commit a8e830f

File tree

1 file changed

+92
-46
lines changed

1 file changed

+92
-46
lines changed

litellm/llms/custom_httpx/http_handler.py

Lines changed: 92 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,46 @@
4747
_DEFAULT_TIMEOUT = httpx.Timeout(timeout=5.0, connect=5.0)
4848

4949

50+
def _prepare_request_data_and_content(
51+
data: Optional[Union[dict, str, bytes]] = None,
52+
content: Any = None,
53+
) -> tuple[Optional[Union[dict, Mapping]], Any]:
54+
"""
55+
Helper function to route data/content parameters correctly for httpx requests
56+
57+
This prevents httpx DeprecationWarnings that cause memory leaks.
58+
59+
Background:
60+
- httpx shows a DeprecationWarning when you pass bytes/str to `data=`
61+
- It wants you to use `content=` instead for bytes/str
62+
- The warning itself leaks memory when triggered repeatedly
63+
64+
Solution:
65+
- Move bytes/str from `data=` to `content=` before calling build_request
66+
- Keep dicts in `data=` (that's still the correct parameter for dicts)
67+
68+
Args:
69+
data: Request data (can be dict, str, or bytes)
70+
content: Request content (raw bytes/str)
71+
72+
Returns:
73+
Tuple of (request_data, request_content) properly routed for httpx
74+
"""
75+
request_data = None
76+
request_content = content
77+
78+
if data is not None:
79+
if isinstance(data, (bytes, str)):
80+
# Bytes/strings belong in content= (only if not already provided)
81+
if content is None:
82+
request_content = data
83+
else:
84+
# dict/Mapping stays in data= parameter
85+
request_data = data
86+
87+
return request_data, request_content
88+
89+
5090
def get_ssl_configuration(
5191
ssl_verify: Optional[VerifyTypes] = None,
5292
) -> Union[bool, str, ssl.SSLContext]:
@@ -301,33 +341,8 @@ async def post(
301341
if timeout is None:
302342
timeout = self.timeout
303343

304-
# ============================================================================
305-
# MEMORY LEAK FIX — Prevent httpx DeprecationWarning
306-
# ============================================================================
307-
# Problem:
308-
# httpx shows a DeprecationWarning when you pass bytes/str to `data=`.
309-
# It wants you to use `content=` instead.
310-
#
311-
# Impact:
312-
# The warning leaks memory. Preventing the warning fixes the leak.
313-
#
314-
# Fix:
315-
# Move bytes/str from `data=` to `content=` before calling build_request.
316-
# Keep dicts in `data=` (that's still correct).
317-
# ============================================================================
318-
319-
request_data = None
320-
request_content = content
321-
322-
# Route data parameter to the correct httpx parameter based on type
323-
if data is not None:
324-
if isinstance(data, (bytes, str)):
325-
# Bytes/strings belong in content= (only if not already provided)
326-
if content is None:
327-
request_content = data
328-
else:
329-
# dict/Mapping stays in data= parameter
330-
request_data = data
344+
# Prepare data/content parameters to prevent httpx DeprecationWarning (memory leak fix)
345+
request_data, request_content = _prepare_request_data_and_content(data, content)
331346

332347
req = self.client.build_request(
333348
"POST",
@@ -392,19 +407,23 @@ async def post(
392407
async def put(
393408
self,
394409
url: str,
395-
data: Optional[Union[dict, str]] = None, # type: ignore
410+
data: Optional[Union[dict, str, bytes]] = None, # type: ignore
396411
json: Optional[dict] = None,
397412
params: Optional[dict] = None,
398413
headers: Optional[dict] = None,
399414
timeout: Optional[Union[float, httpx.Timeout]] = None,
400415
stream: bool = False,
416+
content: Any = None,
401417
):
402418
try:
403419
if timeout is None:
404420
timeout = self.timeout
405421

422+
# Prepare data/content parameters to prevent httpx DeprecationWarning (memory leak fix)
423+
request_data, request_content = _prepare_request_data_and_content(data, content)
424+
406425
req = self.client.build_request(
407-
"PUT", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore
426+
"PUT", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
408427
)
409428
response = await self.client.send(req)
410429
response.raise_for_status()
@@ -452,19 +471,23 @@ async def put(
452471
async def patch(
453472
self,
454473
url: str,
455-
data: Optional[Union[dict, str]] = None, # type: ignore
474+
data: Optional[Union[dict, str, bytes]] = None, # type: ignore
456475
json: Optional[dict] = None,
457476
params: Optional[dict] = None,
458477
headers: Optional[dict] = None,
459478
timeout: Optional[Union[float, httpx.Timeout]] = None,
460479
stream: bool = False,
480+
content: Any = None,
461481
):
462482
try:
463483
if timeout is None:
464484
timeout = self.timeout
465485

486+
# Prepare data/content parameters to prevent httpx DeprecationWarning (memory leak fix)
487+
request_data, request_content = _prepare_request_data_and_content(data, content)
488+
466489
req = self.client.build_request(
467-
"PATCH", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore
490+
"PATCH", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
468491
)
469492
response = await self.client.send(req)
470493
response.raise_for_status()
@@ -512,18 +535,23 @@ async def patch(
512535
async def delete(
513536
self,
514537
url: str,
515-
data: Optional[Union[dict, str]] = None, # type: ignore
538+
data: Optional[Union[dict, str, bytes]] = None, # type: ignore
516539
json: Optional[dict] = None,
517540
params: Optional[dict] = None,
518541
headers: Optional[dict] = None,
519542
timeout: Optional[Union[float, httpx.Timeout]] = None,
520543
stream: bool = False,
544+
content: Any = None,
521545
):
522546
try:
523547
if timeout is None:
524548
timeout = self.timeout
549+
550+
# Prepare data/content parameters to prevent httpx DeprecationWarning (memory leak fix)
551+
request_data, request_content = _prepare_request_data_and_content(data, content)
552+
525553
req = self.client.build_request(
526-
"DELETE", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore
554+
"DELETE", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
527555
)
528556
response = await self.client.send(req, stream=stream)
529557
response.raise_for_status()
@@ -571,8 +599,11 @@ async def single_connection_post_request(
571599
572600
Used for retrying connection client errors.
573601
"""
602+
# Prepare data/content parameters to prevent httpx DeprecationWarning (memory leak fix)
603+
request_data, request_content = _prepare_request_data_and_content(data, content)
604+
574605
req = client.build_request(
575-
"POST", url, data=data, json=json, params=params, headers=headers, content=content # type: ignore
606+
"POST", url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
576607
)
577608
response = await client.send(req, stream=stream)
578609
response.raise_for_status()
@@ -826,21 +857,24 @@ def post(
826857
logging_obj: Optional[LiteLLMLoggingObject] = None,
827858
):
828859
try:
860+
# Prepare data/content parameters to prevent httpx DeprecationWarning (memory leak fix)
861+
request_data, request_content = _prepare_request_data_and_content(data, content)
862+
829863
if timeout is not None:
830864
req = self.client.build_request(
831865
"POST",
832866
url,
833-
data=data, # type: ignore
867+
data=request_data, # type: ignore
834868
json=json,
835869
params=params,
836870
headers=headers,
837871
timeout=timeout,
838872
files=files,
839-
content=content, # type: ignore
873+
content=request_content, # type: ignore
840874
)
841875
else:
842876
req = self.client.build_request(
843-
"POST", url, data=data, json=json, params=params, headers=headers, files=files, content=content # type: ignore
877+
"POST", url, data=request_data, json=json, params=params, headers=headers, files=files, content=request_content # type: ignore
844878
)
845879
response = self.client.send(req, stream=stream)
846880
response.raise_for_status()
@@ -868,21 +902,25 @@ def post(
868902
def patch(
869903
self,
870904
url: str,
871-
data: Optional[Union[dict, str]] = None,
905+
data: Optional[Union[dict, str, bytes]] = None,
872906
json: Optional[Union[dict, str]] = None,
873907
params: Optional[dict] = None,
874908
headers: Optional[dict] = None,
875909
stream: bool = False,
876910
timeout: Optional[Union[float, httpx.Timeout]] = None,
911+
content: Any = None,
877912
):
878913
try:
914+
# Prepare data/content parameters to prevent httpx DeprecationWarning (memory leak fix)
915+
request_data, request_content = _prepare_request_data_and_content(data, content)
916+
879917
if timeout is not None:
880918
req = self.client.build_request(
881-
"PATCH", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore
919+
"PATCH", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
882920
)
883921
else:
884922
req = self.client.build_request(
885-
"PATCH", url, data=data, json=json, params=params, headers=headers # type: ignore
923+
"PATCH", url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
886924
)
887925
response = self.client.send(req, stream=stream)
888926
response.raise_for_status()
@@ -911,21 +949,25 @@ def patch(
911949
def put(
912950
self,
913951
url: str,
914-
data: Optional[Union[dict, str]] = None,
952+
data: Optional[Union[dict, str, bytes]] = None,
915953
json: Optional[Union[dict, str]] = None,
916954
params: Optional[dict] = None,
917955
headers: Optional[dict] = None,
918956
stream: bool = False,
919957
timeout: Optional[Union[float, httpx.Timeout]] = None,
958+
content: Any = None,
920959
):
921960
try:
961+
# Prepare data/content parameters to prevent httpx DeprecationWarning (memory leak fix)
962+
request_data, request_content = _prepare_request_data_and_content(data, content)
963+
922964
if timeout is not None:
923965
req = self.client.build_request(
924-
"PUT", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore
966+
"PUT", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
925967
)
926968
else:
927969
req = self.client.build_request(
928-
"PUT", url, data=data, json=json, params=params, headers=headers # type: ignore
970+
"PUT", url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
929971
)
930972
response = self.client.send(req, stream=stream)
931973
return response
@@ -941,21 +983,25 @@ def put(
941983
def delete(
942984
self,
943985
url: str,
944-
data: Optional[Union[dict, str]] = None, # type: ignore
986+
data: Optional[Union[dict, str, bytes]] = None, # type: ignore
945987
json: Optional[dict] = None,
946988
params: Optional[dict] = None,
947989
headers: Optional[dict] = None,
948990
timeout: Optional[Union[float, httpx.Timeout]] = None,
949991
stream: bool = False,
992+
content: Any = None,
950993
):
951994
try:
995+
# Prepare data/content parameters to prevent httpx DeprecationWarning (memory leak fix)
996+
request_data, request_content = _prepare_request_data_and_content(data, content)
997+
952998
if timeout is not None:
953999
req = self.client.build_request(
954-
"DELETE", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore
1000+
"DELETE", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
9551001
)
9561002
else:
9571003
req = self.client.build_request(
958-
"DELETE", url, data=data, json=json, params=params, headers=headers # type: ignore
1004+
"DELETE", url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
9591005
)
9601006
response = self.client.send(req, stream=stream)
9611007
response.raise_for_status()

0 commit comments

Comments
 (0)