mirror of https://github.com/pallets/flask.git
Merge pull request #3181 from EtiennePelletier/Flask-3125
Use Werkzeug's JSONMixin class (moved from Flask) and update tests
This commit is contained in:
commit
5a49b96d0e
|
|
@ -9,6 +9,8 @@ Version 1.1.0
|
|||
|
||||
Unreleased
|
||||
|
||||
- Bump minimum Werkzeug version to >= 0.15.
|
||||
- Drop support for Python 3.4.
|
||||
- :meth:`flask.RequestContext.copy` includes the current session
|
||||
object in the request context copy. This prevents ``session``
|
||||
pointing to an out-of-date object. (`#2935`_)
|
||||
|
|
@ -27,12 +29,17 @@ Unreleased
|
|||
- :attr:`Flask.jinja_options` is a ``dict`` instead of an
|
||||
``ImmutableDict`` to allow easier configuration. Changes must still
|
||||
be made before creating the environment. :pr:`3190`
|
||||
- Flask's ``JSONMixin`` for the request and response wrappers was
|
||||
moved into Werkzeug. Use Werkzeug's version with Flask-specific
|
||||
support. This bumps the Werkzeug dependency to >= 0.15.
|
||||
:issue:`3125`
|
||||
|
||||
.. _#2935: https://github.com/pallets/flask/issues/2935
|
||||
.. _#2957: https://github.com/pallets/flask/issues/2957
|
||||
.. _#2994: https://github.com/pallets/flask/pull/2994
|
||||
.. _#3059: https://github.com/pallets/flask/pull/3059
|
||||
.. _#3179: https://github.com/pallets/flask/pull/3179
|
||||
.. _#3125: https://github.com/pallets/flask/pull/3125
|
||||
|
||||
|
||||
Version 1.0.3
|
||||
|
|
|
|||
|
|
@ -11,99 +11,17 @@
|
|||
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
|
||||
from werkzeug.wrappers.json import JSONMixin as _JSONMixin
|
||||
|
||||
from flask import json
|
||||
from flask.globals import current_app
|
||||
|
||||
|
||||
class JSONMixin(object):
|
||||
"""Common mixin for both request and response objects to provide JSON
|
||||
parsing capabilities.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
"""
|
||||
|
||||
_cached_json = (Ellipsis, Ellipsis)
|
||||
|
||||
@property
|
||||
def is_json(self):
|
||||
"""Check if the mimetype indicates JSON data, either
|
||||
:mimetype:`application/json` or :mimetype:`application/*+json`.
|
||||
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
mt = self.mimetype
|
||||
return (
|
||||
mt == "application/json"
|
||||
or (mt.startswith("application/"))
|
||||
and mt.endswith("+json")
|
||||
)
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
"""This will contain the parsed JSON data if the mimetype indicates
|
||||
JSON (:mimetype:`application/json`, see :meth:`is_json`), otherwise it
|
||||
will be ``None``.
|
||||
"""
|
||||
return self.get_json()
|
||||
|
||||
def _get_data_for_json(self, cache):
|
||||
return self.get_data(cache=cache)
|
||||
|
||||
def get_json(self, force=False, silent=False, cache=True):
|
||||
"""Parse and return the data as JSON. If the mimetype does not
|
||||
indicate JSON (:mimetype:`application/json`, see
|
||||
:meth:`is_json`), this returns ``None`` unless ``force`` is
|
||||
true. 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:
|
||||
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
|
||||
class JSONMixin(_JSONMixin):
|
||||
json_module = json
|
||||
|
||||
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
|
||||
:class:`BadRequest` exception.
|
||||
|
||||
.. versionchanged:: 0.10
|
||||
Raise a :exc:`BadRequest` error instead of returning an error
|
||||
message as JSON. If you want that behavior you can add it by
|
||||
subclassing.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
if current_app is not None and current_app.debug:
|
||||
if current_app and current_app.debug:
|
||||
raise BadRequest("Failed to decode JSON object: {0}".format(e))
|
||||
|
||||
raise BadRequest()
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -36,7 +36,7 @@ setup(
|
|||
platforms="any",
|
||||
python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
|
||||
install_requires=[
|
||||
"Werkzeug>=0.14",
|
||||
"Werkzeug>=0.15",
|
||||
"Jinja2>=2.10.1",
|
||||
"itsdangerous>=0.24",
|
||||
"click>=5.1",
|
||||
|
|
|
|||
|
|
@ -92,33 +92,9 @@ class TestJSON(object):
|
|||
assert json.detect_encoding(data) == encoding
|
||||
assert json.loads(data) == value
|
||||
|
||||
def test_ignore_cached_json(self, app):
|
||||
with app.test_request_context(
|
||||
"/", method="POST", data="malformed", content_type="application/json"
|
||||
):
|
||||
assert flask.request.get_json(silent=True, cache=True) is None
|
||||
with pytest.raises(BadRequest):
|
||||
flask.request.get_json(silent=False, cache=False)
|
||||
|
||||
def test_different_silent_on_bad_request(self, app):
|
||||
with app.test_request_context(
|
||||
"/", method="POST", data="malformed", content_type="application/json"
|
||||
):
|
||||
assert flask.request.get_json(silent=True) is None
|
||||
with pytest.raises(BadRequest):
|
||||
flask.request.get_json(silent=False)
|
||||
|
||||
def test_different_silent_on_normal_request(self, app):
|
||||
with app.test_request_context("/", method="POST", json={"foo": "bar"}):
|
||||
silent_rv = flask.request.get_json(silent=True)
|
||||
normal_rv = flask.request.get_json(silent=False)
|
||||
assert silent_rv is normal_rv
|
||||
assert normal_rv["foo"] == "bar"
|
||||
|
||||
def test_post_empty_json_adds_exception_to_response_content_in_debug(
|
||||
self, app, client
|
||||
):
|
||||
app.config["DEBUG"] = True
|
||||
@pytest.mark.parametrize("debug", (True, False))
|
||||
def test_bad_request_debug_message(self, app, client, debug):
|
||||
app.config["DEBUG"] = debug
|
||||
app.config["TRAP_BAD_REQUEST_ERRORS"] = False
|
||||
|
||||
@app.route("/json", methods=["POST"])
|
||||
|
|
@ -128,22 +104,8 @@ class TestJSON(object):
|
|||
|
||||
rv = client.post("/json", data=None, content_type="application/json")
|
||||
assert rv.status_code == 400
|
||||
assert b"Failed to decode JSON object" in rv.data
|
||||
|
||||
def test_post_empty_json_wont_add_exception_to_response_if_no_debug(
|
||||
self, app, client
|
||||
):
|
||||
app.config["DEBUG"] = False
|
||||
app.config["TRAP_BAD_REQUEST_ERRORS"] = False
|
||||
|
||||
@app.route("/json", methods=["POST"])
|
||||
def post_json():
|
||||
flask.request.get_json()
|
||||
return None
|
||||
|
||||
rv = client.post("/json", data=None, content_type="application/json")
|
||||
assert rv.status_code == 400
|
||||
assert b"Failed to decode JSON object" not in rv.data
|
||||
contains = b"Failed to decode JSON object" in rv.data
|
||||
assert contains == debug
|
||||
|
||||
def test_json_bad_requests(self, app, client):
|
||||
@app.route("/json", methods=["POST"])
|
||||
|
|
|
|||
Loading…
Reference in New Issue