forked from falconry/falcon
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(media) Initial Implementation of custom json handling (falconry#…
…1404) Closes falconry#1206 Closes falconry#1192
- Loading branch information
Showing
13 changed files
with
187 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,89 @@ | ||
from __future__ import absolute_import | ||
|
||
from functools import partial | ||
|
||
from falcon import errors | ||
from falcon.media import BaseHandler | ||
from falcon.util import compat | ||
from falcon.util import json | ||
|
||
|
||
class JSONHandler(BaseHandler): | ||
"""JSON media handler. | ||
This handler uses Python's :py:mod:`json` by default, but will | ||
use :py:mod:`ujson` if available. | ||
This handler uses Python's standard :py:mod:`json` library by default, but | ||
can be easily configured to use any of a number of third-party JSON | ||
libraries, depending on your needs. For example, you can often | ||
realize a significant performance boost under CPython by using an | ||
alternative library. Good options in this respect include `orjson`, | ||
`python-rapidjson`, and `mujson`. | ||
Note: | ||
If you are deploying to PyPy, we recommend sticking with the standard | ||
library's JSON implementation, since it will be faster in most cases | ||
as compared to a third-party library. | ||
Overriding the default JSON implementation is simply a matter of specifying | ||
the desired ``dumps`` and ``loads`` functions:: | ||
import falcon | ||
from falcon import media | ||
import rapidjson | ||
json_handler = media.JSONHandler( | ||
dumps=rapidjson.dumps, | ||
loads=rapidjson.loads, | ||
) | ||
extra_handlers = { | ||
'application/json': json_handler, | ||
} | ||
api = falcon.API() | ||
api.req_options.media_handlers.update(extra_handlers) | ||
api.resp_options.media_handlers.update(extra_handlers) | ||
By default, ``ensure_ascii`` is passed to the ``json.dumps`` function. | ||
If you override the ``dumps`` function, you will need to explicitly set | ||
``ensure_ascii`` to ``False`` in order to enable the serialization of | ||
Unicode characters to UTF-8. This is easily done by using | ||
``functools.partial`` to apply the desired keyword argument. In fact, you | ||
can use this same technique to customize any option supported by the | ||
``dumps`` and ``loads`` functions:: | ||
from functools import partial | ||
from falcon import media | ||
import rapidjson | ||
json_handler = media.JSONHandler( | ||
dumps=partial( | ||
rapidjson.dumps, | ||
ensure_ascii=False, sort_keys=True | ||
), | ||
) | ||
Keyword Arguments: | ||
dumps (func): Function to use when serializing JSON responses. | ||
loads (func): Function to use when deserializing JSON requests. | ||
""" | ||
|
||
def __init__(self, dumps=None, loads=None): | ||
self.dumps = dumps or partial(json.dumps, ensure_ascii=False) | ||
self.loads = loads or json.loads | ||
|
||
def deserialize(self, stream, content_type, content_length): | ||
try: | ||
return json.loads(stream.read().decode('utf-8')) | ||
return self.loads(stream.read().decode('utf-8')) | ||
except ValueError as err: | ||
raise errors.HTTPBadRequest( | ||
'Invalid JSON', | ||
'Could not parse JSON body - {0}'.format(err) | ||
) | ||
|
||
def serialize(self, media, content_type): | ||
result = json.dumps(media, ensure_ascii=False) | ||
result = self.dumps(media) | ||
|
||
if compat.PY3 or not isinstance(result, bytes): | ||
if not isinstance(result, bytes): | ||
return result.encode('utf-8') | ||
|
||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,97 @@ | ||
from functools import partial | ||
import io | ||
import json | ||
import platform | ||
import sys | ||
|
||
import mujson | ||
import pytest | ||
import ujson | ||
|
||
from falcon import media | ||
|
||
orjson = None | ||
rapidjson = None | ||
if sys.version_info >= (3, 5): | ||
import rapidjson | ||
|
||
if platform.python_implementation() == 'CPython': | ||
import orjson | ||
|
||
|
||
COMMON_SERIALIZATION_PARAM_LIST = [ | ||
# Default json.dumps, with only ascii | ||
(None, {'test': 'value'}, b'{"test":"value"}'), | ||
(mujson.dumps, {'test': 'value'}, b'{"test":"value"}'), | ||
(ujson.dumps, {'test': 'value'}, b'{"test":"value"}'), | ||
(partial(lambda media, **kwargs: json.dumps([media, kwargs]), | ||
ensure_ascii=True), | ||
{'test': 'value'}, | ||
b'[{"test":"value"},{"ensure_ascii":true}]'), | ||
] | ||
|
||
COMMON_DESERIALIZATION_PARAM_LIST = [ | ||
(None, b'[1, 2]', [1, 2]), | ||
(partial(json.loads, | ||
object_hook=lambda data: {k: v.upper() for k, v in data.items()}), | ||
b'{"key": "value"}', | ||
{'key': 'VALUE'}), | ||
|
||
(mujson.loads, b'{"test": "value"}', {'test': 'value'}), | ||
(ujson.loads, b'{"test": "value"}', {'test': 'value'}), | ||
] | ||
|
||
YEN = b'\xc2\xa5' | ||
|
||
if orjson: | ||
SERIALIZATION_PARAM_LIST = COMMON_SERIALIZATION_PARAM_LIST + [ | ||
# Default json.dumps, with non-ascii characters | ||
(None, {'yen': YEN.decode()}, b'{"yen":"' + YEN + b'"}'), | ||
|
||
# Extra Python 3 json libraries | ||
(rapidjson.dumps, {'test': 'value'}, b'{"test":"value"}'), | ||
(orjson.dumps, {'test': 'value'}, b'{"test":"value"}'), | ||
] | ||
|
||
DESERIALIZATION_PARAM_LIST = COMMON_DESERIALIZATION_PARAM_LIST + [ | ||
(rapidjson.loads, b'{"test": "value"}', {'test': 'value'}), | ||
(orjson.loads, b'{"test": "value"}', {'test': 'value'}), | ||
] | ||
elif rapidjson: | ||
SERIALIZATION_PARAM_LIST = COMMON_SERIALIZATION_PARAM_LIST + [ | ||
# Default json.dumps, with non-ascii characters | ||
(None, {'yen': YEN.decode()}, b'{"yen":"' + YEN + b'"}'), | ||
|
||
# Extra Python 3 json libraries | ||
(rapidjson.dumps, {'test': 'value'}, b'{"test":"value"}'), | ||
] | ||
|
||
DESERIALIZATION_PARAM_LIST = COMMON_DESERIALIZATION_PARAM_LIST + [ | ||
(rapidjson.loads, b'{"test": "value"}', {'test': 'value'}), | ||
] | ||
else: | ||
SERIALIZATION_PARAM_LIST = COMMON_SERIALIZATION_PARAM_LIST + [ | ||
# Default json.dumps, with non-ascii characters | ||
(None, {'yen': YEN.decode('utf-8')}, b'{"yen":"' + YEN + b'"}'), | ||
] | ||
DESERIALIZATION_PARAM_LIST = COMMON_DESERIALIZATION_PARAM_LIST | ||
|
||
|
||
@pytest.mark.parametrize('func, body, expected', SERIALIZATION_PARAM_LIST) | ||
def test_serialization(func, body, expected): | ||
JH = media.JSONHandler(dumps=func) | ||
|
||
# NOTE(nZac) PyPy and CPython render the final string differently. One | ||
# includes spaces and the other doesn't. This replace will normalize that. | ||
assert JH.serialize(body, b'application/javacript').replace(b' ', b'') == expected # noqa | ||
|
||
def test_base_handler_contract(): | ||
class TestHandler(media.BaseHandler): | ||
pass | ||
|
||
with pytest.raises(TypeError) as err: | ||
TestHandler() | ||
@pytest.mark.parametrize('func, body, expected', DESERIALIZATION_PARAM_LIST) | ||
def test_deserialization(func, body, expected): | ||
JH = media.JSONHandler(loads=func) | ||
|
||
assert 'abstract methods deserialize, serialize' in str(err.value) | ||
assert JH.deserialize( | ||
io.BytesIO(body), | ||
'application/javacript', | ||
len(body) | ||
) == expected |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,4 @@ | ||
try: | ||
import ujson as json | ||
except ImportError: | ||
import json | ||
import json | ||
|
||
import falcon | ||
from falcon import testing | ||
|