1
0

base_request.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. from functools import update_wrapper
  2. from io import BytesIO
  3. from .._compat import to_native
  4. from .._compat import to_unicode
  5. from .._compat import wsgi_decoding_dance
  6. from .._compat import wsgi_get_bytes
  7. from ..datastructures import CombinedMultiDict
  8. from ..datastructures import EnvironHeaders
  9. from ..datastructures import ImmutableList
  10. from ..datastructures import ImmutableMultiDict
  11. from ..datastructures import iter_multi_items
  12. from ..datastructures import MultiDict
  13. from ..formparser import default_stream_factory
  14. from ..formparser import FormDataParser
  15. from ..http import parse_cookie
  16. from ..http import parse_list_header
  17. from ..http import parse_options_header
  18. from ..urls import url_decode
  19. from ..utils import cached_property
  20. from ..utils import environ_property
  21. from ..wsgi import get_content_length
  22. from ..wsgi import get_current_url
  23. from ..wsgi import get_host
  24. from ..wsgi import get_input_stream
  25. class BaseRequest(object):
  26. """Very basic request object. This does not implement advanced stuff like
  27. entity tag parsing or cache controls. The request object is created with
  28. the WSGI environment as first argument and will add itself to the WSGI
  29. environment as ``'werkzeug.request'`` unless it's created with
  30. `populate_request` set to False.
  31. There are a couple of mixins available that add additional functionality
  32. to the request object, there is also a class called `Request` which
  33. subclasses `BaseRequest` and all the important mixins.
  34. It's a good idea to create a custom subclass of the :class:`BaseRequest`
  35. and add missing functionality either via mixins or direct implementation.
  36. Here an example for such subclasses::
  37. from werkzeug.wrappers import BaseRequest, ETagRequestMixin
  38. class Request(BaseRequest, ETagRequestMixin):
  39. pass
  40. Request objects are **read only**. As of 0.5 modifications are not
  41. allowed in any place. Unlike the lower level parsing functions the
  42. request object will use immutable objects everywhere possible.
  43. Per default the request object will assume all the text data is `utf-8`
  44. encoded. Please refer to :doc:`the unicode chapter </unicode>` for more
  45. details about customizing the behavior.
  46. Per default the request object will be added to the WSGI
  47. environment as `werkzeug.request` to support the debugging system.
  48. If you don't want that, set `populate_request` to `False`.
  49. If `shallow` is `True` the environment is initialized as shallow
  50. object around the environ. Every operation that would modify the
  51. environ in any way (such as consuming form data) raises an exception
  52. unless the `shallow` attribute is explicitly set to `False`. This
  53. is useful for middlewares where you don't want to consume the form
  54. data by accident. A shallow request is not populated to the WSGI
  55. environment.
  56. .. versionchanged:: 0.5
  57. read-only mode was enforced by using immutables classes for all
  58. data.
  59. """
  60. #: the charset for the request, defaults to utf-8
  61. charset = "utf-8"
  62. #: the error handling procedure for errors, defaults to 'replace'
  63. encoding_errors = "replace"
  64. #: the maximum content length. This is forwarded to the form data
  65. #: parsing function (:func:`parse_form_data`). When set and the
  66. #: :attr:`form` or :attr:`files` attribute is accessed and the
  67. #: parsing fails because more than the specified value is transmitted
  68. #: a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
  69. #:
  70. #: Have a look at :ref:`dealing-with-request-data` for more details.
  71. #:
  72. #: .. versionadded:: 0.5
  73. max_content_length = None
  74. #: the maximum form field size. This is forwarded to the form data
  75. #: parsing function (:func:`parse_form_data`). When set and the
  76. #: :attr:`form` or :attr:`files` attribute is accessed and the
  77. #: data in memory for post data is longer than the specified value a
  78. #: :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
  79. #:
  80. #: Have a look at :ref:`dealing-with-request-data` for more details.
  81. #:
  82. #: .. versionadded:: 0.5
  83. max_form_memory_size = None
  84. #: the class to use for `args` and `form`. The default is an
  85. #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports
  86. #: multiple values per key. alternatively it makes sense to use an
  87. #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which
  88. #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict`
  89. #: which is the fastest but only remembers the last key. It is also
  90. #: possible to use mutable structures, but this is not recommended.
  91. #:
  92. #: .. versionadded:: 0.6
  93. parameter_storage_class = ImmutableMultiDict
  94. #: the type to be used for list values from the incoming WSGI environment.
  95. #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used
  96. #: (for example for :attr:`access_list`).
  97. #:
  98. #: .. versionadded:: 0.6
  99. list_storage_class = ImmutableList
  100. #: The type to be used for dict values from the incoming WSGI
  101. #: environment. (For example for :attr:`cookies`.) By default an
  102. #: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used.
  103. #:
  104. #: .. versionchanged:: 1.0.0
  105. #: Changed to ``ImmutableMultiDict`` to support multiple values.
  106. #:
  107. #: .. versionadded:: 0.6
  108. dict_storage_class = ImmutableMultiDict
  109. #: The form data parser that shoud be used. Can be replaced to customize
  110. #: the form date parsing.
  111. form_data_parser_class = FormDataParser
  112. #: Optionally a list of hosts that is trusted by this request. By default
  113. #: all hosts are trusted which means that whatever the client sends the
  114. #: host is will be accepted.
  115. #:
  116. #: Because `Host` and `X-Forwarded-Host` headers can be set to any value by
  117. #: a malicious client, it is recommended to either set this property or
  118. #: implement similar validation in the proxy (if application is being run
  119. #: behind one).
  120. #:
  121. #: .. versionadded:: 0.9
  122. trusted_hosts = None
  123. #: Indicates whether the data descriptor should be allowed to read and
  124. #: buffer up the input stream. By default it's enabled.
  125. #:
  126. #: .. versionadded:: 0.9
  127. disable_data_descriptor = False
  128. def __init__(self, environ, populate_request=True, shallow=False):
  129. self.environ = environ
  130. if populate_request and not shallow:
  131. self.environ["werkzeug.request"] = self
  132. self.shallow = shallow
  133. def __repr__(self):
  134. # make sure the __repr__ even works if the request was created
  135. # from an invalid WSGI environment. If we display the request
  136. # in a debug session we don't want the repr to blow up.
  137. args = []
  138. try:
  139. args.append("'%s'" % to_native(self.url, self.url_charset))
  140. args.append("[%s]" % self.method)
  141. except Exception:
  142. args.append("(invalid WSGI environ)")
  143. return "<%s %s>" % (self.__class__.__name__, " ".join(args))
  144. @property
  145. def url_charset(self):
  146. """The charset that is assumed for URLs. Defaults to the value
  147. of :attr:`charset`.
  148. .. versionadded:: 0.6
  149. """
  150. return self.charset
  151. @classmethod
  152. def from_values(cls, *args, **kwargs):
  153. """Create a new request object based on the values provided. If
  154. environ is given missing values are filled from there. This method is
  155. useful for small scripts when you need to simulate a request from an URL.
  156. Do not use this method for unittesting, there is a full featured client
  157. object (:class:`Client`) that allows to create multipart requests,
  158. support for cookies etc.
  159. This accepts the same options as the
  160. :class:`~werkzeug.test.EnvironBuilder`.
  161. .. versionchanged:: 0.5
  162. This method now accepts the same arguments as
  163. :class:`~werkzeug.test.EnvironBuilder`. Because of this the
  164. `environ` parameter is now called `environ_overrides`.
  165. :return: request object
  166. """
  167. from ..test import EnvironBuilder
  168. charset = kwargs.pop("charset", cls.charset)
  169. kwargs["charset"] = charset
  170. builder = EnvironBuilder(*args, **kwargs)
  171. try:
  172. return builder.get_request(cls)
  173. finally:
  174. builder.close()
  175. @classmethod
  176. def application(cls, f):
  177. """Decorate a function as responder that accepts the request as
  178. the last argument. This works like the :func:`responder`
  179. decorator but the function is passed the request object as the
  180. last argument and the request object will be closed
  181. automatically::
  182. @Request.application
  183. def my_wsgi_app(request):
  184. return Response('Hello World!')
  185. As of Werkzeug 0.14 HTTP exceptions are automatically caught and
  186. converted to responses instead of failing.
  187. :param f: the WSGI callable to decorate
  188. :return: a new WSGI callable
  189. """
  190. #: return a callable that wraps the -2nd argument with the request
  191. #: and calls the function with all the arguments up to that one and
  192. #: the request. The return value is then called with the latest
  193. #: two arguments. This makes it possible to use this decorator for
  194. #: both standalone WSGI functions as well as bound methods and
  195. #: partially applied functions.
  196. from ..exceptions import HTTPException
  197. def application(*args):
  198. request = cls(args[-2])
  199. with request:
  200. try:
  201. resp = f(*args[:-2] + (request,))
  202. except HTTPException as e:
  203. resp = e.get_response(args[-2])
  204. return resp(*args[-2:])
  205. return update_wrapper(application, f)
  206. def _get_file_stream(
  207. self, total_content_length, content_type, filename=None, content_length=None
  208. ):
  209. """Called to get a stream for the file upload.
  210. This must provide a file-like class with `read()`, `readline()`
  211. and `seek()` methods that is both writeable and readable.
  212. The default implementation returns a temporary file if the total
  213. content length is higher than 500KB. Because many browsers do not
  214. provide a content length for the files only the total content
  215. length matters.
  216. :param total_content_length: the total content length of all the
  217. data in the request combined. This value
  218. is guaranteed to be there.
  219. :param content_type: the mimetype of the uploaded file.
  220. :param filename: the filename of the uploaded file. May be `None`.
  221. :param content_length: the length of this file. This value is usually
  222. not provided because webbrowsers do not provide
  223. this value.
  224. """
  225. return default_stream_factory(
  226. total_content_length=total_content_length,
  227. filename=filename,
  228. content_type=content_type,
  229. content_length=content_length,
  230. )
  231. @property
  232. def want_form_data_parsed(self):
  233. """Returns True if the request method carries content. As of
  234. Werkzeug 0.9 this will be the case if a content type is transmitted.
  235. .. versionadded:: 0.8
  236. """
  237. return bool(self.environ.get("CONTENT_TYPE"))
  238. def make_form_data_parser(self):
  239. """Creates the form data parser. Instantiates the
  240. :attr:`form_data_parser_class` with some parameters.
  241. .. versionadded:: 0.8
  242. """
  243. return self.form_data_parser_class(
  244. self._get_file_stream,
  245. self.charset,
  246. self.encoding_errors,
  247. self.max_form_memory_size,
  248. self.max_content_length,
  249. self.parameter_storage_class,
  250. )
  251. def _load_form_data(self):
  252. """Method used internally to retrieve submitted data. After calling
  253. this sets `form` and `files` on the request object to multi dicts
  254. filled with the incoming form data. As a matter of fact the input
  255. stream will be empty afterwards. You can also call this method to
  256. force the parsing of the form data.
  257. .. versionadded:: 0.8
  258. """
  259. # abort early if we have already consumed the stream
  260. if "form" in self.__dict__:
  261. return
  262. _assert_not_shallow(self)
  263. if self.want_form_data_parsed:
  264. content_type = self.environ.get("CONTENT_TYPE", "")
  265. content_length = get_content_length(self.environ)
  266. mimetype, options = parse_options_header(content_type)
  267. parser = self.make_form_data_parser()
  268. data = parser.parse(
  269. self._get_stream_for_parsing(), mimetype, content_length, options
  270. )
  271. else:
  272. data = (
  273. self.stream,
  274. self.parameter_storage_class(),
  275. self.parameter_storage_class(),
  276. )
  277. # inject the values into the instance dict so that we bypass
  278. # our cached_property non-data descriptor.
  279. d = self.__dict__
  280. d["stream"], d["form"], d["files"] = data
  281. def _get_stream_for_parsing(self):
  282. """This is the same as accessing :attr:`stream` with the difference
  283. that if it finds cached data from calling :meth:`get_data` first it
  284. will create a new stream out of the cached data.
  285. .. versionadded:: 0.9.3
  286. """
  287. cached_data = getattr(self, "_cached_data", None)
  288. if cached_data is not None:
  289. return BytesIO(cached_data)
  290. return self.stream
  291. def close(self):
  292. """Closes associated resources of this request object. This
  293. closes all file handles explicitly. You can also use the request
  294. object in a with statement which will automatically close it.
  295. .. versionadded:: 0.9
  296. """
  297. files = self.__dict__.get("files")
  298. for _key, value in iter_multi_items(files or ()):
  299. value.close()
  300. def __enter__(self):
  301. return self
  302. def __exit__(self, exc_type, exc_value, tb):
  303. self.close()
  304. @cached_property
  305. def stream(self):
  306. """
  307. If the incoming form data was not encoded with a known mimetype
  308. the data is stored unmodified in this stream for consumption. Most
  309. of the time it is a better idea to use :attr:`data` which will give
  310. you that data as a string. The stream only returns the data once.
  311. Unlike :attr:`input_stream` this stream is properly guarded that you
  312. can't accidentally read past the length of the input. Werkzeug will
  313. internally always refer to this stream to read data which makes it
  314. possible to wrap this object with a stream that does filtering.
  315. .. versionchanged:: 0.9
  316. This stream is now always available but might be consumed by the
  317. form parser later on. Previously the stream was only set if no
  318. parsing happened.
  319. """
  320. _assert_not_shallow(self)
  321. return get_input_stream(self.environ)
  322. input_stream = environ_property(
  323. "wsgi.input",
  324. """The WSGI input stream.
  325. In general it's a bad idea to use this one because you can
  326. easily read past the boundary. Use the :attr:`stream`
  327. instead.""",
  328. )
  329. @cached_property
  330. def args(self):
  331. """The parsed URL parameters (the part in the URL after the question
  332. mark).
  333. By default an
  334. :class:`~werkzeug.datastructures.ImmutableMultiDict`
  335. is returned from this function. This can be changed by setting
  336. :attr:`parameter_storage_class` to a different type. This might
  337. be necessary if the order of the form data is important.
  338. """
  339. return url_decode(
  340. wsgi_get_bytes(self.environ.get("QUERY_STRING", "")),
  341. self.url_charset,
  342. errors=self.encoding_errors,
  343. cls=self.parameter_storage_class,
  344. )
  345. @cached_property
  346. def data(self):
  347. """
  348. Contains the incoming request data as string in case it came with
  349. a mimetype Werkzeug does not handle.
  350. """
  351. if self.disable_data_descriptor:
  352. raise AttributeError("data descriptor is disabled")
  353. # XXX: this should eventually be deprecated.
  354. # We trigger form data parsing first which means that the descriptor
  355. # will not cache the data that would otherwise be .form or .files
  356. # data. This restores the behavior that was there in Werkzeug
  357. # before 0.9. New code should use :meth:`get_data` explicitly as
  358. # this will make behavior explicit.
  359. return self.get_data(parse_form_data=True)
  360. def get_data(self, cache=True, as_text=False, parse_form_data=False):
  361. """This reads the buffered incoming data from the client into one
  362. bytestring. By default this is cached but that behavior can be
  363. changed by setting `cache` to `False`.
  364. Usually it's a bad idea to call this method without checking the
  365. content length first as a client could send dozens of megabytes or more
  366. to cause memory problems on the server.
  367. Note that if the form data was already parsed this method will not
  368. return anything as form data parsing does not cache the data like
  369. this method does. To implicitly invoke form data parsing function
  370. set `parse_form_data` to `True`. When this is done the return value
  371. of this method will be an empty string if the form parser handles
  372. the data. This generally is not necessary as if the whole data is
  373. cached (which is the default) the form parser will used the cached
  374. data to parse the form data. Please be generally aware of checking
  375. the content length first in any case before calling this method
  376. to avoid exhausting server memory.
  377. If `as_text` is set to `True` the return value will be a decoded
  378. unicode string.
  379. .. versionadded:: 0.9
  380. """
  381. rv = getattr(self, "_cached_data", None)
  382. if rv is None:
  383. if parse_form_data:
  384. self._load_form_data()
  385. rv = self.stream.read()
  386. if cache:
  387. self._cached_data = rv
  388. if as_text:
  389. rv = rv.decode(self.charset, self.encoding_errors)
  390. return rv
  391. @cached_property
  392. def form(self):
  393. """The form parameters. By default an
  394. :class:`~werkzeug.datastructures.ImmutableMultiDict`
  395. is returned from this function. This can be changed by setting
  396. :attr:`parameter_storage_class` to a different type. This might
  397. be necessary if the order of the form data is important.
  398. Please keep in mind that file uploads will not end up here, but instead
  399. in the :attr:`files` attribute.
  400. .. versionchanged:: 0.9
  401. Previous to Werkzeug 0.9 this would only contain form data for POST
  402. and PUT requests.
  403. """
  404. self._load_form_data()
  405. return self.form
  406. @cached_property
  407. def values(self):
  408. """A :class:`werkzeug.datastructures.CombinedMultiDict` that combines
  409. :attr:`args` and :attr:`form`."""
  410. args = []
  411. for d in self.args, self.form:
  412. if not isinstance(d, MultiDict):
  413. d = MultiDict(d)
  414. args.append(d)
  415. return CombinedMultiDict(args)
  416. @cached_property
  417. def files(self):
  418. """:class:`~werkzeug.datastructures.MultiDict` object containing
  419. all uploaded files. Each key in :attr:`files` is the name from the
  420. ``<input type="file" name="">``. Each value in :attr:`files` is a
  421. Werkzeug :class:`~werkzeug.datastructures.FileStorage` object.
  422. It basically behaves like a standard file object you know from Python,
  423. with the difference that it also has a
  424. :meth:`~werkzeug.datastructures.FileStorage.save` function that can
  425. store the file on the filesystem.
  426. Note that :attr:`files` will only contain data if the request method was
  427. POST, PUT or PATCH and the ``<form>`` that posted to the request had
  428. ``enctype="multipart/form-data"``. It will be empty otherwise.
  429. See the :class:`~werkzeug.datastructures.MultiDict` /
  430. :class:`~werkzeug.datastructures.FileStorage` documentation for
  431. more details about the used data structure.
  432. """
  433. self._load_form_data()
  434. return self.files
  435. @cached_property
  436. def cookies(self):
  437. """A :class:`dict` with the contents of all cookies transmitted with
  438. the request."""
  439. return parse_cookie(
  440. self.environ,
  441. self.charset,
  442. self.encoding_errors,
  443. cls=self.dict_storage_class,
  444. )
  445. @cached_property
  446. def headers(self):
  447. """The headers from the WSGI environ as immutable
  448. :class:`~werkzeug.datastructures.EnvironHeaders`.
  449. """
  450. return EnvironHeaders(self.environ)
  451. @cached_property
  452. def path(self):
  453. """Requested path as unicode. This works a bit like the regular path
  454. info in the WSGI environment but will always include a leading slash,
  455. even if the URL root is accessed.
  456. """
  457. raw_path = wsgi_decoding_dance(
  458. self.environ.get("PATH_INFO") or "", self.charset, self.encoding_errors
  459. )
  460. return "/" + raw_path.lstrip("/")
  461. @cached_property
  462. def full_path(self):
  463. """Requested path as unicode, including the query string."""
  464. return self.path + u"?" + to_unicode(self.query_string, self.url_charset)
  465. @cached_property
  466. def script_root(self):
  467. """The root path of the script without the trailing slash."""
  468. raw_path = wsgi_decoding_dance(
  469. self.environ.get("SCRIPT_NAME") or "", self.charset, self.encoding_errors
  470. )
  471. return raw_path.rstrip("/")
  472. @cached_property
  473. def url(self):
  474. """The reconstructed current URL as IRI.
  475. See also: :attr:`trusted_hosts`.
  476. """
  477. return get_current_url(self.environ, trusted_hosts=self.trusted_hosts)
  478. @cached_property
  479. def base_url(self):
  480. """Like :attr:`url` but without the querystring
  481. See also: :attr:`trusted_hosts`.
  482. """
  483. return get_current_url(
  484. self.environ, strip_querystring=True, trusted_hosts=self.trusted_hosts
  485. )
  486. @cached_property
  487. def url_root(self):
  488. """The full URL root (with hostname), this is the application
  489. root as IRI.
  490. See also: :attr:`trusted_hosts`.
  491. """
  492. return get_current_url(self.environ, True, trusted_hosts=self.trusted_hosts)
  493. @cached_property
  494. def host_url(self):
  495. """Just the host with scheme as IRI.
  496. See also: :attr:`trusted_hosts`.
  497. """
  498. return get_current_url(
  499. self.environ, host_only=True, trusted_hosts=self.trusted_hosts
  500. )
  501. @cached_property
  502. def host(self):
  503. """Just the host including the port if available.
  504. See also: :attr:`trusted_hosts`.
  505. """
  506. return get_host(self.environ, trusted_hosts=self.trusted_hosts)
  507. query_string = environ_property(
  508. "QUERY_STRING",
  509. "",
  510. read_only=True,
  511. load_func=wsgi_get_bytes,
  512. doc="The URL parameters as raw bytestring.",
  513. )
  514. method = environ_property(
  515. "REQUEST_METHOD",
  516. "GET",
  517. read_only=True,
  518. load_func=lambda x: x.upper(),
  519. doc="The request method. (For example ``'GET'`` or ``'POST'``).",
  520. )
  521. @cached_property
  522. def access_route(self):
  523. """If a forwarded header exists this is a list of all ip addresses
  524. from the client ip to the last proxy server.
  525. """
  526. if "HTTP_X_FORWARDED_FOR" in self.environ:
  527. return self.list_storage_class(
  528. parse_list_header(self.environ["HTTP_X_FORWARDED_FOR"])
  529. )
  530. elif "REMOTE_ADDR" in self.environ:
  531. return self.list_storage_class([self.environ["REMOTE_ADDR"]])
  532. return self.list_storage_class()
  533. @property
  534. def remote_addr(self):
  535. """The remote address of the client."""
  536. return self.environ.get("REMOTE_ADDR")
  537. remote_user = environ_property(
  538. "REMOTE_USER",
  539. doc="""If the server supports user authentication, and the
  540. script is protected, this attribute contains the username the
  541. user has authenticated as.""",
  542. )
  543. scheme = environ_property(
  544. "wsgi.url_scheme",
  545. doc="""
  546. URL scheme (http or https).
  547. .. versionadded:: 0.7""",
  548. )
  549. is_secure = property(
  550. lambda self: self.environ["wsgi.url_scheme"] == "https",
  551. doc="`True` if the request is secure.",
  552. )
  553. is_multithread = environ_property(
  554. "wsgi.multithread",
  555. doc="""boolean that is `True` if the application is served by a
  556. multithreaded WSGI server.""",
  557. )
  558. is_multiprocess = environ_property(
  559. "wsgi.multiprocess",
  560. doc="""boolean that is `True` if the application is served by a
  561. WSGI server that spawns multiple processes.""",
  562. )
  563. is_run_once = environ_property(
  564. "wsgi.run_once",
  565. doc="""boolean that is `True` if the application will be
  566. executed only once in a process lifetime. This is the case for
  567. CGI for example, but it's not guaranteed that the execution only
  568. happens one time.""",
  569. )
  570. def _assert_not_shallow(request):
  571. if request.shallow:
  572. raise RuntimeError(
  573. "A shallow request tried to consume form data. If you really"
  574. " want to do that, set `shallow` to False."
  575. )