ctx.py 15 KB

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