Skip to content

Commit 970a800

Browse files
authored
Merge pull request pallets#2256 from davidism/refactor-make_response
refactor make_response to be easier to follow
2 parents 46f8366 + 697f7b9 commit 970a800

File tree

3 files changed

+207
-110
lines changed

3 files changed

+207
-110
lines changed

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,17 @@ Major release, unreleased
2929
handled by the app's error handlers. (`#2254`_)
3030
- Blueprints gained ``json_encoder`` and ``json_decoder`` attributes to
3131
override the app's encoder and decoder. (`#1898`_)
32+
- ``Flask.make_response`` raises ``TypeError`` instead of ``ValueError`` for
33+
bad response types. The error messages have been improved to describe why the
34+
type is invalid. (`#2256`_)
3235

3336
.. _#1489: https://github.com/pallets/flask/pull/1489
3437
.. _#1898: https://github.com/pallets/flask/pull/1898
3538
.. _#1936: https://github.com/pallets/flask/pull/1936
3639
.. _#2017: https://github.com/pallets/flask/pull/2017
3740
.. _#2223: https://github.com/pallets/flask/pull/2223
3841
.. _#2254: https://github.com/pallets/flask/pull/2254
42+
.. _#2256: https://github.com/pallets/flask/pull/2256
3943

4044
Version 0.12.1
4145
--------------

flask/app.py

Lines changed: 107 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,30 @@
1010
"""
1111
import os
1212
import sys
13-
from threading import Lock
1413
from datetime import timedelta
15-
from itertools import chain
1614
from functools import update_wrapper
15+
from itertools import chain
16+
from threading import Lock
1717

18-
from werkzeug.datastructures import ImmutableDict
19-
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
20-
from werkzeug.exceptions import HTTPException, InternalServerError, \
21-
MethodNotAllowed, BadRequest, default_exceptions
22-
23-
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
24-
locked_cached_property, _endpoint_from_view_func, find_package, \
25-
get_debug_flag
26-
from . import json, cli
27-
from .wrappers import Request, Response
28-
from .config import ConfigAttribute, Config
29-
from .ctx import RequestContext, AppContext, _AppCtxGlobals
30-
from .globals import _request_ctx_stack, request, session, g
18+
from werkzeug.datastructures import ImmutableDict, Headers
19+
from werkzeug.exceptions import BadRequest, HTTPException, \
20+
InternalServerError, MethodNotAllowed, default_exceptions
21+
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
22+
23+
from . import cli, json
24+
from ._compat import integer_types, reraise, string_types, text_type
25+
from .config import Config, ConfigAttribute
26+
from .ctx import AppContext, RequestContext, _AppCtxGlobals
27+
from .globals import _request_ctx_stack, g, request, session
28+
from .helpers import _PackageBoundObject, \
29+
_endpoint_from_view_func, find_package, get_debug_flag, \
30+
get_flashed_messages, locked_cached_property, url_for
3131
from .sessions import SecureCookieSessionInterface
32+
from .signals import appcontext_tearing_down, got_request_exception, \
33+
request_finished, request_started, request_tearing_down
3234
from .templating import DispatchingJinjaLoader, Environment, \
33-
_default_template_ctx_processor
34-
from .signals import request_started, request_finished, got_request_exception, \
35-
request_tearing_down, appcontext_tearing_down
36-
from ._compat import reraise, string_types, text_type, integer_types
35+
_default_template_ctx_processor
36+
from .wrappers import Request, Response
3737

3838
# a lock used for logger initialization
3939
_logger_lock = Lock()
@@ -1715,62 +1715,106 @@ def should_ignore_error(self, error):
17151715
return False
17161716

17171717
def make_response(self, rv):
1718-
"""Converts the return value from a view function to a real
1719-
response object that is an instance of :attr:`response_class`.
1720-
1721-
The following types are allowed for `rv`:
1722-
1723-
.. tabularcolumns:: |p{3.5cm}|p{9.5cm}|
1724-
1725-
======================= ===========================================
1726-
:attr:`response_class` the object is returned unchanged
1727-
:class:`str` a response object is created with the
1728-
string as body
1729-
:class:`unicode` a response object is created with the
1730-
string encoded to utf-8 as body
1731-
a WSGI function the function is called as WSGI application
1732-
and buffered as response object
1733-
:class:`tuple` A tuple in the form ``(response, status,
1734-
headers)`` or ``(response, headers)``
1735-
where `response` is any of the
1736-
types defined here, `status` is a string
1737-
or an integer and `headers` is a list or
1738-
a dictionary with header values.
1739-
======================= ===========================================
1740-
1741-
:param rv: the return value from the view function
1718+
"""Convert the return value from a view function to an instance of
1719+
:attr:`response_class`.
1720+
1721+
:param rv: the return value from the view function. The view function
1722+
must return a response. Returning ``None``, or the view ending
1723+
without returning, is not allowed. The following types are allowed
1724+
for ``view_rv``:
1725+
1726+
``str`` (``unicode`` in Python 2)
1727+
A response object is created with the string encoded to UTF-8
1728+
as the body.
1729+
1730+
``bytes`` (``str`` in Python 2)
1731+
A response object is created with the bytes as the body.
1732+
1733+
``tuple``
1734+
Either ``(body, status, headers)``, ``(body, status)``, or
1735+
``(body, headers)``, where ``body`` is any of the other types
1736+
allowed here, ``status`` is a string or an integer, and
1737+
``headers`` is a dictionary or a list of ``(key, value)``
1738+
tuples. If ``body`` is a :attr:`response_class` instance,
1739+
``status`` overwrites the exiting value and ``headers`` are
1740+
extended.
1741+
1742+
:attr:`response_class`
1743+
The object is returned unchanged.
1744+
1745+
other :class:`~werkzeug.wrappers.Response` class
1746+
The object is coerced to :attr:`response_class`.
1747+
1748+
:func:`callable`
1749+
The function is called as a WSGI application. The result is
1750+
used to create a response object.
17421751
17431752
.. versionchanged:: 0.9
17441753
Previously a tuple was interpreted as the arguments for the
17451754
response object.
17461755
"""
1747-
status_or_headers = headers = None
1748-
if isinstance(rv, tuple):
1749-
rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))
17501756

