Search code examples
flaskflask-apispec

@use_kwargs changes response content


I notice that @use_kwargs in flask-apispec changes the response content-type. In the following "hello world" example, The use of @use_kwargs changes the response content-type from text/html to application/json. I find it a bit surprising since the flask-apispec doc doesn't mention it and I wouldn't expect injecting args also changes the response type:

from flask import Flask
from flask_apispec import use_kwargs
from marshmallow import fields

app = Flask(__name__)

@app.route('/')
@use_kwargs({'email': fields.Str()}, location="query")
def hello_world(**kwargs):
    return 'Hello, World!

curl -v http://127.0.0.1:5000/\?email\=abc shows the response

> GET /?email=abc HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/7.64.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 16
< Server: Werkzeug/1.0.1 Python/3.8.2
< Date: Tue, 12 Jan 2021 06:09:25 GMT
<
"Hello, World!"

Note that the Content-Type: application/json and value hello world is quoted. However, without the line of @use_kwargs, the response is of Content-Type: text/html and the content hello world is not quoted:

~ curl -v http://127.0.0.1:5000/\?email\=abc
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
> GET /?email=abc HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/7.64.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 13
< Server: Werkzeug/1.0.1 Python/3.8.2
< Date: Tue, 12 Jan 2021 06:09:42 GMT
<
* Closing connection 0
Hello, World!%

What is the rationale of changing the response? How can I set the response content type of "text/html" even with the use of @use_kwargs? Thank you !

Update:

Just add a bit more details on the answer given by @Diego Miguel: The effect of changing content is caused by the logic in Wrapper.call

    def __call__(self, *args, **kwargs):
        response = self.call_view(*args, **kwargs)
        if isinstance(response, werkzeug.Response):
            return response
        rv, status_code, headers = unpack(response)
        mv = self.marshal_result(rv, status_code)
        response = packed(mv, status_code, headers)
        return flask.current_app.make_response(response)

marshal_result calls flask.jsonify which makes a Response object with "application/json" mimetype and also introduces an extra level of quotation of the "hello world" example above.


Solution

  • I'm not entirely sure why using @use_kwargs changes the content-type. By looking at the source code, it seems to return a dict, judging by this function (that is called by activate). So my best guess is that Flask when executing app.route jsonifys that dict, as that the default behaviour. At that point, the content-type is changed to application/json. However, hello_world is executed after use_kwargs, finally returning a string, that is, "Hello World!".

    In any case, I don't think this behaviour is actually intended by flask-apispec.

    You can change the content-type of your response (and any other field), creating a Flask.Response object with make_reponse and then setting its content-type to "text/html" (however, this is set by default when passing a string to make_response so it's not necessary):

    from flask import Flask, make_response
    from flask_apispec import use_kwargs
    from marshmallow import fields
    
    app = Flask(__name__)
    
    @app.route('/')
    @use_kwargs({'email': fields.Str()}, location="query")
    def hello_world(**kwargs):
        response = make_response("Hello World!")
        response.content_type = 'text/html'  # can be omitted
        return response
    

    Output of curl -v http://127.0.0.1:5000/\?email\=abc:

    > GET /?email=abc HTTP/1.1
    > Host: 127.0.0.1:5000
    > User-Agent: curl/7.74.0
    > Accept: */*
    > 
    * Mark bundle as not supporting multiuse
    * HTTP 1.0, assume close after body
    < HTTP/1.0 200 OK
    < Content-Type: text/html
    < Content-Length: 13
    < Server: Werkzeug/1.0.1 Python/3.9.1
    < Date: Tue, 12 Jan 2021 19:08:05 GMT
    < 
    Hello World!