diff --git a/CHANGES.rst b/CHANGES.rst index 2a6641c6..b3c092b7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -52,6 +52,10 @@ Unreleased - Add an ``--extra-files`` option to the ``flask run`` CLI command to specify extra files that will trigger the reloader on change. :issue:`2897` +- Allow returning a dictionary from a view function. Similar to how + returning a string will produce a ``text/html`` response, returning + a dict will call ``jsonify`` to produce a ``application/json`` + response. :pr:`3111` .. _#2935: https://github.com/pallets/flask/issues/2935 .. _#2957: https://github.com/pallets/flask/issues/2957 diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 47200a13..2b1449e9 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -679,23 +679,26 @@ See :ref:`error-handlers` for more details. About Responses --------------- -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 converted -into a response object with the string as response body, a ``200 OK`` -status code and a :mimetype:`text/html` mimetype. -The logic that Flask applies to converting return values into -response objects is as follows: +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 +converted into a response object with the string as response body, a +``200 OK`` status code and a :mimetype:`text/html` mimetype. If the +return value is a dict, ``jsonify`` is called to produce a response. +The logic that Flask applies to converting return values into response +objects is as follows: 1. If a response object of the correct type is returned it's directly returned from the view. -2. If it's a string, a response object is created with that data and the - default parameters. -3. If a tuple is returned the items in the tuple can provide extra information. - Such tuples have to be in the form ``(response, status, headers)``, - ``(response, headers)`` or ``(response, status)`` where at least one item - has to be in the tuple. The ``status`` value will override the status code - and ``headers`` can be a list or dictionary of additional header values. -4. If none of that works, Flask will assume the return value is a +2. If it's a string, a response object is created with that data and + the default parameters. +3. If it's a dict, a response object is created using ``jsonify``. +4. If a tuple is returned the items in the tuple can provide extra + information. Such tuples have to be in the form + ``(response, status)``, ``(response, headers)``, or + ``(response, status, headers)``. The ``status`` value will override + the status code and ``headers`` can be a list or dictionary of + additional header values. +5. If none of that works, Flask will assume the return value is a valid WSGI application and convert that into a response object. If you want to get hold of the resulting response object inside the view diff --git a/flask/app.py b/flask/app.py index 50775ce4..b0b2bc26 100644 --- a/flask/app.py +++ b/flask/app.py @@ -44,6 +44,7 @@ from .helpers import ( url_for, get_load_dotenv, ) +from .json import jsonify from .logging import create_logger from .sessions import SecureCookieSessionInterface from .signals import ( @@ -2001,6 +2002,9 @@ class Flask(_PackageBoundObject): ``bytes`` (``str`` in Python 2) A response object is created with the bytes as the body. + ``dict`` + A dictionary that will be jsonify'd before being returned. + ``tuple`` Either ``(body, status, headers)``, ``(body, status)``, or ``(body, headers)``, where ``body`` is any of the other types @@ -2064,6 +2068,8 @@ class Flask(_PackageBoundObject): # special logic rv = self.response_class(rv, status=status, headers=headers) status = headers = None + elif isinstance(rv, dict): + rv = jsonify(rv) else: # evaluate a WSGI callable, or coerce a different response # class to the correct type diff --git a/tests/test_basic.py b/tests/test_basic.py index b759098f..7a16ebd4 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1147,8 +1147,12 @@ def test_response_types(app, client): def from_wsgi(): return NotFound() - assert client.get("/text").data == u"Hällo Wörld".encode("utf-8") - assert client.get("/bytes").data == u"Hällo Wörld".encode("utf-8") + @app.route('/dict') + def from_dict(): + return {"foo": "bar"}, 201 + + assert client.get('/text').data == u'Hällo Wörld'.encode('utf-8') + assert client.get('/bytes').data == u'Hällo Wörld'.encode('utf-8') rv = client.get("/full_tuple") assert rv.data == b"Meh" @@ -1181,6 +1185,10 @@ def test_response_types(app, client): assert b"Not Found" in rv.data assert rv.status_code == 404 + rv = client.get('/dict') + assert rv.json == {"foo": "bar"} + assert rv.status_code == 201 + def test_response_type_errors(): app = flask.Flask(__name__)