Blame 423.patch

a1beae5
From bb72a51860ea8a42c928f69bdd44ad20b1f9ee7e Mon Sep 17 00:00:00 2001
a1beae5
From: "Stacy W. Smith" <stacys@netflix.com>
a1beae5
Date: Mon, 28 Mar 2022 17:48:36 -0600
a1beae5
Subject: [PATCH 1/2] Handle Werkzeug 2.1.0 change to `Request.get_json()`.
a1beae5
a1beae5
pallets/werkzeug#2339 changed the behavior of `Request.get_json()`
a1beae5
and the `Request.json` property to raise a `BadRequest` if `Request.get_json()`
a1beae5
is called without `silent=True`, or the `Request.json` property is accessed,
a1beae5
and the content type is not `"application/json"`.
a1beae5
a1beae5
Argument parsing allows parsing from multiple locations, and defaults to
a1beae5
`["json", "values"]`, but if the locations include `"json"` and the content
a1beae5
type is not `"application/json"`, a `BadRequest` is now raised with Werkzeug >= 2.1.0.
a1beae5
a1beae5
Invoking `Request.get_json()` with the `silent=True` parameter now handles
a1beae5
the situation where `"json"` is included in the locations, but the content type
a1beae5
is not `"application/json"`.
a1beae5
---
a1beae5
 flask_restx/reqparse.py | 10 ++++++++--
a1beae5
 tests/test_reqparse.py  | 18 +++++++++++-------
a1beae5
 2 files changed, 19 insertions(+), 9 deletions(-)
a1beae5
a1beae5
diff --git a/flask_restx/reqparse.py b/flask_restx/reqparse.py
a1beae5
index 63260660..6fc327b9 100644
a1beae5
--- a/flask_restx/reqparse.py
a1beae5
+++ b/flask_restx/reqparse.py
a1beae5
@@ -138,7 +138,10 @@ def source(self, request):
a1beae5
         :param request: The flask request object to parse arguments from
