diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 3cb9b2f7..c8a1140b 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -676,9 +676,9 @@ converting return values into response objects is as follows: 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)`` 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. + headers)`` or ``(response, headers)`` 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 valid WSGI application and convert that into a response object. diff --git a/flask/app.py b/flask/app.py index 4aff8339..c709c839 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1544,7 +1544,8 @@ class Flask(_PackageBoundObject): a WSGI function the function is called as WSGI application and buffered as response object :class:`tuple` A tuple in the form ``(response, status, - headers)`` where `response` is any of the + headers)`` or ``(response, headers)`` + where `response` is any of the types defined here, `status` is a string or an integer and `headers` is a list or a dictionary with header values. @@ -1556,34 +1557,38 @@ class Flask(_PackageBoundObject): Previously a tuple was interpreted as the arguments for the response object. """ - status = headers = None + status_or_headers = headers = None if isinstance(rv, tuple): - rv, status, headers = rv + (None,) * (3 - len(rv)) + rv, status_or_headers, headers = rv + (None,) * (3 - len(rv)) if rv is None: raise ValueError('View function did not return a response') + if isinstance(status_or_headers, (dict, list)): + headers, status_or_headers = status_or_headers, None + if not isinstance(rv, self.response_class): # When we create a response object directly, we let the constructor # set the headers and status. We do this because there can be # some extra logic involved when creating these objects with # specific values (like default content type selection). if isinstance(rv, (text_type, bytes, bytearray)): - rv = self.response_class(rv, headers=headers, status=status) - headers = status = None + rv = self.response_class(rv, headers=headers, status=status_or_headers) + headers = status_or_headers = None else: rv = self.response_class.force_type(rv, request.environ) - if status is not None: - if isinstance(status, string_types): - rv.status = status + if status_or_headers is not None: + if isinstance(status_or_headers, string_types): + rv.status = status_or_headers else: - rv.status_code = status + rv.status_code = status_or_headers if headers: rv.headers.extend(headers) return rv + def create_url_adapter(self, request): """Creates a URL adapter for the given request. The URL adapter is created at a point where the request context is not yet set up diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 6fcb3b68..bbbdf5e0 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -735,7 +735,23 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'Meh', 400, { 'X-Foo': 'Testing', 'Content-Type': 'text/plain; charset=utf-8' + } + @app.route('/two_args') + def from_two_args_tuple(): + return 'Hello', { + 'X-Foo': 'Test', + 'Content-Type': 'text/plain; charset=utf-8' } + @app.route('/args_status') + def from_status_tuple(): + return 'Hi, status!', 400 + @app.route('/args_header') + def from_response_instance_status_tuple(): + return flask.Response('Hello world', 404), { + "X-Foo": "Bar", + "X-Bar": "Foo" + } + c = app.test_client() self.assert_equal(c.get('/unicode').data, u'Hällo Wörld'.encode('utf-8')) self.assert_equal(c.get('/string').data, u'Hällo Wörld'.encode('utf-8')) @@ -744,6 +760,20 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(rv.headers['X-Foo'], 'Testing') self.assert_equal(rv.status_code, 400) self.assert_equal(rv.mimetype, 'text/plain') + rv2 = c.get('/two_args') + self.assert_equal(rv2.data, b'Hello') + self.assert_equal(rv2.headers['X-Foo'], 'Test') + self.assert_equal(rv2.status_code, 200) + self.assert_equal(rv2.mimetype, 'text/plain') + rv3 = c.get('/args_status') + self.assert_equal(rv3.data, b'Hi, status!') + self.assert_equal(rv3.status_code, 400) + self.assert_equal(rv3.mimetype, 'text/html') + rv4 = c.get('/args_header') + self.assert_equal(rv4.data, b'Hello world') + self.assert_equal(rv4.headers['X-Foo'], 'Bar') + self.assert_equal(rv4.headers['X-Bar'], 'Foo') + self.assert_equal(rv4.status_code, 404) def test_make_response(self): app = flask.Flask(__name__)