Skip to content

Commit 4e177fd

Browse files
CaselITvytas7
andcommitted
fix(asgi.App): correctly pass params when using orjson.dumps (#2102)
* fix: this change fixes an issue when using orjson as json serializer with asyncio * docs: improve newsfragment * test: ensure _serialize_async_b is also tested using the app * docs(newsfragments): capitalize JSON Co-authored-by: Vytautas Liuolia <vytautas.liuolia@gmail.com>
1 parent 83ef5b1 commit 4e177fd

3 files changed

Lines changed: 51 additions & 3 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The ``orjson`` library now works correctly when used as JSON serializer in
2+
the media handlers in the ASGI version of Falcon.

falcon/media/json.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,9 @@ def _serialize_s(self, media, content_type=None) -> bytes:
181181
async def _serialize_async_s(self, media, content_type) -> bytes:
182182
return self._dumps(media).encode()
183183

184-
def _serialize_b(self, media, content_type) -> bytes:
184+
# NOTE(kgriffs): Make content_type a kwarg to support the
185+
# Request.render_body() shortcut optimization.
186+
def _serialize_b(self, media, content_type=None) -> bytes:
185187
return self._dumps(media)
186188

187189
async def _serialize_async_b(self, media, content_type) -> bytes:

tests/test_media_handlers.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,30 @@
5555
(mujson.loads, b'{"test": "value"}', {'test': 'value'}),
5656
(ujson.loads, b'{"test": "value"}', {'test': 'value'}),
5757
]
58+
ALL_JSON_IMPL = [
59+
(json.dumps, json.loads),
60+
(partial(mujson.dumps, ensure_ascii=True), mujson.loads),
61+
(ujson.dumps, ujson.loads),
62+
]
5863

5964

6065
if orjson:
6166
SERIALIZATION_PARAM_LIST += [
6267
(orjson.dumps, {'test': 'value'}, b'{"test":"value"}'),
6368
]
64-
6569
DESERIALIZATION_PARAM_LIST += [
6670
(orjson.loads, b'{"test": "value"}', {'test': 'value'}),
6771
]
72+
ALL_JSON_IMPL += [(orjson.dumps, orjson.loads)]
73+
6874
if rapidjson:
6975
SERIALIZATION_PARAM_LIST += [
7076
(rapidjson.dumps, {'test': 'value'}, b'{"test":"value"}'),
7177
]
72-
7378
DESERIALIZATION_PARAM_LIST += [
7479
(rapidjson.loads, b'{"test": "value"}', {'test': 'value'}),
7580
]
81+
ALL_JSON_IMPL += [(rapidjson.dumps, rapidjson.loads)]
7682

7783

7884
@pytest.mark.parametrize('func, body, expected', SERIALIZATION_PARAM_LIST)
@@ -114,6 +120,44 @@ def test_deserialization(asgi, func, body, expected):
114120
assert result == expected
115121

116122

123+
@pytest.mark.parametrize('dumps, loads', ALL_JSON_IMPL)
124+
@pytest.mark.parametrize('subclass', (True, False))
125+
def test_full_app(asgi, dumps, loads, subclass):
126+
if subclass:
127+
128+
class JSONHandlerSubclass(media.JSONHandler):
129+
pass
130+
131+
handler = JSONHandlerSubclass(dumps=dumps, loads=loads)
132+
assert handler._serialize_sync is None
133+
assert handler._deserialize_sync is None
134+
else:
135+
handler = media.JSONHandler(dumps=dumps, loads=loads)
136+
assert handler._serialize_sync is not None
137+
assert handler._deserialize_sync is not None
138+
app = create_app(asgi)
139+
app.req_options.media_handlers[falcon.MEDIA_JSON] = handler
140+
app.resp_options.media_handlers[falcon.MEDIA_JSON] = handler
141+
142+
if asgi:
143+
144+
class Resp:
145+
async def on_get(self, req, res):
146+
res.media = await req.get_media()
147+
148+
else:
149+
150+
class Resp:
151+
def on_get(self, req, res):
152+
res.media = req.get_media()
153+
154+
app.add_route('/go', Resp())
155+
156+
data = {'foo': 123, 'bar': [2, 3], 'baz': {'x': 'y'}}
157+
res = testing.simulate_get(app, '/go', json=data)
158+
assert res.json == data
159+
160+
117161
@pytest.mark.parametrize('monkeypatch_resolver', [True, False])
118162
@pytest.mark.parametrize(
119163
'handler_mt',

0 commit comments

Comments
 (0)