Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using reqparse.add_argument() with default location parameter to parse requests with Content-Type other than "application/json" raises an exception when using Werkzeug>=2.1.0 #963

Open
dlindquist opened this issue Jun 9, 2023 · 3 comments

Comments

@dlindquist
Copy link

Background

Werkzeug 2.1.0 changelog:

Request.get_json() will raise a 400 BadRequest error if the Content-Type header is not application/json. This makes a very common source of confusion more visible. :issue:2339

Werkzeug 2.3.0 changelog:

Request.get_json() will raise a 415 Unsupported Media Type error if the Content-Type header is not application/json, instead of a generic 400. :issue:2550

Issue

It seems that reqparse.add_argument() with a default location parameter will attempt to load JSON data regardless of the request Content-Type header. This causes Werkzeug to raise a Bad Request or Unsupported Media Type exception if a request is made with a Content-Type header that is not application/json.

Ref:

value = value()

Reproduction

Flask==2.3.2
Werkzeug==2.3.6
Flask-RESTful==0.3.10
from flask import Flask
from flask_restful import reqparse

def test_json_argument_with_form_location():
    '''
    passed
    '''
    app = Flask(__name__)

    parser = reqparse.RequestParser()
    parser.add_argument("example_argument", location='form')

    with app.test_request_context('/', method="post",
                                  data={'example_argument': 'example_value'}):
        args = parser.parse_args()
        assert args['example_argument'] == 'example_value'

def test_argument_with_json_data():
    '''
    passed
    '''
    app = Flask(__name__)

    parser = reqparse.RequestParser()
    parser.add_argument("example_argument")

    with app.test_request_context('/',
                                  json={'example_argument': 'example_value'}):
        args = parser.parse_args()
        assert args['example_argument'] == 'example_value'

def test_argument_without_json_data():
    '''
    failed
    '''
    app = Flask(__name__)

    parser = reqparse.RequestParser()
    parser.add_argument("example_argument")

    with app.test_request_context('/', method="post",
                                  data={'example_argument': 'example_value'}):
        args = parser.parse_args()
        assert args['example_argument'] == 'example_value'
$ pytest
======================================================================== test session starts ========================================================================
platform darwin -- Python 3.8.16, pytest-7.3.1, pluggy-1.0.0
collected 3 items                                                                                                                                                   

test_json_argument_without_json_content_type.py ..F                                                                                                           [100%]

============================================================================= FAILURES ==============================================================================
___________________________________________________________ test_json_argument_without_json_content_type ____________________________________________________________

    def test_json_argument_without_json_content_type():
        '''
        failed
        '''
        app = Flask(__name__)
    
        parser = reqparse.RequestParser()
        parser.add_argument("example_argument")
    
        with app.test_request_context('/', method="post",
                                      data={'example_argument': 'example_value'}):
>           args = parser.parse_args()

test_json_argument_without_json_content_type.py:43: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
env/lib/python3.8/site-packages/flask_restful/reqparse.py:328: in parse_args
    value, found = arg.parse(req, self.bundle_errors)
env/lib/python3.8/site-packages/flask_restful/reqparse.py:184: in parse
    source = self.source(request)
env/lib/python3.8/site-packages/flask_restful/reqparse.py:125: in source
    value = getattr(request, l, None)
env/lib/python3.8/site-packages/werkzeug/wrappers/request.py:561: in json
    return self.get_json()
env/lib/python3.8/site-packages/werkzeug/wrappers/request.py:607: in get_json
    return self.on_json_loading_failed(None)
env/lib/python3.8/site-packages/flask/wrappers.py:130: in on_json_loading_failed
    return super().on_json_loading_failed(e)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <Request 'http://localhost/' [POST]>, e = None

    def on_json_loading_failed(self, e: ValueError | None) -> t.Any:
        """Called if :meth:`get_json` 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
        :exc:`~werkzeug.exceptions.BadRequest`.
    
        :param e: If parsing failed, this is the exception. It will be
            ``None`` if the content type wasn't ``application/json``.
    
        .. versionchanged:: 2.3
            Raise a 415 error instead of 400.
        """
        if e is not None:
            raise BadRequest(f"Failed to decode JSON object: {e}")
    
>       raise UnsupportedMediaType(
            "Did not attempt to load JSON data because the request"
            " Content-Type was not 'application/json'."
        )
E       werkzeug.exceptions.UnsupportedMediaType: 415 Unsupported Media Type: Did not attempt to load JSON data because the request Content-Type was not 'application/json'.

env/lib/python3.8/site-packages/werkzeug/wrappers/request.py:650: UnsupportedMediaType
====================================================================== short test summary info ======================================================================
FAILED test_json_argument_without_json_content_type.py::test_json_argument_without_json_content_type - werkzeug.exceptions.UnsupportedMediaType: 415 Unsupported Media Type: Did not attempt to load JSON data because the request Content-Type was not 'application/js...
==================================================================== 1 failed, 2 passed in 0.10s ====================================================================
@dlindquist dlindquist changed the title Using reqparse.add_argument() with default location parameter to parse requests with Content-Type other than "application/json" raises an exception when Werkzeug>=2.1.0 Using reqparse.add_argument() with default location parameter to parse requests with Content-Type other than "application/json" raises an exception when using Werkzeug>=2.1.0 Jun 9, 2023
@browell
Copy link

browell commented Jun 26, 2023

This bug is indeed real and quite annoying. A workaround is just to set location as follows:
parser.add_argument('whatever,' type=str, location=('values',))

@legendjslc
Copy link

same error

@ZenithClown
Copy link

Facing the same issue, the app seems to be working fine but suddenly everything freezes! @browell's method did fix the issue but it is cumbersome. Any update on when this will be resolved?

ZenithClown added a commit to ZenithClown/finfolio that referenced this issue Apr 6, 2024
- reference issue: flask-restful/flask-restful#963
- todo work on user management system class
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants