From d88f81df9f4283d0d915890e51ff2b31d6695f90 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 28 Mar 2022 17:48:36 -0600 Subject: [PATCH] Handle Werkzeug 2.1.0 change to `Request.get_json()`. pallets/werkzeug#2339 changed the behavior of `Request.get_json()` and the `Request.json` property to raise a `BadRequest` if `Request.get_json()` is called without `silent=True`, or the `Request.json` property is accessed, and the content type is not `"application/json"`. Argument parsing allows parsing from multiple locations, and defaults to `["json", "values"]`, but if the locations include `"json"` and the content type is not `"application/json"`, a `BadRequest` is now raised with Werkzeug >= 2.1.0. Invoking `Request.get_json()` with the `silent=True` parameter now handles the situation where `"json"` is included in the locations, but the content type is not `"application/json"`. --- flask_restx/reqparse.py | 10 ++++++++-- tests/test_reqparse.py | 16 +++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/flask_restx/reqparse.py b/flask_restx/reqparse.py index 63260660..6fc327b9 100644 --- a/flask_restx/reqparse.py +++ b/flask_restx/reqparse.py @@ -138,7 +138,10 @@ def source(self, request): :param request: The flask request object to parse arguments from """ if isinstance(self.location, six.string_types): - value = getattr(request, self.location, MultiDict()) + if self.location in {"json", "get_json"}: + value = request.get_json(silent=True) + else: + value = getattr(request, self.location, MultiDict()) if callable(value): value = value() if value is not None: @@ -146,7 +149,10 @@ def source(self, request): else: values = MultiDict() for l in self.location: - value = getattr(request, l, None) + if l in {"json", "get_json"}: + value = request.get_json(silent=True) + else: + value = getattr(request, l, None) if callable(value): value = value() if value is not None: diff --git a/tests/test_reqparse.py b/tests/test_reqparse.py index 18710f3b..2c0689ab 100644 --- a/tests/test_reqparse.py +++ b/tests/test_reqparse.py @@ -41,8 +41,9 @@ def test_help(self, app, mocker): ) parser = RequestParser() parser.add_argument("foo", choices=("one", "two"), help="Bad choice.") - req = mocker.Mock(["values"]) + req = mocker.Mock(["values", "get_json"]) req.values = MultiDict([("foo", "three")]) + req.get_json.return_value = None with pytest.raises(BadRequest): parser.parse_args(req) expected = { @@ -58,7 +59,8 @@ def test_no_help(self, app, mocker): ) parser = RequestParser() parser.add_argument("foo", choices=["one", "two"]) - req = mocker.Mock(["values"]) + req = mocker.Mock(["values", "get_json"]) + req.get_json.return_value = None req.values = MultiDict([("foo", "three")]) with pytest.raises(BadRequest): parser.parse_args(req) @@ -76,9 +78,9 @@ def test_viewargs(self, mocker): args = parser.parse_args(req) assert args["foo"] == "bar" - req = mocker.Mock() + req = mocker.Mock(["get_json"]) req.values = () - req.json = None + req.get_json.return_value = None req.view_args = {"foo": "bar"} parser = RequestParser() parser.add_argument("foo", store_missing=True) @@ -101,11 +103,10 @@ def test_parse_unicode_app(self, app): args = parser.parse_args() assert args["foo"] == "barß" - @pytest.mark.request_context("/bubble", method="post") + @pytest.mark.request_context("/bubble", method="post", content_type='application/json') def test_json_location(self): parser = RequestParser() parser.add_argument("foo", location="json", store_missing=True) - args = parser.parse_args() assert args["foo"] is None @@ -856,7 +857,8 @@ def test_source_bad_location(self, mocker): assert len(arg.source(req)) == 0 # yes, basically you don't find it def test_source_default_location(self, mocker): - req = mocker.Mock(["values"]) + req = mocker.Mock(["values", "get_json"]) + req.get_json.return_value = None req._get_child_mock = lambda **kwargs: MultiDict() arg = Argument("foo") assert arg.source(req) == req.values