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