wrappers.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. # -*- coding: utf-8 -*-
  2. """
  3. flask.wrappers
  4. ~~~~~~~~~~~~~~
  5. Implements the WSGI wrappers (request and response).
  6. :copyright: © 2010 by the Pallets team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. from werkzeug.exceptions import BadRequest
  10. from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
  11. from flask import json
  12. from flask.globals import current_app
  13. class JSONMixin(object):
  14. """Common mixin for both request and response objects to provide JSON
  15. parsing capabilities.
  16. .. versionadded:: 1.0
  17. """
  18. _cached_json = (Ellipsis, Ellipsis)
  19. @property
  20. def is_json(self):
  21. """Check if the mimetype indicates JSON data, either
  22. :mimetype:`application/json` or :mimetype:`application/*+json`.
  23. .. versionadded:: 0.11
  24. """
  25. mt = self.mimetype
  26. return (
  27. mt == 'application/json'
  28. or (mt.startswith('application/')) and mt.endswith('+json')
  29. )
  30. @property
  31. def json(self):
  32. """This will contain the parsed JSON data if the mimetype indicates
  33. JSON (:mimetype:`application/json`, see :meth:`is_json`), otherwise it
  34. will be ``None``.
  35. """
  36. return self.get_json()
  37. def _get_data_for_json(self, cache):
  38. return self.get_data(cache=cache)
  39. def get_json(self, force=False, silent=False, cache=True):
  40. """Parse and return the data as JSON. If the mimetype does not
  41. indicate JSON (:mimetype:`application/json`, see
  42. :meth:`is_json`), this returns ``None`` unless ``force`` is
  43. true. If parsing fails, :meth:`on_json_loading_failed` is called
  44. and its return value is used as the return value.
  45. :param force: Ignore the mimetype and always try to parse JSON.
  46. :param silent: Silence parsing errors and return ``None``
  47. instead.
  48. :param cache: Store the parsed JSON to return for subsequent
  49. calls.
  50. """
  51. if cache and self._cached_json[silent] is not Ellipsis:
  52. return self._cached_json[silent]
  53. if not (force or self.is_json):
  54. return None
  55. data = self._get_data_for_json(cache=cache)
  56. try:
  57. rv = json.loads(data)
  58. except ValueError as e:
  59. if silent:
  60. rv = None
  61. if cache:
  62. normal_rv, _ = self._cached_json
  63. self._cached_json = (normal_rv, rv)
  64. else:
  65. rv = self.on_json_loading_failed(e)
  66. if cache:
  67. _, silent_rv = self._cached_json
  68. self._cached_json = (rv, silent_rv)
  69. else:
  70. if cache:
  71. self._cached_json = (rv, rv)
  72. return rv
  73. def on_json_loading_failed(self, e):
  74. """Called if :meth:`get_json` parsing fails and isn't silenced. If
  75. this method returns a value, it is used as the return value for
  76. :meth:`get_json`. The default implementation raises a
  77. :class:`BadRequest` exception.
  78. .. versionchanged:: 0.10
  79. Raise a :exc:`BadRequest` error instead of returning an error
  80. message as JSON. If you want that behavior you can add it by
  81. subclassing.
  82. .. versionadded:: 0.8
  83. """
  84. if current_app is not None and current_app.debug:
  85. raise BadRequest('Failed to decode JSON object: {0}'.format(e))
  86. raise BadRequest()
  87. class Request(RequestBase, JSONMixin):
  88. """The request object used by default in Flask. Remembers the
  89. matched endpoint and view arguments.
  90. It is what ends up as :class:`~flask.request`. If you want to replace
  91. the request object used you can subclass this and set
  92. :attr:`~flask.Flask.request_class` to your subclass.
  93. The request object is a :class:`~werkzeug.wrappers.Request` subclass and
  94. provides all of the attributes Werkzeug defines plus a few Flask
  95. specific ones.
  96. """
  97. #: The internal URL rule that matched the request. This can be
  98. #: useful to inspect which methods are allowed for the URL from
  99. #: a before/after handler (``request.url_rule.methods``) etc.
  100. #: Though if the request's method was invalid for the URL rule,
  101. #: the valid list is available in ``routing_exception.valid_methods``
  102. #: instead (an attribute of the Werkzeug exception :exc:`~werkzeug.exceptions.MethodNotAllowed`)
  103. #: because the request was never internally bound.
  104. #:
  105. #: .. versionadded:: 0.6
  106. url_rule = None
  107. #: A dict of view arguments that matched the request. If an exception
  108. #: happened when matching, this will be ``None``.
  109. view_args = None
  110. #: If matching the URL failed, this is the exception that will be
  111. #: raised / was raised as part of the request handling. This is
  112. #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
  113. #: something similar.
  114. routing_exception = None
  115. @property
  116. def max_content_length(self):
  117. """Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
  118. if current_app:
  119. return current_app.config['MAX_CONTENT_LENGTH']
  120. @property
  121. def endpoint(self):
  122. """The endpoint that matched the request. This in combination with
  123. :attr:`view_args` can be used to reconstruct the same or a
  124. modified URL. If an exception happened when matching, this will
  125. be ``None``.
  126. """
  127. if self.url_rule is not None:
  128. return self.url_rule.endpoint
  129. @property
  130. def blueprint(self):
  131. """The name of the current blueprint"""
  132. if self.url_rule and '.' in self.url_rule.endpoint:
  133. return self.url_rule.endpoint.rsplit('.', 1)[0]
  134. def _load_form_data(self):
  135. RequestBase._load_form_data(self)
  136. # In debug mode we're replacing the files multidict with an ad-hoc
  137. # subclass that raises a different error for key errors.
  138. if (
  139. current_app
  140. and current_app.debug
  141. and self.mimetype != 'multipart/form-data'
  142. and not self.files
  143. ):
  144. from .debughelpers import attach_enctype_error_multidict
  145. attach_enctype_error_multidict(self)
  146. class Response(ResponseBase, JSONMixin):
  147. """The response object that is used by default in Flask. Works like the
  148. response object from Werkzeug but is set to have an HTML mimetype by
  149. default. Quite often you don't have to create this object yourself because
  150. :meth:`~flask.Flask.make_response` will take care of that for you.
  151. If you want to replace the response object used you can subclass this and
  152. set :attr:`~flask.Flask.response_class` to your subclass.
  153. .. versionchanged:: 1.0
  154. JSON support is added to the response, like the request. This is useful
  155. when testing to get the test client response data as JSON.
  156. .. versionchanged:: 1.0
  157. Added :attr:`max_cookie_size`.
  158. """
  159. default_mimetype = 'text/html'
  160. def _get_data_for_json(self, cache):
  161. return self.get_data()
  162. @property
  163. def max_cookie_size(self):
  164. """Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
  165. See :attr:`~werkzeug.wrappers.BaseResponse.max_cookie_size` in
  166. Werkzeug's docs.
  167. """
  168. if current_app:
  169. return current_app.config['MAX_COOKIE_SIZE']
  170. # return Werkzeug's default when not in an app context
  171. return super(Response, self).max_cookie_size