a1beae5
         """
a1beae5
         if isinstance(self.location, six.string_types):
a1beae5
-            value = getattr(request, self.location, MultiDict())
a1beae5
+            if self.location in {"json", "get_json"}:
a1beae5
+                value = request.get_json(silent=True)
a1beae5
+            else:
a1beae5
+                value = getattr(request, self.location, MultiDict())
a1beae5
             if callable(value):
a1beae5
                 value = value()
a1beae5
             if value is not None:
a1beae5
@@ -146,7 +149,10 @@ def source(self, request):
a1beae5
         else:
a1beae5
             values = MultiDict()
a1beae5
             for l in self.location:
a1beae5
-                value = getattr(request, l, None)
a1beae5
+                if l in {"json", "get_json"}:
a1beae5
+                    value = request.get_json(silent=True)
a1beae5
+                else:
a1beae5
+                    value = getattr(request, l, None)
a1beae5
                 if callable(value):
a1beae5
                     value = value()
a1beae5
                 if value is not None:
a1beae5
diff --git a/tests/test_reqparse.py b/tests/test_reqparse.py
a1beae5
index 18710f3b..174f2988 100644
a1beae5
--- a/tests/test_reqparse.py
a1beae5
+++ b/tests/test_reqparse.py
a1beae5
@@ -41,8 +41,9 @@ def test_help(self, app, mocker):
a1beae5
         )
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("foo", choices=("one", "two"), help="Bad choice.")
a1beae5
-        req = mocker.Mock(["values"])
a1beae5
+        req = mocker.Mock(["values", "get_json"])
a1beae5
         req.values = MultiDict([("foo", "three")])
a1beae5
+        req.get_json.return_value = None
a1beae5
         with pytest.raises(BadRequest):
a1beae5
             parser.parse_args(req)
a1beae5
         expected = {
a1beae5
@@ -58,7 +59,8 @@ def test_no_help(self, app, mocker):
a1beae5
         )
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("foo", choices=["one", "two"])
a1beae5
-        req = mocker.Mock(["values"])
a1beae5
+        req = mocker.Mock(["values", "get_json"])
a1beae5
+        req.get_json.return_value = None
a1beae5
         req.values = MultiDict([("foo", "three")])
a1beae5
         with pytest.raises(BadRequest):
a1beae5
             parser.parse_args(req)
a1beae5
@@ -76,9 +78,9 @@ def test_viewargs(self, mocker):
a1beae5
         args = parser.parse_args(req)
a1beae5
         assert args["foo"] == "bar"
a1beae5
a1beae5
-        req = mocker.Mock()
a1beae5
+        req = mocker.Mock(["get_json"])
a1beae5
         req.values = ()
a1beae5
-        req.json = None
a1beae5
+        req.get_json.return_value = None
a1beae5
         req.view_args = {"foo": "bar"}
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("foo", store_missing=True)
a1beae5
@@ -101,11 +103,12 @@ def test_parse_unicode_app(self, app):
a1beae5
             args = parser.parse_args()
a1beae5
             assert args["foo"] == "barß"
a1beae5
a1beae5
-    @pytest.mark.request_context("/bubble", method="post")
a1beae5
+    @pytest.mark.request_context(
a1beae5
+        "/bubble", method="post", content_type="application/json"
a1beae5
+    )
a1beae5
     def test_json_location(self):
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("foo", location="json", store_missing=True)
a1beae5
-
a1beae5
         args = parser.parse_args()
a1beae5
         assert args["foo"] is None
a1beae5
a1beae5
@@ -856,7 +859,8 @@ def test_source_bad_location(self, mocker):
a1beae5
         assert len(arg.source(req)) == 0  # yes, basically you don't find it
a1beae5
a1beae5
     def test_source_default_location(self, mocker):
a1beae5
-        req = mocker.Mock(["values"])
a1beae5
+        req = mocker.Mock(["values", "get_json"])
a1beae5
+        req.get_json.return_value = None
a1beae5
         req._get_child_mock = lambda **kwargs: MultiDict()
a1beae5
         arg = Argument("foo")
a1beae5
         assert arg.source(req) == req.values
a1beae5
a1beae5
From 57c5beb313e2b297c10ec3fb305c37c0760e9209 Mon Sep 17 00:00:00 2001
a1beae5
From: "Stacy W. Smith" <stacy@acm.org>
a1beae5
Date: Fri, 1 Apr 2022 15:04:45 -0600
a1beae5
Subject: [PATCH 2/2] Black formatting changes in modified files which are
a1beae5
 unrelated to the PR change.
a1beae5
a1beae5
---
a1beae5
 flask_restx/reqparse.py |  5 ++-
a1beae5
 tests/test_reqparse.py  | 84 ++++++++++++++++++++++++++++++++++-------
a1beae5
 2 files changed, 75 insertions(+), 14 deletions(-)
a1beae5
a1beae5
diff --git a/flask_restx/reqparse.py b/flask_restx/reqparse.py
a1beae5
index 6fc327b9..18ce6cf9 100644
a1beae5
--- a/flask_restx/reqparse.py
a1beae5
+++ b/flask_restx/reqparse.py
a1beae5
@@ -106,7 +106,10 @@ def __init__(
a1beae5
         required=False,
a1beae5
         ignore=False,
a1beae5
         type=text_type,
a1beae5
-        location=("json", "values",),
a1beae5
+        location=(
a1beae5
+            "json",
a1beae5
+            "values",
a1beae5
+        ),
a1beae5
         choices=(),
a1beae5
         action="store",
a1beae5
         help=None,
a1beae5
diff --git a/tests/test_reqparse.py b/tests/test_reqparse.py
a1beae5
index 174f2988..3ac89a7a 100644
a1beae5
--- a/tests/test_reqparse.py
a1beae5
+++ b/tests/test_reqparse.py
a1beae5
@@ -914,28 +914,47 @@ def test_unknown_type(self):
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("unknown", type=lambda v: v)
a1beae5
         assert parser.__schema__ == [
a1beae5
-            {"name": "unknown", "type": "string", "in": "query",}
a1beae5
+            {
a1beae5
+                "name": "unknown",
a1beae5
+                "type": "string",
a1beae5
+                "in": "query",
a1beae5
+            }
a1beae5
         ]
a1beae5
a1beae5
     def test_required(self):
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("int", type=int, required=True)
a1beae5
         assert parser.__schema__ == [
a1beae5
-            {"name": "int", "type": "integer", "in": "query", "required": True,}
a1beae5
+            {
a1beae5
+                "name": "int",
a1beae5
+                "type": "integer",
a1beae5
+                "in": "query",
a1beae5
+                "required": True,
a1beae5
+            }
a1beae5
         ]
a1beae5
a1beae5
     def test_default(self):
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("int", type=int, default=5)
a1beae5
         assert parser.__schema__ == [
a1beae5
-            {"name": "int", "type": "integer", "in": "query", "default": 5,}
a1beae5
+            {
a1beae5
+                "name": "int",
a1beae5
+                "type": "integer",
a1beae5
+                "in": "query",
a1beae5
+                "default": 5,
a1beae5
+            }
a1beae5
         ]
a1beae5
a1beae5
     def test_default_as_false(self):
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("bool", type=inputs.boolean, default=False)
a1beae5
         assert parser.__schema__ == [
a1beae5
-            {"name": "bool", "type": "boolean", "in": "query", "default": False,}
a1beae5
+            {
a1beae5
+                "name": "bool",
a1beae5
+                "type": "boolean",
a1beae5
+                "in": "query",
a1beae5
+                "default": False,
a1beae5
+            }
a1beae5
         ]
a1beae5
a1beae5
     def test_choices(self):
a1beae5
@@ -958,31 +977,59 @@ def test_location(self):
a1beae5
         parser.add_argument("in_headers", type=int, location="headers")
a1beae5
         parser.add_argument("in_cookie", type=int, location="cookie")
a1beae5
         assert parser.__schema__ == [
a1beae5
-            {"name": "default", "type": "integer", "in": "query",},
a1beae5
-            {"name": "in_values", "type": "integer", "in": "query",},
a1beae5
-            {"name": "in_query", "type": "integer", "in": "query",},
a1beae5
-            {"name": "in_headers", "type": "integer", "in": "header",},
a1beae5
+            {
a1beae5
+                "name": "default",
a1beae5
+                "type": "integer",
a1beae5
+                "in": "query",
a1beae5
+            },
a1beae5
+            {
a1beae5
+                "name": "in_values",
a1beae5
+                "type": "integer",
a1beae5
+                "in": "query",
a1beae5
+            },
a1beae5
+            {
a1beae5
+                "name": "in_query",
a1beae5
+                "type": "integer",
a1beae5
+                "in": "query",
a1beae5
+            },
a1beae5
+            {
a1beae5
+                "name": "in_headers",
a1beae5
+                "type": "integer",
a1beae5
+                "in": "header",
a1beae5
+            },
a1beae5
         ]
a1beae5
a1beae5
     def test_location_json(self):
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("in_json", type=str, location="json")
a1beae5
         assert parser.__schema__ == [
a1beae5
-            {"name": "in_json", "type": "string", "in": "body",}
a1beae5
+            {
a1beae5
+                "name": "in_json",
a1beae5
+                "type": "string",
a1beae5
+                "in": "body",
a1beae5
+            }
a1beae5
         ]
a1beae5
a1beae5
     def test_location_form(self):
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("in_form", type=int, location="form")
a1beae5
         assert parser.__schema__ == [
a1beae5
-            {"name": "in_form", "type": "integer", "in": "formData",}
a1beae5
+            {
a1beae5
+                "name": "in_form",
a1beae5
+                "type": "integer",
a1beae5
+                "in": "formData",
a1beae5
+            }
a1beae5
         ]
a1beae5
a1beae5
     def test_location_files(self):
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("in_files", type=FileStorage, location="files")
a1beae5
         assert parser.__schema__ == [
a1beae5
-            {"name": "in_files", "type": "file", "in": "formData",}
a1beae5
+            {
a1beae5
+                "name": "in_files",
a1beae5
+                "type": "file",
a1beae5
+                "in": "formData",
a1beae5
+            }
a1beae5
         ]
a1beae5
a1beae5
     def test_form_and_body_location(self):
a1beae5
@@ -1012,7 +1059,13 @@ def test_models(self):
a1beae5
         )
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("todo", type=todo_fields)
a1beae5
-        assert parser.__schema__ == [{"name": "todo", "type": "Todo", "in": "body",}]
a1beae5
+        assert parser.__schema__ == [
a1beae5
+            {
a1beae5
+                "name": "todo",
a1beae5
+                "type": "Todo",
a1beae5
+                "in": "body",
a1beae5
+            }
a1beae5
+        ]
a1beae5
a1beae5
     def test_lists(self):
a1beae5
         parser = RequestParser()
a1beae5
@@ -1065,5 +1118,10 @@ def test_callable_default(self):
a1beae5
         parser = RequestParser()
a1beae5
         parser.add_argument("int", type=int, default=lambda: 5)
a1beae5
         assert parser.__schema__ == [
a1beae5
-            {"name": "int", "type": "integer", "in": "query", "default": 5,}
a1beae5
+            {
a1beae5
+                "name": "int",
a1beae5
+                "type": "integer",
a1beae5
+                "in": "query",
a1beae5
+                "default": 5,
a1beae5
+            }
a1beae5
         ]