Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure JSON representation is compact. #3363 #3367

Merged
merged 7 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion httpx/_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ def encode_html(html: str) -> tuple[dict[str, str], ByteStream]:


def encode_json(json: Any) -> tuple[dict[str, str], ByteStream]:
body = json_dumps(json).encode("utf-8")
body = json_dumps(
json, ensure_ascii=False, separators=(",", ":"), allow_nan=False
).encode("utf-8")
content_length = str(len(body))
content_type = "application/json"
headers = {"Content-Length": content_length, "Content-Type": content_type}
Expand Down
4 changes: 2 additions & 2 deletions tests/client/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ async def test_async_auth_reads_response_body() -> None:
response = await client.get(url, auth=auth)

assert response.status_code == 200
assert response.json() == {"auth": '{"auth": "xyz"}'}
assert response.json() == {"auth": '{"auth":"xyz"}'}


def test_sync_auth_reads_response_body() -> None:
Expand All @@ -759,7 +759,7 @@ def test_sync_auth_reads_response_body() -> None:
response = client.get(url, auth=auth)

assert response.status_code == 200
assert response.json() == {"auth": '{"auth": "xyz"}'}
assert response.json() == {"auth": '{"auth":"xyz"}'}


@pytest.mark.anyio
Expand Down
8 changes: 4 additions & 4 deletions tests/models/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_json_encoded_data():
request.read()

assert request.headers["Content-Type"] == "application/json"
assert request.content == b'{"test": 123}'
assert request.content == b'{"test":123}'


def test_headers():
Expand All @@ -71,7 +71,7 @@ def test_headers():
assert request.headers == {
"Host": "example.org",
"Content-Type": "application/json",
"Content-Length": "13",
"Content-Length": "12",
}


Expand Down Expand Up @@ -183,12 +183,12 @@ def test_request_picklable():
assert pickle_request.method == "POST"
assert pickle_request.url.path == "/"
assert pickle_request.headers["Content-Type"] == "application/json"
assert pickle_request.content == b'{"test": 123}'
assert pickle_request.content == b'{"test":123}'
assert pickle_request.stream is not None
assert request.headers == {
"Host": "example.org",
"Content-Type": "application/json",
"content-length": "13",
"content-length": "12",
}


Expand Down
4 changes: 2 additions & 2 deletions tests/models/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ def test_response_json():

assert response.status_code == 200
assert response.reason_phrase == "OK"
assert response.json() == {"hello": "world"}
assert str(response.json()) == "{'hello': 'world'}"
assert response.headers == {
"Content-Length": "18",
"Content-Length": "17",
"Content-Type": "application/json",
}

Expand Down
2 changes: 1 addition & 1 deletion tests/models/test_whatwg.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

# URL test cases from...
# https://github.com/web-platform-tests/wpt/blob/master/url/resources/urltestdata.json
with open("tests/models/whatwg.json", "r") as input:
with open("tests/models/whatwg.json", "r", encoding="utf-8") as input:
test_cases = json.load(input)
test_cases = [
item
Expand Down
43 changes: 40 additions & 3 deletions tests/test_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest

import httpx
from httpx._content import encode_json
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just noticed this accidentally snuck in a private import.

@BERRADA-Omar would you be up for refactoring these tests?

We can test this against public API using httpx.Request(). Eg...

data = {...}
req = httpx.Request("POST", "https://www.example.com/", json=data)
assert req.content == ...
assert req.headers = ...


method = "POST"
url = "https://www.example.com"
Expand Down Expand Up @@ -173,11 +174,11 @@ async def test_json_content():

assert request.headers == {
"Host": "www.example.com",
"Content-Length": "19",
"Content-Length": "18",
"Content-Type": "application/json",
}
assert sync_content == b'{"Hello": "world!"}'
assert async_content == b'{"Hello": "world!"}'
assert sync_content == b'{"Hello":"world!"}'
assert async_content == b'{"Hello":"world!"}'


@pytest.mark.anyio
Expand Down Expand Up @@ -484,3 +485,39 @@ async def hello_world() -> typing.AsyncIterator[bytes]:
def test_response_invalid_argument():
with pytest.raises(TypeError):
httpx.Response(200, content=123) # type: ignore


def test_ensure_ascii_false_with_french_characters():
data = {"greeting": "Bonjour, ça va ?"}
headers, byte_stream = encode_json(data)
json_output = b"".join(byte_stream).decode("utf-8")

assert (
"ça va" in json_output
), "ensure_ascii=False should preserve French accented characters"
assert headers["Content-Type"] == "application/json"


def test_separators_for_compact_json():
data = {"clé": "valeur", "liste": [1, 2, 3]}
headers, byte_stream = encode_json(data)
json_output = b"".join(byte_stream).decode("utf-8")

assert (
json_output == '{"clé":"valeur","liste":[1,2,3]}'
), "separators=(',', ':') should produce a compact representation"
assert headers["Content-Type"] == "application/json"


def test_allow_nan_false():
data_with_nan = {"nombre": float("nan")}
data_with_inf = {"nombre": float("inf")}

with pytest.raises(
ValueError, match="Out of range float values are not JSON compliant"
):
encode_json(data_with_nan)
with pytest.raises(
ValueError, match="Out of range float values are not JSON compliant"
):
encode_json(data_with_inf)
2 changes: 1 addition & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def test_post(server):
"content-type: text/plain",
"Transfer-Encoding: chunked",
"",
'{"hello": "world"}',
'{"hello":"world"}',
]


Expand Down