From 8bb71852849907d1b67ba7514cf1812348e11b44 Mon Sep 17 00:00:00 2001 From: John Zeringue Date: Fri, 31 May 2019 10:56:01 -0400 Subject: [PATCH] Better error message when view return type is not supported Before, returning a `bool` from a route caused the error ``` [2019-05-31 10:08:42,216] ERROR in app: Exception on / [GET] Traceback (most recent call last): File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 2070, in make_response rv = self.response_class.force_type(rv, request.environ) File "/Users/johnzeringue/Documents/ts-open/flask/env/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 269, in force_type response = BaseResponse(*_run_wsgi_app(response, environ)) File "/Users/johnzeringue/Documents/ts-open/flask/env/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 26, in _run_wsgi_app return _run_wsgi_app(*args) File "/Users/johnzeringue/Documents/ts-open/flask/env/lib/python3.7/site-packages/werkzeug/test.py", line 1119, in run_wsgi_app app_rv = app(environ, start_response) TypeError: 'bool' object is not callable During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 2393, in wsgi_app response = self.full_dispatch_request() File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 1906, in full_dispatch_request return self.finalize_request(rv) File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 1921, in finalize_request response = self.make_response(rv) File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 2078, in make_response reraise(TypeError, new_error, sys.exc_info()[2]) File "/Users/johnzeringue/Documents/ts-open/flask/flask/_compat.py", line 39, in reraise raise value.with_traceback(tb) File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 2070, in make_response rv = self.response_class.force_type(rv, request.environ) File "/Users/johnzeringue/Documents/ts-open/flask/env/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 269, in force_type response = BaseResponse(*_run_wsgi_app(response, environ)) File "/Users/johnzeringue/Documents/ts-open/flask/env/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 26, in _run_wsgi_app return _run_wsgi_app(*args) File "/Users/johnzeringue/Documents/ts-open/flask/env/lib/python3.7/site-packages/werkzeug/test.py", line 1119, in run_wsgi_app app_rv = app(environ, start_response) TypeError: 'bool' object is not callable The view function did not return a valid response. The return type must be a string, tuple, Response instance, or WSGI callable, but it was a bool. ``` Now, it returns the more readable ``` [2019-05-31 10:36:19,500] ERROR in app: Exception on / [GET] Traceback (most recent call last): File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 2400, in wsgi_app response = self.full_dispatch_request() File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 1907, in full_dispatch_request return self.finalize_request(rv) File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 1922, in finalize_request response = self.make_response(rv) File "/Users/johnzeringue/Documents/ts-open/flask/flask/app.py", line 2085, in make_response " {rv.__class__.__name__}.".format(rv=rv)) TypeError: The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a bool. ``` Fixes #3214 --- CHANGES.rst | 2 ++ flask/app.py | 12 ++++++++++-- tests/test_basic.py | 10 +++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f7508afd..f70461ba 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -64,6 +64,8 @@ Unreleased - When using the test client as a context manager (``with client:``), all preserved request contexts are popped when the block exits, ensuring nested contexts are cleaned up correctly. :pr:`3157` +- Show a better error message when the view return type is not + supported. :issue:`3214` .. _#2935: https://github.com/pallets/flask/issues/2935 .. _#2957: https://github.com/pallets/flask/issues/2957 diff --git a/flask/app.py b/flask/app.py index 76e45a30..654ee963 100644 --- a/flask/app.py +++ b/flask/app.py @@ -27,6 +27,7 @@ from werkzeug.exceptions import ( default_exceptions, ) from werkzeug.routing import BuildError, Map, RequestRedirect, RoutingException, Rule +from werkzeug.wrappers import BaseResponse from . import cli, json from ._compat import integer_types, reraise, string_types, text_type @@ -2063,7 +2064,7 @@ class Flask(_PackageBoundObject): status = headers = None elif isinstance(rv, dict): rv = jsonify(rv) - else: + elif isinstance(rv, BaseResponse) or callable(rv): # evaluate a WSGI callable, or coerce a different response # class to the correct type try: @@ -2071,11 +2072,18 @@ class Flask(_PackageBoundObject): except TypeError as e: new_error = TypeError( "{e}\nThe view function did not return a valid" - " response. The return type must be a string, tuple," + " response. The return type must be a string, dict, tuple," " Response instance, or WSGI callable, but it was a" " {rv.__class__.__name__}.".format(e=e, rv=rv) ) reraise(TypeError, new_error, sys.exc_info()[2]) + else: + raise TypeError( + "The view function did not return a valid" + " response. The return type must be a string, dict, tuple," + " Response instance, or WSGI callable, but it was a" + " {rv.__class__.__name__}.".format(rv=rv) + ) # prefer the status if it was provided if status is not None: diff --git a/tests/test_basic.py b/tests/test_basic.py index 32803b8c..3351380b 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1218,17 +1218,21 @@ def test_response_type_errors(): with pytest.raises(TypeError) as e: c.get("/none") - assert "returned None" in str(e) + + assert "returned None" in str(e) with pytest.raises(TypeError) as e: c.get("/small_tuple") - assert "tuple must have the form" in str(e) + + assert "tuple must have the form" in str(e) pytest.raises(TypeError, c.get, "/large_tuple") with pytest.raises(TypeError) as e: c.get("/bad_type") - assert "it was a bool" in str(e) + + assert "object is not callable" not in str(e) + assert "it was a bool" in str(e) pytest.raises(TypeError, c.get, "/bad_wsgi")