Skip to content

Commit 5f8e675

Browse files
Fix: Raise helpful TypeError for invalid list 'data'
1 parent 8910202 commit 5f8e675

3 files changed

Lines changed: 89 additions & 0 deletions

File tree

httpx/_client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,16 @@ def build_request(
363363
364364
[0]: /advanced/clients/#request-instances
365365
"""
366+
# Validate data parameter for better error messages
367+
if data is not None and isinstance(data, list):
368+
# Check if this looks like invalid JSON array (list of dicts/strings)
369+
# but allow valid multipart form data (list of 2-item tuples)
370+
if data and all(isinstance(item, (dict, str, int, float, bool)) for item in data):
371+
raise TypeError(
372+
"Invalid value for 'data'. To send a JSON array, use the 'json' parameter. "
373+
"For form data, use a dictionary or a list of 2-item tuples."
374+
)
375+
366376
url = self._merge_url(url)
367377
headers = self._merge_headers(headers)
368378
cookies = self._merge_cookies(cookies)

tests/client/test_async_client.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,43 @@ async def test_server_extensions(server):
373373
response = await client.get(url)
374374
assert response.status_code == 200
375375
assert response.extensions["http_version"] == b"HTTP/1.1"
376+
377+
378+
INVALID_DATA_FORMATS_ASYNC = [
379+
pytest.param([{"a": "b"}], id="list-of-dicts"),
380+
pytest.param(["a", "b", "c"], id="list-of-strings"),
381+
pytest.param([1, 2, 3], id="list-of-integers"),
382+
]
383+
384+
385+
@pytest.mark.anyio
386+
@pytest.mark.parametrize("invalid_data", INVALID_DATA_FORMATS_ASYNC)
387+
async def test_async_build_request_with_invalid_data_list(invalid_data):
388+
"""
389+
Verify that AsyncClient.build_request raises a helpful TypeError for invalid list formats.
390+
"""
391+
async with httpx.AsyncClient() as client:
392+
expected_message = (
393+
"Invalid value for 'data'. To send a JSON array, use the 'json' parameter. "
394+
"For form data, use a dictionary or a list of 2-item tuples."
395+
)
396+
with pytest.raises(TypeError, match=expected_message):
397+
client.build_request("POST", "https://example.com", data=invalid_data)
398+
399+
400+
@pytest.mark.anyio
401+
async def test_async_build_request_with_valid_data_formats():
402+
"""
403+
Verify that AsyncClient.build_request accepts valid data formats without raising our custom TypeError.
404+
"""
405+
async with httpx.AsyncClient() as client:
406+
# Test with a dictionary
407+
request = client.build_request("POST", "https://example.com", data={"a": "b"})
408+
assert isinstance(request, httpx.Request)
409+
410+
# Test with a list of 2-item tuples (for multipart)
411+
# This is a valid use case and should not raise our TypeError.
412+
# We explicitly catch and ignore the DeprecationWarning that httpx raises in this specific case.
413+
with pytest.warns(DeprecationWarning):
414+
request = client.build_request("POST", "https://example.com", data=[("a", "b")])
415+
assert isinstance(request, httpx.Request)

tests/client/test_client.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,42 @@ def cp1252_but_no_content_type(request):
460460
assert response.reason_phrase == "OK"
461461
assert response.encoding == "ISO-8859-1"
462462
assert response.text == text
463+
464+
465+
INVALID_DATA_FORMATS_SYNC = [
466+
pytest.param([{"a": "b"}], id="list-of-dicts"),
467+
pytest.param(["a", "b", "c"], id="list-of-strings"),
468+
pytest.param([1, 2, 3], id="list-of-integers"),
469+
]
470+
471+
472+
@pytest.mark.parametrize("invalid_data", INVALID_DATA_FORMATS_SYNC)
473+
def test_sync_build_request_with_invalid_data_list(invalid_data):
474+
"""
475+
Verify that Client.build_request raises a helpful TypeError for invalid list formats.
476+
"""
477+
client = httpx.Client()
478+
expected_message = (
479+
"Invalid value for 'data'. To send a JSON array, use the 'json' parameter. "
480+
"For form data, use a dictionary or a list of 2-item tuples."
481+
)
482+
with pytest.raises(TypeError, match=expected_message):
483+
client.build_request("POST", "https://example.com", data=invalid_data)
484+
485+
486+
def test_sync_build_request_with_valid_data_formats():
487+
"""
488+
Verify that Client.build_request accepts valid data formats without raising our custom TypeError.
489+
"""
490+
client = httpx.Client()
491+
492+
# Test with a dictionary
493+
request = client.build_request("POST", "https://example.com", data={"a": "b"})
494+
assert isinstance(request, httpx.Request)
495+
496+
# Test with a list of 2-item tuples (for multipart)
497+
# This is a valid use case and should not raise our TypeError.
498+
# We explicitly catch and ignore the DeprecationWarning that httpx raises in this specific case.
499+
with pytest.warns(DeprecationWarning):
500+
request = client.build_request("POST", "https://example.com", data=[("a", "b")])
501+
assert isinstance(request, httpx.Request)

0 commit comments

Comments
 (0)