1751-
if rv is None:
1752-
raise ValueError('View function did not return a response')
1757+
status = headers = None
1758+
1759+
# unpack tuple returns
1760+
if isinstance(rv, (tuple, list)):
1761+
len_rv = len(rv)
17531762

1754-
if isinstance(status_or_headers, (dict, list)):
1755-
headers, status_or_headers = status_or_headers, None
1763+
# a 3-tuple is unpacked directly
1764+
if len_rv == 3:
1765+
rv, status, headers = rv
1766+
# decide if a 2-tuple has status or headers
1767+
elif len_rv == 2:
1768+
if isinstance(rv[1], (Headers, dict, tuple, list)):
1769+
rv, headers = rv
1770+
else:
1771+
rv, status = rv
1772+
# other sized tuples are not allowed
1773+
else:
1774+
raise TypeError(
1775+
'The view function did not return a valid response tuple.'
1776+
' The tuple must have the form (body, status, headers),'
1777+
' (body, status), or (body, headers).'
1778+
)
17561779

1780+
# the body must not be None
1781+
if rv is None:
1782+
raise TypeError(
1783+
'The view function did not return a valid response. The'
1784+
' function either returned None or ended without a return'
1785+
' statement.'
1786+
)
1787+
1788+
# make sure the body is an instance of the response class
17571789
if not isinstance(rv, self.response_class):
1758-
# When we create a response object directly, we let the constructor
1759-
# set the headers and status. We do this because there can be
1760-
# some extra logic involved when creating these objects with
1761-
# specific values (like default content type selection).
17621790
if isinstance(rv, (text_type, bytes, bytearray)):
1763-
rv = self.response_class(rv, headers=headers,
1764-
status=status_or_headers)
1765-
headers = status_or_headers = None
1791+
# let the response class set the status and headers instead of
1792+
# waiting to do it manually, so that the class can handle any
1793+
# special logic
1794+
rv = self.response_class(rv, status=status, headers=headers)
1795+
status = headers = None
17661796
else:
1767-
rv = self.response_class.force_type(rv, request.environ)
1768-
1769-
if status_or_headers is not None:
1770-
if isinstance(status_or_headers, string_types):
1771-
rv.status = status_or_headers
1797+
# evaluate a WSGI callable, or coerce a different response
1798+
# class to the correct type
1799+
try:
1800+
rv = self.response_class.force_type(rv, request.environ)
1801+
except TypeError as e:
1802+
new_error = TypeError(
1803+
'{e}\nThe view function did not return a valid'
1804+
' response. The return type must be a string, tuple,'
1805+
' Response instance, or WSGI callable, but it was a'
1806+
' {rv.__class__.__name__}.'.format(e=e, rv=rv)
1807+
)
1808+
reraise(TypeError, new_error, sys.exc_info()[2])
1809+
1810+
# prefer the status if it was provided
1811+
if status is not None:
1812+
if isinstance(status, (text_type, bytes, bytearray)):
1813+
rv.status = status
17721814
else:
1773-
rv.status_code = status_or_headers
1815+
rv.status_code = status
1816+
1817+
# extend existing headers with provided headers
17741818
if headers:
17751819
rv.headers.extend(headers)
17761820

0 commit comments

Comments
 (0)