|
10 | 10 | """
|
11 | 11 | import os
|
12 | 12 | import sys
|
13 |
| -from threading import Lock |
14 | 13 | from datetime import timedelta
|
15 |
| -from itertools import chain |
16 | 14 | from functools import update_wrapper
|
| 15 | +from itertools import chain |
| 16 | +from threading import Lock |
17 | 17 |
|
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 |
31 | 31 | from .sessions import SecureCookieSessionInterface
|
| 32 | +from .signals import appcontext_tearing_down, got_request_exception, \ |
| 33 | + request_finished, request_started, request_tearing_down |
32 | 34 | 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 |
37 | 37 |
|
38 | 38 | # a lock used for logger initialization
|
39 | 39 | _logger_lock = Lock()
|
@@ -1715,62 +1715,106 @@ def should_ignore_error(self, error):
|
1715 | 1715 | return False
|
1716 | 1716 |
|
1717 | 1717 | 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. |
1742 | 1751 |
|
1743 | 1752 | .. versionchanged:: 0.9
|
1744 | 1753 | Previously a tuple was interpreted as the arguments for the
|
1745 | 1754 | response object.
|
1746 | 1755 | """
|
1747 |
| - status_or_headers = headers = None |
1748 |
| - if isinstance(rv, tuple): |
1749 |
| - rv, status_or_headers, headers = rv + (None,) * (3 - len(rv)) |
1750 | 1756 |
|
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) |
1753 | 1762 |
|
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 | + ) |
1756 | 1779 |
|
| 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 |
1757 | 1789 | 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). |
1762 | 1790 | 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 |
1766 | 1796 | 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 |
1772 | 1814 | else:
|
1773 |
| - rv.status_code = status_or_headers |
| 1815 | + rv.status_code = status |
| 1816 | + |
| 1817 | + # extend existing headers with provided headers |
1774 | 1818 | if headers:
|
1775 | 1819 | rv.headers.extend(headers)
|
1776 | 1820 |
|
|
0 commit comments