Use Werkzeug's JSONMixin class and update tests
Flask's JSONMixin class was moved to Werkzeug>=0.15 Co-authored-by: Jeffrey Eliasen <jeff@jke.net>
This commit is contained in:
parent
d0bfb065e5
commit
8590d65a57
5 changed files with 18 additions and 131 deletions
|
|
@ -9,6 +9,8 @@ Version 1.1.0
|
||||||
|
|
||||||
Unreleased
|
Unreleased
|
||||||
|
|
||||||
|
- Bump minimum Werkzeug version to >= 0.15.
|
||||||
|
- Drop support for Python 3.4.
|
||||||
- :meth:`flask.RequestContext.copy` includes the current session
|
- :meth:`flask.RequestContext.copy` includes the current session
|
||||||
object in the request context copy. This prevents ``session``
|
object in the request context copy. This prevents ``session``
|
||||||
pointing to an out-of-date object. (`#2935`_)
|
pointing to an out-of-date object. (`#2935`_)
|
||||||
|
|
@ -27,12 +29,17 @@ Unreleased
|
||||||
- :attr:`Flask.jinja_options` is a ``dict`` instead of an
|
- :attr:`Flask.jinja_options` is a ``dict`` instead of an
|
||||||
``ImmutableDict`` to allow easier configuration. Changes must still
|
``ImmutableDict`` to allow easier configuration. Changes must still
|
||||||
be made before creating the environment. :pr:`3190`
|
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
|
.. _#2935: https://github.com/pallets/flask/issues/2935
|
||||||
.. _#2957: https://github.com/pallets/flask/issues/2957
|
.. _#2957: https://github.com/pallets/flask/issues/2957
|
||||||
.. _#2994: https://github.com/pallets/flask/pull/2994
|
.. _#2994: https://github.com/pallets/flask/pull/2994
|
||||||
.. _#3059: https://github.com/pallets/flask/pull/3059
|
.. _#3059: https://github.com/pallets/flask/pull/3059
|
||||||
.. _#3179: https://github.com/pallets/flask/pull/3179
|
.. _#3179: https://github.com/pallets/flask/pull/3179
|
||||||
|
.. _#3125: https://github.com/pallets/flask/pull/3125
|
||||||
|
|
||||||
|
|
||||||
Version 1.0.3
|
Version 1.0.3
|
||||||
|
|
|
||||||
|
|
@ -11,99 +11,17 @@
|
||||||
|
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
|
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
|
||||||
|
from werkzeug.wrappers.json import JSONMixin as _JSONMixin
|
||||||
|
|
||||||
from flask import json
|
from flask import json
|
||||||
from flask.globals import current_app
|
from flask.globals import current_app
|
||||||
|
|
||||||
|
|
||||||
class JSONMixin(object):
|
class JSONMixin(_JSONMixin):
|
||||||
"""Common mixin for both request and response objects to provide JSON
|
json_module = 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
|
|
||||||
|
|
||||||
def on_json_loading_failed(self, e):
|
def on_json_loading_failed(self, e):
|
||||||
"""Called if :meth:`get_json` parsing fails and isn't silenced. If
|
if current_app and current_app.debug:
|
||||||
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:
|
|
||||||
raise BadRequest("Failed to decode JSON object: {0}".format(e))
|
raise BadRequest("Failed to decode JSON object: {0}".format(e))
|
||||||
|
|
||||||
raise BadRequest()
|
raise BadRequest()
|
||||||
|
|
|
||||||
2
setup.py
2
setup.py
|
|
@ -36,7 +36,7 @@ setup(
|
||||||
platforms="any",
|
platforms="any",
|
||||||
python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
|
python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"Werkzeug>=0.14",
|
"Werkzeug>=0.15",
|
||||||
"Jinja2>=2.10.1",
|
"Jinja2>=2.10.1",
|
||||||
"itsdangerous>=0.24",
|
"itsdangerous>=0.24",
|
||||||
"click>=5.1",
|
"click>=5.1",
|
||||||
|
|
|
||||||
|
|
@ -92,33 +92,9 @@ class TestJSON(object):
|
||||||
assert json.detect_encoding(data) == encoding
|
assert json.detect_encoding(data) == encoding
|
||||||
assert json.loads(data) == value
|
assert json.loads(data) == value
|
||||||
|
|
||||||
def test_ignore_cached_json(self, app):
|
@pytest.mark.parametrize("debug", (True, False))
|
||||||
with app.test_request_context(
|
def test_bad_request_debug_message(self, app, client, debug):
|
||||||
"/", method="POST", data="malformed", content_type="application/json"
|
app.config["DEBUG"] = debug
|
||||||
):
|
|
||||||
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
|
|
||||||
app.config["TRAP_BAD_REQUEST_ERRORS"] = False
|
app.config["TRAP_BAD_REQUEST_ERRORS"] = False
|
||||||
|
|
||||||
@app.route("/json", methods=["POST"])
|
@app.route("/json", methods=["POST"])
|
||||||
|
|
@ -128,22 +104,8 @@ class TestJSON(object):
|
||||||
|
|
||||||
rv = client.post("/json", data=None, content_type="application/json")
|
rv = client.post("/json", data=None, content_type="application/json")
|
||||||
assert rv.status_code == 400
|
assert rv.status_code == 400
|
||||||
assert b"Failed to decode JSON object" in rv.data
|
contains = b"Failed to decode JSON object" in rv.data
|
||||||
|
assert contains == debug
|
||||||
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
|
|
||||||
|
|
||||||
def test_json_bad_requests(self, app, client):
|
def test_json_bad_requests(self, app, client):
|
||||||
@app.route("/json", methods=["POST"])
|
@app.route("/json", methods=["POST"])
|
||||||
|
|
|
||||||
2
tox.ini
2
tox.ini
|
|
@ -14,7 +14,7 @@ deps =
|
||||||
blinker
|
blinker
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
|
||||||
lowest: Werkzeug==0.14
|
lowest: Werkzeug==0.15
|
||||||
lowest: Jinja2==2.10
|
lowest: Jinja2==2.10
|
||||||
lowest: itsdangerous==0.24
|
lowest: itsdangerous==0.24
|
||||||
lowest: Click==5.1
|
lowest: Click==5.1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue