-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
port Flask's JSONMixin implementation
- Loading branch information
Showing
4 changed files
with
148 additions
and
25 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
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,29 +1,109 @@ | ||
from __future__ import absolute_import | ||
|
||
from werkzeug.exceptions import BadRequest | ||
from werkzeug.utils import cached_property | ||
|
||
try: | ||
from simplejson import loads | ||
import simplejson as json | ||
except ImportError: | ||
from json import loads | ||
import json | ||
|
||
|
||
class JSONMixin(object): | ||
"""Add json method to a request object. This will parse the input | ||
data through simplejson if possible. | ||
"""Mixin to parse :attr:`data` as JSON. Can be mixed in for both | ||
:class:`~werkzeug.wrappers.Request` and | ||
:class:`~werkzeug.wrappers.Response` classes. | ||
:exc:`~werkzeug.exceptions.BadRequest` will be raised if the | ||
content-type is not json or if the data itself cannot be parsed as | ||
json. | ||
If `simplejson`_ is installed it is preferred over Python's built-in | ||
:mod:`json` module. | ||
.. _simplejson: https://simplejson.readthedocs.io/en/latest/ | ||
""" | ||
|
||
@cached_property | ||
#: A module or other object that has ``dumps`` and ``loads`` | ||
#: functions that match the API of the built-in :mod:`json` module. | ||
json_module = json | ||
|
||
@property | ||
def json(self): | ||
"""Get the result of simplejson.loads if possible.""" | ||
if 'json' not in self.environ.get('CONTENT_TYPE', ''): | ||
raise BadRequest('Not a JSON request') | ||
"""The parsed JSON data if :attr:`mimetype` indicates JSON | ||
(:mimetype:`application/json`, see :meth:`is_json`). | ||
Calls :meth:`get_json` with default arguments. | ||
""" | ||
return self.get_json() | ||
|
||
@property | ||
def is_json(self): | ||
"""Check if the mimetype indicates JSON data, either | ||
:mimetype:`application/json` or :mimetype:`application/*+json`. | ||
""" | ||
mt = self.mimetype | ||
return ( | ||
mt == 'application/json' | ||
or mt.startswith('application/') | ||
and mt.endswith('+json') | ||
) | ||
|
||
def _get_data_for_json(self, cache): | ||
try: | ||
return self.get_data(cache=cache) | ||
except TypeError: | ||
# Response doesn't have cache param. | ||
return self.get_data() | ||
|
||
# Cached values for ``(silent=False, silent=True)``. Initialized | ||
# with sentinel values. | ||
_cached_json = (Ellipsis, Ellipsis) | ||
|
||
def get_json(self, force=False, silent=False, cache=True): | ||
"""Parse :attr:`data` as JSON. | ||
If the mimetype does not indicate JSON | ||
(:mimetype:`application/json`, see :meth:`is_json`), this | ||
returns ``None``. | ||
If parsing fails, :meth:`on_json_loading_failed` is called and | ||
its return value is used as the return value. | ||
:param force: Ignore the mimetype and always try to parse JSON. | ||
:param silent: Silence parsing errors and return ``None`` | ||
instead. | ||
:param cache: Store the parsed JSON to return for subsequent | ||
calls. | ||
""" | ||
if cache and self._cached_json[silent] is not Ellipsis: | ||
return self._cached_json[silent] | ||
|
||
if not (force or self.is_json): | ||
return None | ||
|
||
data = self._get_data_for_json(cache=cache) | ||
|
||
try: | ||
return loads(self.data.decode(self.charset, self.encoding_errors)) | ||
except Exception: | ||
raise BadRequest('Unable to read JSON request') | ||
rv = json.loads(data) | ||
except ValueError as e: | ||
if silent: | ||
rv = None | ||
|
||
if cache: | ||
normal_rv, _ = self._cached_json | ||
self._cached_json = (normal_rv, rv) | ||
else: | ||
rv = self.on_json_loading_failed(e) | ||
|
||
if cache: | ||
_, silent_rv = self._cached_json | ||
self._cached_json = (rv, silent_rv) | ||
else: | ||
if cache: | ||
self._cached_json = (rv, rv) | ||
|
||
return rv | ||
|
||
def on_json_loading_failed(self, e): | ||
"""Called if :meth:`get_json` parsing fails and isn't silenced. | ||
If this method returns a value, it is used as the return value | ||
for :meth:`get_json`. The default implementation raises a | ||
:exc:`~BadRequest` exception. | ||
""" | ||
raise BadRequest('Failed to decode JSON object: {0}'.format(e)) |