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.
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!