ctx.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. from __future__ import annotations
  2. import contextvars
  3. import sys
  4. import typing as t
  5. from functools import update_wrapper
  6. from types import TracebackType
  7. from werkzeug.exceptions import HTTPException
  8. from . import typing as ft
  9. from .globals import _cv_app
  10. from .globals import _cv_request
  11. from .signals import appcontext_popped
  12. from .signals import appcontext_pushed
  13. if t.TYPE_CHECKING: # pragma: no cover
  14. from _typeshed.wsgi import WSGIEnvironment
  15. from .app import Flask
  16. from .sessions import SessionMixin
  17. from .wrappers import Request
  18. # a singleton sentinel value for parameter defaults
  19. _sentinel = object()
  20. class _AppCtxGlobals:
  21. """A plain object. Used as a namespace for storing data during an
  22. application context.
  23. Creating an app context automatically creates this object, which is
  24. made available as the :data:`g` proxy.
  25. .. describe:: 'key' in g
  26. Check whether an attribute is present.
  27. .. versionadded:: 0.10
  28. .. describe:: iter(g)
  29. Return an iterator over the attribute names.
  30. .. versionadded:: 0.10
  31. """
  32. # Define attr methods to let mypy know this is a namespace object
  33. # that has arbitrary attributes.
  34. def __getattr__(self, name: str) -> t.Any:
  35. try:
  36. return self.__dict__[name]
  37. except KeyError:
  38. raise AttributeError(name) from None
  39. def __setattr__(self, name: str, value: t.Any) -> None:
  40. self.__dict__[name] = value
  41. def __delattr__(self, name: str) -> None:
  42. try:
  43. del self.__dict__[name]
  44. except KeyError:
  45. raise AttributeError(name) from None
  46. def get(self, name: str, default: t.Any | None = None) -> t.Any:
  47. """Get an attribute by name, or a default value. Like
  48. :meth:`dict.get`.
  49. :param name: Name of attribute to get.
  50. :param default: Value to return if the attribute is not present.
  51. .. versionadded:: 0.10
  52. """
  53. return self.__dict__.get(name, default)
  54. def pop(self, name: str, default: t.Any = _sentinel) -> t.Any:
  55. """Get and remove an attribute by name. Like :meth:`dict.pop`.
  56. :param name: Name of attribute to pop.
  57. :param default: Value to return if the attribute is not present,
  58. instead of raising a ``KeyError``.
  59. .. versionadded:: 0.11
  60. """
  61. if default is _sentinel:
  62. return self.__dict__.pop(name)
  63. else:
  64. return self.__dict__.pop(name, default)
  65. def setdefault(self, name: str, default: t.Any = None) -> t.Any:
  66. """Get the value of an attribute if it is present, otherwise
  67. set and return a default value. Like :meth:`dict.setdefault`.
  68. :param name: Name of attribute to get.
  69. :param default: Value to set and return if the attribute is not
  70. present.
  71. .. versionadded:: 0.11
  72. """
  73. return self.__dict__.setdefault(name, default)
  74. def __contains__(self, item: str) -> bool:
  75. return item in self.__dict__
  76. def __iter__(self) -> t.Iterator[str]:
  77. return iter(self.__dict__)
  78. def __repr__(self) -> str:
  79. ctx = _cv_app.get(None)
  80. if ctx is not None:
  81. return f"<flask.g of '{ctx.app.name}'>"
  82. return object.__repr__(self)
  83. def after_this_request(
  84. f: ft.AfterRequestCallable[t.Any],
  85. ) -> ft.AfterRequestCallable[t.Any]:
  86. """Executes a function after this request. This is useful to modify
  87. response objects. The function is passed the response object and has
  88. to return the same or a new one.
  89. Example::
  90. @app.route('/')
  91. def index():
  92. @after_this_request
  93. def add_header(response):
  94. response.headers['X-Foo'] = 'Parachute'
  95. return response
  96. return 'Hello World!'
  97. This is more useful if a function other than the view function wants to
  98. modify a response. For instance think of a decorator that wants to add
  99. some headers without converting the return value into a response object.
  100. .. versionadded:: 0.9
  101. """
  102. ctx = _cv_request.get(None)
  103. if ctx is None:
  104. raise RuntimeError(
  105. "'after_this_request' can only be used when a request"
  106. " context is active, such as in a view function."
  107. )
  108. ctx._after_request_functions.append(f)
  109. return f
  110. F = t.TypeVar("F", bound=t.Callable[..., t.Any])
  111. def copy_current_request_context(f: F) -> F:
  112. """A helper function that decorates a function to retain the current
  113. request context. This is useful when working with greenlets. The moment
  114. the function is decorated a copy of the request context is created and
  115. then pushed when the function is called. The current session is also
  116. included in the copied request context.
  117. Example::
  118. import gevent
  119. from flask import copy_current_request_context
  120. @app.route('/')
  121. def index():
  122. @copy_current_request_context
  123. def do_some_work():
  124. # do some work here, it can access flask.request or
  125. # flask.session like you would otherwise in the view function.
  126. ...
  127. gevent.spawn(do_some_work)
  128. return 'Regular response'
  129. .. versionadded:: 0.10
  130. """
  131. ctx = _cv_request.get(None)
  132. if ctx is None:
  133. raise RuntimeError(
  134. "'copy_current_request_context' can only be used when a"
  135. " request context is active, such as in a view function."
  136. )
  137. ctx = ctx.copy()
  138. def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
  139. with ctx: # type: ignore[union-attr]
  140. return ctx.app.ensure_sync(f)(*args, **kwargs) # type: ignore[union-attr]
  141. return update_wrapper(wrapper, f) # type: ignore[return-value]
  142. def has_request_context() -> bool:
  143. """If you have code that wants to test if a request context is there or
  144. not this function can be used. For instance, you may want to take advantage
  145. of request information if the request object is available, but fail
  146. silently if it is unavailable.
  147. ::
  148. class User(db.Model):
  149. def __init__(self, username, remote_addr=None):
  150. self.username = username
  151. if remote_addr is None and has_request_context():
  152. remote_addr = request.remote_addr
  153. self.remote_addr = remote_addr
  154. Alternatively you can also just test any of the context bound objects
  155. (such as :class:`request` or :class:`g`) for truthness::
  156. class User(db.Model):
  157. def __init__(self, username, remote_addr=None):
  158. self.username = username
  159. if remote_addr is None and request:
  160. remote_addr = request.remote_addr
  161. self.remote_addr = remote_addr
  162. .. versionadded:: 0.7
  163. """
  164. return _cv_request.get(None) is not None
  165. def has_app_context() -> bool:
  166. """Works like :func:`has_request_context` but for the application
  167. context. You can also just do a boolean check on the
  168. :data:`current_app` object instead.
  169. .. versionadded:: 0.9
  170. """
  171. return _cv_app.get(None) is not None
  172. class AppContext:
  173. """The app context contains application-specific information. An app
  174. context is created and pushed at the beginning of each request if
  175. one is not already active. An app context is also pushed when
  176. running CLI commands.
  177. """
  178. def __init__(self, app: Flask) -> None:
  179. self.app = app
  180. self.url_adapter = app.create_url_adapter(None)
  181. self.g: _AppCtxGlobals = app.app_ctx_globals_class()
  182. self._cv_tokens: list[contextvars.Token[AppContext]] = []
  183. def push(self) -> None:
  184. """Binds the app context to the current context."""
  185. self._cv_tokens.append(_cv_app.set(self))
  186. appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync)
  187. def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
  188. """Pops the app context."""
  189. try:
  190. if len(self._cv_tokens) == 1:
  191. if exc is _sentinel:
  192. exc = sys.exc_info()[1]
  193. self.app.do_teardown_appcontext(exc)
  194. finally:
  195. ctx = _cv_app.get()
  196. _cv_app.reset(self._cv_tokens.pop())
  197. if ctx is not self:
  198. raise AssertionError(
  199. f"Popped wrong app context. ({ctx!r} instead of {self!r})"
  200. )
  201. appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)
  202. def __enter__(self) -> AppContext:
  203. self.push()
  204. return self
  205. def __exit__(
  206. self,
  207. exc_type: type | None,
  208. exc_value: BaseException | None,
  209. tb: TracebackType | None,
  210. ) -> None:
  211. self.pop(exc_value)
  212. class RequestContext:
  213. """The request context contains per-request information. The Flask
  214. app creates and pushes it at the beginning of the request, then pops
  215. it at the end of the request. It will create the URL adapter and
  216. request object for the WSGI environment provided.
  217. Do not attempt to use this class directly, instead use
  218. :meth:`~flask.Flask.test_request_context` and
  219. :meth:`~flask.Flask.request_context` to create this object.
  220. When the request context is popped, it will evaluate all the
  221. functions registered on the application for teardown execution
  222. (:meth:`~flask.Flask.teardown_request`).
  223. The request context is automatically popped at the end of the
  224. request. When using the interactive debugger, the context will be
  225. restored so ``request`` is still accessible. Similarly, the test
  226. client can preserve the context after the request ends. However,
  227. teardown functions may already have closed some resources such as
  228. database connections.
  229. """
  230. def __init__(
  231. self,
  232. app: Flask,
  233. environ: WSGIEnvironment,
  234. request: Request | None = None,
  235. session: SessionMixin | None = None,
  236. ) -> None:
  237. self.app = app
  238. if request is None:
  239. request = app.request_class(environ)
  240. request.json_module = app.json
  241. self.request: Request = request
  242. self.url_adapter = None
  243. try:
  244. self.url_adapter = app.create_url_adapter(self.request)
  245. except HTTPException as e:
  246. self.request.routing_exception = e
  247. self.flashes: list[tuple[str, str]] | None = None
  248. self.session: SessionMixin | None = session
  249. # Functions that should be executed after the request on the response
  250. # object. These will be called before the regular "after_request"
  251. # functions.
  252. self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = []
  253. self._cv_tokens: list[
  254. tuple[contextvars.Token[RequestContext], AppContext | None]
  255. ] = []
  256. def copy(self) -> RequestContext:
  257. """Creates a copy of this request context with the same request object.
  258. This can be used to move a request context to a different greenlet.
  259. Because the actual request object is the same this cannot be used to
  260. move a request context to a different thread unless access to the
  261. request object is locked.
  262. .. versionadded:: 0.10
  263. .. versionchanged:: 1.1
  264. The current session object is used instead of reloading the original
  265. data. This prevents `flask.session` pointing to an out-of-date object.
  266. """
  267. return self.__class__(
  268. self.app,
  269. environ=self.request.environ,
  270. request=self.request,
  271. session=self.session,
  272. )
  273. def match_request(self) -> None:
  274. """Can be overridden by a subclass to hook into the matching
  275. of the request.
  276. """
  277. try:
  278. result = self.url_adapter.match(return_rule=True) # type: ignore
  279. self.request.url_rule, self.request.view_args = result # type: ignore
  280. except HTTPException as e:
  281. self.request.routing_exception = e
  282. def push(self) -> None:
  283. # Before we push the request context we have to ensure that there
  284. # is an application context.
  285. app_ctx = _cv_app.get(None)
  286. if app_ctx is None or app_ctx.app is not self.app:
  287. app_ctx = self.app.app_context()
  288. app_ctx.push()
  289. else:
  290. app_ctx = None
  291. self._cv_tokens.append((_cv_request.set(self), app_ctx))
  292. # Open the session at the moment that the request context is available.
  293. # This allows a custom open_session method to use the request context.
  294. # Only open a new session if this is the first time the request was
  295. # pushed, otherwise stream_with_context loses the session.
  296. if self.session is None:
  297. session_interface = self.app.session_interface
  298. self.session = session_interface.open_session(self.app, self.request)
  299. if self.session is None:
  300. self.session = session_interface.make_null_session(self.app)
  301. # Match the request URL after loading the session, so that the
  302. # session is available in custom URL converters.
  303. if self.url_adapter is not None:
  304. self.match_request()
  305. def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
  306. """Pops the request context and unbinds it by doing that. This will
  307. also trigger the execution of functions registered by the
  308. :meth:`~flask.Flask.teardown_request` decorator.
  309. .. versionchanged:: 0.9
  310. Added the `exc` argument.
  311. """
  312. clear_request = len(self._cv_tokens) == 1
  313. try:
  314. if clear_request:
  315. if exc is _sentinel:
  316. exc = sys.exc_info()[1]
  317. self.app.do_teardown_request(exc)
  318. request_close = getattr(self.request, "close", None)
  319. if request_close is not None:
  320. request_close()
  321. finally:
  322. ctx = _cv_request.get()
  323. token, app_ctx = self._cv_tokens.pop()
  324. _cv_request.reset(token)
  325. # get rid of circular dependencies at the end of the request
  326. # so that we don't require the GC to be active.
  327. if clear_request:
  328. ctx.request.environ["werkzeug.request"] = None
  329. if app_ctx is not None:
  330. app_ctx.pop(exc)
  331. if ctx is not self:
  332. raise AssertionError(
  333. f"Popped wrong request context. ({ctx!r} instead of {self!r})"
  334. )
  335. def __enter__(self) -> RequestContext:
  336. self.push()
  337. return self
  338. def __exit__(
  339. self,
  340. exc_type: type | None,
  341. exc_value: BaseException | None,
  342. tb: TracebackType | None,
  343. ) -> None:
  344. self.pop(exc_value)
  345. def __repr__(self) -> str:
  346. return (
  347. f"<{type(self).__name__} {self.request.url!r}"
  348. f" [{self.request.method}] of {self.app.name}>"
  349. )