From cf1641e5beec1ec11f418fa6e775fc44b9410180 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 15:56:33 +0100 Subject: [PATCH] Changed the implementation of returning tuples from functions --- CHANGES | 2 ++ docs/quickstart.rst | 7 +++-- docs/upgrading.rst | 15 +++++++++++ flask/app.py | 55 ++++++++++++++++++++++++---------------- flask/testsuite/basic.py | 15 +++++------ 5 files changed, 61 insertions(+), 33 deletions(-) diff --git a/CHANGES b/CHANGES index bb03d088..cb7b751e 100644 --- a/CHANGES +++ b/CHANGES @@ -63,6 +63,8 @@ Relase date to be decided, codename to be chosen. the `get_send_file_options` hook is used. - Fixed an assumption in sessions implementation which could break message flashing on sessions implementations which use external storage. +- Changed the behavior of tuple return values from functions. They are no + longer arguments to the response object, they now have a defined meaning. Version 0.8.1 ------------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8497f082..f7b6ee02 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -674,8 +674,11 @@ converting return values into response objects is as follows: 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 response object is created by passing the - tuple as arguments to the response object's constructor. +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. 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/docs/upgrading.rst b/docs/upgrading.rst index 0ba46c13..ab00624e 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -19,6 +19,21 @@ installation, make sure to pass it the ``-U`` parameter:: $ easy_install -U Flask +Version 0.9 +----------- + +The behavior of returning tuples from a function was simplified. If you +return a tuple it no longer defines the arguments for the response object +you're creating, it's now always a tuple in the form ``(response, status, +headers)`` where at least one item has to be provided. If you depend on +the old behavior, you can add it easily by subclassing Flask:: + + class TraditionalFlask(Flask): + def make_response(self, rv): + if isinstance(rv, tuple): + return self.response_class(*rv) + return Flask.make_response(self, rv) + Version 0.8 ----------- diff --git a/flask/app.py b/flask/app.py index e16b35bf..a0ffed66 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1354,37 +1354,48 @@ class Flask(_PackageBoundObject): string as body :class:`unicode` a response object is created with the string encoded to utf-8 as body - :class:`tuple` the response object is created with the - contents of the tuple as arguments 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 + types defined here, `status` is a string + or an integer and `headers` is a list of + a dictionary with header values. ======================= =========================================== :param rv: the return value from the view function + + .. versionchanged:: 0.9 + Previously a tuple was interpreted as the arguments for the + response object. """ + status = headers = None + if isinstance(rv, tuple): + rv, status, headers = rv + (None,) * (3 - len(rv)) + if rv is None: raise ValueError('View function did not return a response') - if isinstance(rv, self.response_class): - return rv - if isinstance(rv, basestring): - return self.response_class(rv) - if isinstance(rv, tuple): - if len(rv) > 0 and isinstance(rv[0], self.response_class): - original = rv[0] - new_response = self.response_class('', *rv[1:]) - if len(rv) < 3: - # The args for the response class are - # response=None, status=None, headers=None, - # mimetype=None, content_type=None, ... - # so if there's at least 3 elements the rv - # tuple contains header information so the - # headers from rv[0] "win." - new_response.headers = original.headers - new_response.response = original.response - return new_response + + 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 defualt content type selection). + if isinstance(rv, basestring): + rv = self.response_class(rv, headers=headers, status=status) + headers = status = None + else: + rv = self.response_class.force_type(rv, request.environ) + + if status is not None: + if isinstance(status, basestring): + rv.status = status else: - return self.response_class(*rv) - return self.response_class.force_type(rv, request.environ) + rv.status_code = status + 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 diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 41efb196..0a4b1d9c 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -631,7 +631,10 @@ class BasicFunctionalityTestCase(FlaskTestCase): return u'Hällo Wörld'.encode('utf-8') @app.route('/args') def from_tuple(): - return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain' + return 'Meh', 400, { + 'X-Foo': 'Testing', + 'Content-Type': 'text/plain; charset=utf-8' + } 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')) @@ -677,16 +680,10 @@ class BasicFunctionalityTestCase(FlaskTestCase): rv = flask.make_response( flask.Response('', headers={'Content-Type': 'text/html'}), - 400, None, 'application/json') - self.assertEqual(rv.status_code, 400) - self.assertEqual(rv.headers['Content-Type'], 'application/json') - - rv = flask.make_response( - flask.Response('', mimetype='application/json'), - 400, {'Content-Type': 'text/html'}) + 400, [('X-Foo', 'bar')]) self.assertEqual(rv.status_code, 400) self.assertEqual(rv.headers['Content-Type'], 'text/html') - + self.assertEqual(rv.headers['X-Foo'], 'bar') def test_url_generation(self): app = flask.Flask(__name__)