Skip to content

Commit

Permalink
fix serialization of primitives in Response (#120)
Browse files Browse the repository at this point in the history
* fix serialization of primitives in Response
* remove shortcut, better to pass it properly through json_encode
* beautify release notes
  • Loading branch information
devkral authored Dec 20, 2024
1 parent 954c46d commit cfb9701
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 7 deletions.
6 changes: 6 additions & 0 deletions docs/en/docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ hide:

- Refactor `SessionMiddleware`.

## 0.11.9

### Fixed

- Fix serialization of primitives in the Response. Strip `"` by default.

## 0.11.8

### Fixed
Expand Down
15 changes: 10 additions & 5 deletions lilya/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,11 @@ def transform(cls, content: Any) -> Any:
transform_kwargs = {}
return json_encode(content, **transform_kwargs)

def make_response(self, content: Any) -> bytes | str:
def make_response(self, content: Any) -> bytes | memoryview:
"""
Makes the Response object type.
"""
# only handle empty string not empty bytes. Bytes are handled later
if content is None or content is NoReturn or content == "":
if content is None or content is NoReturn:
return b""
if isinstance(content, (bytes, memoryview)):
return content
Expand All @@ -113,6 +112,11 @@ def make_response(self, content: Any) -> bytes | str:
transform_kwargs = transform_kwargs.copy()
if self.encoders:
transform_kwargs["with_encoders"] = (*self.encoders, *ENCODER_TYPES.get())
# strip " from stringified primitives
transform_kwargs.setdefault(
"post_transform_fn",
lambda x: x.strip('"') if isinstance(x, str) else x.strip(b'"'),
)
content = json_encode(content, **transform_kwargs)

if isinstance(content, (bytes, memoryview)):
Expand Down Expand Up @@ -310,7 +314,7 @@ def __init__(
encoders=encoders,
)

def make_response(self, content: Any) -> bytes:
def make_response(self, content: Any) -> bytes | memoryview:
if content is NoReturn:
return b""
new_params = RESPONSE_TRANSFORM_KWARGS.get()
Expand Down Expand Up @@ -546,7 +550,8 @@ def make_response(
headers: Mapping[str, str] | None = None,
background: Task | None = None,
encoders: Sequence[Encoder | type[Encoder]] | None = None,
json_encode_extra_kwargs: dict | None = None,
# passing mutables as default argument is not a good style but here is no other way
json_encode_extra_kwargs: dict | None = {}, # noqa: B006
) -> Response:
"""
Build JSON responses from a given content and
Expand Down
31 changes: 29 additions & 2 deletions tests/test_make_response.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
from collections import deque
from dataclasses import dataclass

Expand All @@ -8,7 +9,7 @@
from pydantic import BaseModel

from lilya.encoders import Encoder, apply_structure
from lilya.responses import make_response
from lilya.responses import Response, make_response
from lilya.routing import Path
from lilya.testclient import create_client

Expand Down Expand Up @@ -130,7 +131,7 @@ def test_attrs_custom_make_response_list():
@pytest.mark.parametrize(
"json_encode_kwargs", [{}, {"json_encode_fn": orjson.dumps, "post_transform_fn": orjson.loads}]
)
@pytest.mark.parametrize("value", ["1", 2, 2.2, None], ids=["str", "int", "float", "none"])
@pytest.mark.parametrize("value", ["hello", 2, 2.2, None], ids=["str", "int", "float", "none"])
def test_primitive_responses(value, json_encode_kwargs):
def home():
return make_response(value, status_code=201, json_encode_extra_kwargs=json_encode_kwargs)
Expand All @@ -142,6 +143,32 @@ def home():
assert response.json() == value


@pytest.mark.parametrize(
"json_encode_kwargs", [{}, {"json_encode_fn": orjson.dumps, "post_transform_fn": orjson.loads}]
)
@pytest.mark.parametrize(
"value,result",
[
("hello", "hello"),
(b"hello", "hello"),
(2, "2"),
(2.2, "2.2"),
(None, ""),
(datetime.datetime(2014, 11, 10), "2014-11-10T00:00:00"),
],
ids=["str", "bytes", "int", "float", "none", "datetime"],
)
def test_classic_response(value, result, json_encode_kwargs):
def home():
return make_response(value, status_code=201, response_class=Response)

with create_client(routes=[Path("/", home)]) as client:
response = client.get("/")

assert response.status_code == 201
assert response.text == result


def test_dict_response():
def home():
return make_response({"message": "works"}, status_code=201)
Expand Down

0 comments on commit cfb9701

Please sign in to comment.