ctx.py 14 KB

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