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