wrappers.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. from __future__ import annotations
  2. import typing as t
  3. from werkzeug.exceptions import BadRequest
  4. from werkzeug.exceptions import HTTPException
  5. from werkzeug.wrappers import Request as RequestBase
  6. from werkzeug.wrappers import Response as ResponseBase
  7. from . import json
  8. from .globals import current_app
  9. from .helpers import _split_blueprint_path
  10. if t.TYPE_CHECKING: # pragma: no cover
  11. from werkzeug.routing import Rule
  12. class Request(RequestBase):
  13. """The request object used by default in Flask. Remembers the
  14. matched endpoint and view arguments.
  15. It is what ends up as :class:`~flask.request`. If you want to replace
  16. the request object used you can subclass this and set
  17. :attr:`~flask.Flask.request_class` to your subclass.
  18. The request object is a :class:`~werkzeug.wrappers.Request` subclass and
  19. provides all of the attributes Werkzeug defines plus a few Flask
  20. specific ones.
  21. """
  22. json_module: t.Any = json
  23. #: The internal URL rule that matched the request. This can be
  24. #: useful to inspect which methods are allowed for the URL from
  25. #: a before/after handler (``request.url_rule.methods``) etc.
  26. #: Though if the request's method was invalid for the URL rule,
  27. #: the valid list is available in ``routing_exception.valid_methods``
  28. #: instead (an attribute of the Werkzeug exception
  29. #: :exc:`~werkzeug.exceptions.MethodNotAllowed`)
  30. #: because the request was never internally bound.
  31. #:
  32. #: .. versionadded:: 0.6
  33. url_rule: Rule | None = None
  34. #: A dict of view arguments that matched the request. If an exception
  35. #: happened when matching, this will be ``None``.
  36. view_args: dict[str, t.Any] | None = None
  37. #: If matching the URL failed, this is the exception that will be
  38. #: raised / was raised as part of the request handling. This is
  39. #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
  40. #: something similar.
  41. routing_exception: HTTPException | None = None
  42. _max_content_length: int | None = None
  43. _max_form_memory_size: int | None = None
  44. _max_form_parts: int | None = None
  45. @property
  46. def max_content_length(self) -> int | None:
  47. """The maximum number of bytes that will be read during this request. If
  48. this limit is exceeded, a 413 :exc:`~werkzeug.exceptions.RequestEntityTooLarge`
  49. error is raised. If it is set to ``None``, no limit is enforced at the
  50. Flask application level. However, if it is ``None`` and the request has
  51. no ``Content-Length`` header and the WSGI server does not indicate that
  52. it terminates the stream, then no data is read to avoid an infinite
  53. stream.
  54. Each request defaults to the :data:`MAX_CONTENT_LENGTH` config, which
  55. defaults to ``None``. It can be set on a specific ``request`` to apply
  56. the limit to that specific view. This should be set appropriately based
  57. on an application's or view's specific needs.
  58. .. versionchanged:: 3.1
  59. This can be set per-request.
  60. .. versionchanged:: 0.6
  61. This is configurable through Flask config.
  62. """
  63. if self._max_content_length is not None:
  64. return self._max_content_length
  65. if not current_app:
  66. return super().max_content_length
  67. return current_app.config["MAX_CONTENT_LENGTH"] # type: ignore[no-any-return]
  68. @max_content_length.setter
  69. def max_content_length(self, value: int | None) -> None:
  70. self._max_content_length = value
  71. @property
  72. def max_form_memory_size(self) -> int | None:
  73. """The maximum size in bytes any non-file form field may be in a
  74. ``multipart/form-data`` body. If this limit is exceeded, a 413
  75. :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it
  76. is set to ``None``, no limit is enforced at the Flask application level.
  77. Each request defaults to the :data:`MAX_FORM_MEMORY_SIZE` config, which
  78. defaults to ``500_000``. It can be set on a specific ``request`` to
  79. apply the limit to that specific view. This should be set appropriately
  80. based on an application's or view's specific needs.
  81. .. versionchanged:: 3.1
  82. This is configurable through Flask config.
  83. """
  84. if self._max_form_memory_size is not None:
  85. return self._max_form_memory_size
  86. if not current_app:
  87. return super().max_form_memory_size
  88. return current_app.config["MAX_FORM_MEMORY_SIZE"] # type: ignore[no-any-return]
  89. @max_form_memory_size.setter
  90. def max_form_memory_size(self, value: int | None) -> None:
  91. self._max_form_memory_size = value
  92. @property # type: ignore[override]
  93. def max_form_parts(self) -> int | None:
  94. """The maximum number of fields that may be present in a
  95. ``multipart/form-data`` body. If this limit is exceeded, a 413
  96. :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it
  97. is set to ``None``, no limit is enforced at the Flask application level.
  98. Each request defaults to the :data:`MAX_FORM_PARTS` config, which
  99. defaults to ``1_000``. It can be set on a specific ``request`` to apply
  100. the limit to that specific view. This should be set appropriately based
  101. on an application's or view's specific needs.
  102. .. versionchanged:: 3.1
  103. This is configurable through Flask config.
  104. """
  105. if self._max_form_parts is not None:
  106. return self._max_form_parts
  107. if not current_app:
  108. return super().max_form_parts
  109. return current_app.config["MAX_FORM_PARTS"] # type: ignore[no-any-return]
  110. @max_form_parts.setter
  111. def max_form_parts(self, value: int | None) -> None:
  112. self._max_form_parts = value
  113. @property
  114. def endpoint(self) -> str | None:
  115. """The endpoint that matched the request URL.
  116. This will be ``None`` if matching failed or has not been
  117. performed yet.
  118. This in combination with :attr:`view_args` can be used to
  119. reconstruct the same URL or a modified URL.
  120. """
  121. if self.url_rule is not None:
  122. return self.url_rule.endpoint # type: ignore[no-any-return]
  123. return None
  124. @property
  125. def blueprint(self) -> str | None:
  126. """The registered name of the current blueprint.
  127. This will be ``None`` if the endpoint is not part of a
  128. blueprint, or if URL matching failed or has not been performed
  129. yet.
  130. This does not necessarily match the name the blueprint was
  131. created with. It may have been nested, or registered with a
  132. different name.
  133. """
  134. endpoint = self.endpoint
  135. if endpoint is not None and "." in endpoint:
  136. return endpoint.rpartition(".")[0]
  137. return None
  138. @property
  139. def blueprints(self) -> list[str]:
  140. """The registered names of the current blueprint upwards through
  141. parent blueprints.
  142. This will be an empty list if there is no current blueprint, or
  143. if URL matching failed.
  144. .. versionadded:: 2.0.1
  145. """
  146. name = self.blueprint
  147. if name is None:
  148. return []
  149. return _split_blueprint_path(name)
  150. def _load_form_data(self) -> None:
  151. super()._load_form_data()
  152. # In debug mode we're replacing the files multidict with an ad-hoc
  153. # subclass that raises a different error for key errors.
  154. if (
  155. current_app
  156. and current_app.debug
  157. and self.mimetype != "multipart/form-data"
  158. and not self.files
  159. ):
  160. from .debughelpers import attach_enctype_error_multidict
  161. attach_enctype_error_multidict(self)
  162. def on_json_loading_failed(self, e: ValueError | None) -> t.Any:
  163. try:
  164. return super().on_json_loading_failed(e)
  165. except BadRequest as ebr:
  166. if current_app and current_app.debug:
  167. raise
  168. raise BadRequest() from ebr
  169. class Response(ResponseBase):
  170. """The response object that is used by default in Flask. Works like the
  171. response object from Werkzeug but is set to have an HTML mimetype by
  172. default. Quite often you don't have to create this object yourself because
  173. :meth:`~flask.Flask.make_response` will take care of that for you.
  174. If you want to replace the response object used you can subclass this and
  175. set :attr:`~flask.Flask.response_class` to your subclass.
  176. .. versionchanged:: 1.0
  177. JSON support is added to the response, like the request. This is useful
  178. when testing to get the test client response data as JSON.
  179. .. versionchanged:: 1.0
  180. Added :attr:`max_cookie_size`.
  181. """
  182. default_mimetype: str | None = "text/html"
  183. json_module = json
  184. autocorrect_location_header = False
  185. @property
  186. def max_cookie_size(self) -> int: # type: ignore
  187. """Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
  188. See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in
  189. Werkzeug's docs.
  190. """
  191. if current_app:
  192. return current_app.config["MAX_COOKIE_SIZE"] # type: ignore[no-any-return]
  193. # return Werkzeug's default when not in an app context
  194. return super().max_cookie_size