forked from orbit-oss/flask
Merge pull request #4672 from greyli/return-list
Allow to return list as JSON
This commit is contained in:
commit
559a8458c4
6 changed files with 62 additions and 26 deletions
|
|
@ -65,6 +65,9 @@ Unreleased
|
||||||
``with client`` block. It will be cleaned up when
|
``with client`` block. It will be cleaned up when
|
||||||
``response.get_data()`` or ``response.close()`` is called.
|
``response.get_data()`` or ``response.close()`` is called.
|
||||||
|
|
||||||
|
- Allow returning a list from a view function, to convert it to a
|
||||||
|
JSON response like a dict is. :issue:`4672`
|
||||||
|
|
||||||
|
|
||||||
Version 2.1.3
|
Version 2.1.3
|
||||||
-------------
|
-------------
|
||||||
|
|
|
||||||
|
|
@ -681,22 +681,25 @@ The return value from a view function is automatically converted into
|
||||||
a response object for you. If the return value is a string it's
|
a response object for you. If the return value is a string it's
|
||||||
converted into a response object with the string as response body, a
|
converted into a response object with the string as response body, a
|
||||||
``200 OK`` status code and a :mimetype:`text/html` mimetype. If the
|
``200 OK`` status code and a :mimetype:`text/html` mimetype. If the
|
||||||
return value is a dict, :func:`jsonify` is called to produce a response.
|
return value is a dict or list, :func:`jsonify` is called to produce a
|
||||||
The logic that Flask applies to converting return values into response
|
response. The logic that Flask applies to converting return values into
|
||||||
objects is as follows:
|
response objects is as follows:
|
||||||
|
|
||||||
1. If a response object of the correct type is returned it's directly
|
1. If a response object of the correct type is returned it's directly
|
||||||
returned from the view.
|
returned from the view.
|
||||||
2. If it's a string, a response object is created with that data and
|
2. If it's a string, a response object is created with that data and
|
||||||
the default parameters.
|
the default parameters.
|
||||||
3. If it's a dict, a response object is created using ``jsonify``.
|
3. If it's an iterator or generator returning strings or bytes, it is
|
||||||
4. If a tuple is returned the items in the tuple can provide extra
|
treated as a streaming response.
|
||||||
|
4. If it's a dict or list, a response object is created using
|
||||||
|
:func:`~flask.json.jsonify`.
|
||||||
|
5. If a tuple is returned the items in the tuple can provide extra
|
||||||
information. Such tuples have to be in the form
|
information. Such tuples have to be in the form
|
||||||
``(response, status)``, ``(response, headers)``, or
|
``(response, status)``, ``(response, headers)``, or
|
||||||
``(response, status, headers)``. The ``status`` value will override
|
``(response, status, headers)``. The ``status`` value will override
|
||||||
the status code and ``headers`` can be a list or dictionary of
|
the status code and ``headers`` can be a list or dictionary of
|
||||||
additional header values.
|
additional header values.
|
||||||
5. If none of that works, Flask will assume the return value is a
|
6. If none of that works, Flask will assume the return value is a
|
||||||
valid WSGI application and convert that into a response object.
|
valid WSGI application and convert that into a response object.
|
||||||
|
|
||||||
If you want to get hold of the resulting response object inside the view
|
If you want to get hold of the resulting response object inside the view
|
||||||
|
|
@ -727,8 +730,8 @@ APIs with JSON
|
||||||
``````````````
|
``````````````
|
||||||
|
|
||||||
A common response format when writing an API is JSON. It's easy to get
|
A common response format when writing an API is JSON. It's easy to get
|
||||||
started writing such an API with Flask. If you return a ``dict`` from a
|
started writing such an API with Flask. If you return a ``dict`` or
|
||||||
view, it will be converted to a JSON response.
|
``list`` from a view, it will be converted to a JSON response.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
@ -741,20 +744,20 @@ view, it will be converted to a JSON response.
|
||||||
"image": url_for("user_image", filename=user.image),
|
"image": url_for("user_image", filename=user.image),
|
||||||
}
|
}
|
||||||
|
|
||||||
Depending on your API design, you may want to create JSON responses for
|
|
||||||
types other than ``dict``. In that case, use the
|
|
||||||
:func:`~flask.json.jsonify` function, which will serialize any supported
|
|
||||||
JSON data type. Or look into Flask community extensions that support
|
|
||||||
more complex applications.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from flask import jsonify
|
|
||||||
|
|
||||||
@app.route("/users")
|
@app.route("/users")
|
||||||
def users_api():
|
def users_api():
|
||||||
users = get_all_users()
|
users = get_all_users()
|
||||||
return jsonify([user.to_json() for user in users])
|
return [user.to_json() for user in users]
|
||||||
|
|
||||||
|
This is a shortcut to passing the data to the
|
||||||
|
:func:`~flask.json.jsonify` function, which will serialize any supported
|
||||||
|
JSON data type. That means that all the data in the dict or list must be
|
||||||
|
JSON serializable.
|
||||||
|
|
||||||
|
For complex types such as database models, you'll want to use a
|
||||||
|
serialization library to convert the data to valid JSON types first.
|
||||||
|
There are many serialization libraries and Flask API extensions
|
||||||
|
maintained by the community that support more complex applications.
|
||||||
|
|
||||||
|
|
||||||
.. _sessions:
|
.. _sessions:
|
||||||
|
|
|
||||||
|
|
@ -1830,6 +1830,9 @@ class Flask(Scaffold):
|
||||||
``dict``
|
``dict``
|
||||||
A dictionary that will be jsonify'd before being returned.
|
A dictionary that will be jsonify'd before being returned.
|
||||||
|
|
||||||
|
``list``
|
||||||
|
A list that will be jsonify'd before being returned.
|
||||||
|
|
||||||
``generator`` or ``iterator``
|
``generator`` or ``iterator``
|
||||||
A generator that returns ``str`` or ``bytes`` to be
|
A generator that returns ``str`` or ``bytes`` to be
|
||||||
streamed as the response.
|
streamed as the response.
|
||||||
|
|
@ -1855,6 +1858,7 @@ class Flask(Scaffold):
|
||||||
|
|
||||||
.. versionchanged:: 2.2
|
.. versionchanged:: 2.2
|
||||||
A generator will be converted to a streaming response.
|
A generator will be converted to a streaming response.
|
||||||
|
A list will be converted to a JSON response.
|
||||||
|
|
||||||
.. versionchanged:: 1.1
|
.. versionchanged:: 1.1
|
||||||
A dict will be converted to a JSON response.
|
A dict will be converted to a JSON response.
|
||||||
|
|
@ -1907,7 +1911,7 @@ class Flask(Scaffold):
|
||||||
headers=headers, # type: ignore[arg-type]
|
headers=headers, # type: ignore[arg-type]
|
||||||
)
|
)
|
||||||
status = headers = None
|
status = headers = None
|
||||||
elif isinstance(rv, dict):
|
elif isinstance(rv, (dict, list)):
|
||||||
rv = jsonify(rv)
|
rv = jsonify(rv)
|
||||||
elif isinstance(rv, BaseResponse) or callable(rv):
|
elif isinstance(rv, BaseResponse) or callable(rv):
|
||||||
# evaluate a WSGI callable, or coerce a different response
|
# evaluate a WSGI callable, or coerce a different response
|
||||||
|
|
@ -1920,15 +1924,17 @@ class Flask(Scaffold):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"{e}\nThe view function did not return a valid"
|
f"{e}\nThe view function did not return a valid"
|
||||||
" response. The return type must be a string,"
|
" response. The return type must be a string,"
|
||||||
" dict, tuple, Response instance, or WSGI"
|
" dict, list, tuple with headers or status,"
|
||||||
f" callable, but it was a {type(rv).__name__}."
|
" Response instance, or WSGI callable, but it"
|
||||||
|
f" was a {type(rv).__name__}."
|
||||||
).with_traceback(sys.exc_info()[2]) from None
|
).with_traceback(sys.exc_info()[2]) from None
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"The view function did not return a valid"
|
"The view function did not return a valid"
|
||||||
" response. The return type must be a string,"
|
" response. The return type must be a string,"
|
||||||
" dict, tuple, Response instance, or WSGI"
|
" dict, list, tuple with headers or status,"
|
||||||
f" callable, but it was a {type(rv).__name__}."
|
" Response instance, or WSGI callable, but it was a"
|
||||||
|
f" {type(rv).__name__}."
|
||||||
)
|
)
|
||||||
|
|
||||||
rv = t.cast(Response, rv)
|
rv = t.cast(Response, rv)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,13 @@ if t.TYPE_CHECKING: # pragma: no cover
|
||||||
|
|
||||||
# The possible types that are directly convertible or are a Response object.
|
# The possible types that are directly convertible or are a Response object.
|
||||||
ResponseValue = t.Union[
|
ResponseValue = t.Union[
|
||||||
"Response", str, bytes, t.Dict[str, t.Any], t.Iterator[str], t.Iterator[bytes]
|
"Response",
|
||||||
|
str,
|
||||||
|
bytes,
|
||||||
|
t.List[t.Any],
|
||||||
|
t.Dict[str, t.Any],
|
||||||
|
t.Iterator[str],
|
||||||
|
t.Iterator[bytes],
|
||||||
]
|
]
|
||||||
|
|
||||||
# the possible types for an individual HTTP header
|
# the possible types for an individual HTTP header
|
||||||
|
|
|
||||||
|
|
@ -1166,6 +1166,10 @@ def test_response_types(app, client):
|
||||||
def from_dict():
|
def from_dict():
|
||||||
return {"foo": "bar"}, 201
|
return {"foo": "bar"}, 201
|
||||||
|
|
||||||
|
@app.route("/list")
|
||||||
|
def from_list():
|
||||||
|
return ["foo", "bar"], 201
|
||||||
|
|
||||||
assert client.get("/text").data == "Hällo Wörld".encode()
|
assert client.get("/text").data == "Hällo Wörld".encode()
|
||||||
assert client.get("/bytes").data == "Hällo Wörld".encode()
|
assert client.get("/bytes").data == "Hällo Wörld".encode()
|
||||||
|
|
||||||
|
|
@ -1205,6 +1209,10 @@ def test_response_types(app, client):
|
||||||
assert rv.json == {"foo": "bar"}
|
assert rv.json == {"foo": "bar"}
|
||||||
assert rv.status_code == 201
|
assert rv.status_code == 201
|
||||||
|
|
||||||
|
rv = client.get("/list")
|
||||||
|
assert rv.json == ["foo", "bar"]
|
||||||
|
assert rv.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
def test_response_type_errors():
|
def test_response_type_errors():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,17 @@ def hello_bytes() -> bytes:
|
||||||
|
|
||||||
@app.route("/json")
|
@app.route("/json")
|
||||||
def hello_json() -> Response:
|
def hello_json() -> Response:
|
||||||
return jsonify({"response": "Hello, World!"})
|
return jsonify("Hello, World!")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/json/dict")
|
||||||
|
def hello_json_dict() -> t.Dict[str, t.Any]:
|
||||||
|
return {"response": "Hello, World!"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/json/dict")
|
||||||
|
def hello_json_list() -> t.List[t.Any]:
|
||||||
|
return [{"message": "Hello"}, {"message": "World"}]
|
||||||
|
|
||||||
|
|
||||||
@app.route("/generator")
|
@app.route("/generator")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue