Stricter data typechecking

This commit is contained in:
Tom Christie 2024-03-01 14:52:30 +00:00
parent f3eb3c90fd
commit cd03f62b72
2 changed files with 45 additions and 7 deletions

View File

@ -10,6 +10,7 @@ from typing import (
Iterable,
Iterator,
Mapping,
Tuple,
)
from urllib.parse import urlencode
@ -17,13 +18,14 @@ from ._exceptions import StreamClosed, StreamConsumed
from ._multipart import MultipartStream
from ._types import (
AsyncByteStream,
PrimitiveData,
RequestContent,
RequestData,
RequestFiles,
ResponseContent,
SyncByteStream,
)
from ._utils import peek_filelike_length, primitive_value_to_str
from ._utils import peek_filelike_length
__all__ = ["ByteStream"]
@ -133,15 +135,36 @@ def encode_content(
raise TypeError(f"Unexpected type for 'content', {type(content)!r}")
def _coerce_type(key: str, value: PrimitiveData) -> Tuple[str, str]:
if not isinstance(key, str):
raise TypeError(
f"Request data keys must be str. " f"Got type {type(key).__name__!r}."
)
if value is True:
return (key, "true")
elif value is False:
return (key, "false")
elif value is None:
return (key, "")
elif isinstance(value, (str, int, float)):
return (key, str(value))
raise TypeError(
f"Request data values must be str, int, float, bool, or None. "
f"Got type {type(value).__name__!r} for key {key!r}."
)
def encode_urlencoded_data(
data: RequestData,
) -> tuple[dict[str, str], ByteStream]:
plain_data = []
for key, value in data.items():
if isinstance(value, (list, tuple)):
plain_data.extend([(key, primitive_value_to_str(item)) for item in value])
plain_data.extend([_coerce_type(key, each) for each in value])
else:
plain_data.append((key, primitive_value_to_str(value)))
plain_data.append(_coerce_type(key, value))
body = urlencode(plain_data, doseq=True).encode("utf-8")
content_length = str(len(body))
content_type = "application/x-www-form-urlencoded"

View File

@ -200,7 +200,7 @@ async def test_urlencoded_content():
@pytest.mark.anyio
async def test_urlencoded_boolean():
request = httpx.Request(method, url, data={"example": True})
request = httpx.Request(method, url, data={"true": True, "false": False})
assert isinstance(request.stream, typing.Iterable)
assert isinstance(request.stream, typing.AsyncIterable)
@ -209,11 +209,11 @@ async def test_urlencoded_boolean():
assert request.headers == {
"Host": "www.example.com",
"Content-Length": "12",
"Content-Length": "21",
"Content-Type": "application/x-www-form-urlencoded",
}
assert sync_content == b"example=true"
assert async_content == b"example=true"
assert sync_content == b"true=true&false=false"
assert async_content == b"true=true&false=false"
@pytest.mark.anyio
@ -252,6 +252,21 @@ async def test_urlencoded_list():
assert async_content == b"example=a&example=1&example=true"
def test_urlencoded_invalid_key():
with pytest.raises(TypeError) as e:
httpx.Request(method, url, data={123: "value"}) # type: ignore
assert str(e.value) == "Request data keys must be str. Got type 'int'."
def test_urlencoded_invalid_value():
with pytest.raises(TypeError) as e:
httpx.Request(method, url, data={"key": {"this": "ain't json, buddy"}})
assert str(e.value) == (
"Request data values must be str, int, float, bool, or None. "
"Got type 'dict' for key 'key'."
)
@pytest.mark.anyio
async def test_multipart_files_content():
files = {"file": io.BytesIO(b"<file content>")}