450 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			450 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
from __future__ import annotations
 | 
						|
 | 
						|
import contextvars
 | 
						|
import sys
 | 
						|
import typing as t
 | 
						|
from functools import update_wrapper
 | 
						|
from types import TracebackType
 | 
						|
 | 
						|
from werkzeug.exceptions import HTTPException
 | 
						|
 | 
						|
from . import typing as ft
 | 
						|
from .globals import _cv_app
 | 
						|
from .globals import _cv_request
 | 
						|
from .signals import appcontext_popped
 | 
						|
from .signals import appcontext_pushed
 | 
						|
 | 
						|
if t.TYPE_CHECKING:  # pragma: no cover
 | 
						|
    from _typeshed.wsgi import WSGIEnvironment
 | 
						|
 | 
						|
    from .app import Flask
 | 
						|
    from .sessions import SessionMixin
 | 
						|
    from .wrappers import Request
 | 
						|
 | 
						|
 | 
						|
# a singleton sentinel value for parameter defaults
 | 
						|
_sentinel = object()
 | 
						|
 | 
						|
 | 
						|
class _AppCtxGlobals:
 | 
						|
    """A plain object. Used as a namespace for storing data during an
 | 
						|
    application context.
 | 
						|
 | 
						|
    Creating an app context automatically creates this object, which is
 | 
						|
    made available as the :data:`g` proxy.
 | 
						|
 | 
						|
    .. describe:: 'key' in g
 | 
						|
 | 
						|
        Check whether an attribute is present.
 | 
						|
 | 
						|
        .. versionadded:: 0.10
 | 
						|
 | 
						|
    .. describe:: iter(g)
 | 
						|
 | 
						|
        Return an iterator over the attribute names.
 | 
						|
 | 
						|
        .. versionadded:: 0.10
 | 
						|
    """
 | 
						|
 | 
						|
    # Define attr methods to let mypy know this is a namespace object
 | 
						|
    # that has arbitrary attributes.
 | 
						|
 | 
						|
    def __getattr__(self, name: str) -> t.Any:
 | 
						|
        try:
 | 
						|
            return self.__dict__[name]
 | 
						|
        except KeyError:
 | 
						|
            raise AttributeError(name) from None
 | 
						|
 | 
						|
    def __setattr__(self, name: str, value: t.Any) -> None:
 | 
						|
        self.__dict__[name] = value
 | 
						|
 | 
						|
    def __delattr__(self, name: str) -> None:
 | 
						|
        try:
 | 
						|
            del self.__dict__[name]
 | 
						|
        except KeyError:
 | 
						|
            raise AttributeError(name) from None
 | 
						|
 | 
						|
    def get(self, name: str, default: t.Any | None = None) -> t.Any:
 | 
						|
        """Get an attribute by name, or a default value. Like
 | 
						|
        :meth:`dict.get`.
 | 
						|
 | 
						|
        :param name: Name of attribute to get.
 | 
						|
        :param default: Value to return if the attribute is not present.
 | 
						|
 | 
						|
        .. versionadded:: 0.10
 | 
						|
        """
 | 
						|
        return self.__dict__.get(name, default)
 | 
						|
 | 
						|
    def pop(self, name: str, default: t.Any = _sentinel) -> t.Any:
 | 
						|
        """Get and remove an attribute by name. Like :meth:`dict.pop`.
 | 
						|
 | 
						|
        :param name: Name of attribute to pop.
 | 
						|
        :param default: Value to return if the attribute is not present,
 | 
						|
            instead of raising a ``KeyError``.
 | 
						|
 | 
						|
        .. versionadded:: 0.11
 | 
						|
        """
 | 
						|
        if default is _sentinel:
 | 
						|
            return self.__dict__.pop(name)
 | 
						|
        else:
 | 
						|
            return self.__dict__.pop(name, default)
 | 
						|
 | 
						|
    def setdefault(self, name: str, default: t.Any = None) -> t.Any:
 | 
						|
        """Get the value of an attribute if it is present, otherwise
 | 
						|
        set and return a default value. Like :meth:`dict.setdefault`.
 | 
						|
 | 
						|
        :param name: Name of attribute to get.
 | 
						|
        :param default: Value to set and return if the attribute is not
 | 
						|
            present.
 | 
						|
 | 
						|
        .. versionadded:: 0.11
 | 
						|
        """
 | 
						|
        return self.__dict__.setdefault(name, default)
 | 
						|
 | 
						|
    def __contains__(self, item: str) -> bool:
 | 
						|
        return item in self.__dict__
 | 
						|
 | 
						|
    def __iter__(self) -> t.Iterator[str]:
 | 
						|
        return iter(self.__dict__)
 | 
						|
 | 
						|
    def __repr__(self) -> str:
 | 
						|
        ctx = _cv_app.get(None)
 | 
						|
        if ctx is not None:
 | 
						|
            return f"<flask.g of '{ctx.app.name}'>"
 | 
						|
        return object.__repr__(self)
 | 
						|
 | 
						|
 | 
						|
def after_this_request(
 | 
						|
    f: ft.AfterRequestCallable[t.Any],
 | 
						|
) -> ft.AfterRequestCallable[t.Any]:
 | 
						|
    """Executes a function after this request.  This is useful to modify
 | 
						|
    response objects.  The function is passed the response object and has
 | 
						|
    to return the same or a new one.
 | 
						|
 | 
						|
    Example::
 | 
						|
 | 
						|
        @app.route('/')
 | 
						|
        def index():
 | 
						|
            @after_this_request
 | 
						|
            def add_header(response):
 | 
						|
                response.headers['X-Foo'] = 'Parachute'
 | 
						|
                return response
 | 
						|
            return 'Hello World!'
 | 
						|
 | 
						|
    This is more useful if a function other than the view function wants to
 | 
						|
    modify a response.  For instance think of a decorator that wants to add
 | 
						|
    some headers without converting the return value into a response object.
 | 
						|
 | 
						|
    .. versionadded:: 0.9
 | 
						|
    """
 | 
						|
    ctx = _cv_request.get(None)
 | 
						|
 | 
						|
    if ctx is None:
 | 
						|
        raise RuntimeError(
 | 
						|
            "'after_this_request' can only be used when a request"
 | 
						|
            " context is active, such as in a view function."
 | 
						|
        )
 | 
						|
 | 
						|
    ctx._after_request_functions.append(f)
 | 
						|
    return f
 | 
						|
 | 
						|
 | 
						|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
 | 
						|
 | 
						|
 | 
						|
def copy_current_request_context(f: F) -> F:
 | 
						|
    """A helper function that decorates a function to retain the current
 | 
						|
    request context.  This is useful when working with greenlets.  The moment
 | 
						|
    the function is decorated a copy of the request context is created and
 | 
						|
    then pushed when the function is called.  The current session is also
 | 
						|
    included in the copied request context.
 | 
						|
 | 
						|
    Example::
 | 
						|
 | 
						|
        import gevent
 | 
						|
        from flask import copy_current_request_context
 | 
						|
 | 
						|
        @app.route('/')
 | 
						|
        def index():
 | 
						|
            @copy_current_request_context
 | 
						|
            def do_some_work():
 | 
						|
                # do some work here, it can access flask.request or
 | 
						|
                # flask.session like you would otherwise in the view function.
 | 
						|
                ...
 | 
						|
            gevent.spawn(do_some_work)
 | 
						|
            return 'Regular response'
 | 
						|
 | 
						|
    .. versionadded:: 0.10
 | 
						|
    """
 | 
						|
    ctx = _cv_request.get(None)
 | 
						|
 | 
						|
    if ctx is None:
 | 
						|
        raise RuntimeError(
 | 
						|
            "'copy_current_request_context' can only be used when a"
 | 
						|
            " request context is active, such as in a view function."
 | 
						|
        )
 | 
						|
 | 
						|
    ctx = ctx.copy()
 | 
						|
 | 
						|
    def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
 | 
						|
        with ctx:
 | 
						|
            return ctx.app.ensure_sync(f)(*args, **kwargs)
 | 
						|
 | 
						|
    return update_wrapper(wrapper, f)  # type: ignore[return-value]
 | 
						|
 | 
						|
 | 
						|
def has_request_context() -> bool:
 | 
						|
    """If you have code that wants to test if a request context is there or
 | 
						|
    not this function can be used.  For instance, you may want to take advantage
 | 
						|
    of request information if the request object is available, but fail
 | 
						|
    silently if it is unavailable.
 | 
						|
 | 
						|
    ::
 | 
						|
 | 
						|
        class User(db.Model):
 | 
						|
 | 
						|
            def __init__(self, username, remote_addr=None):
 | 
						|
                self.username = username
 | 
						|
                if remote_addr is None and has_request_context():
 | 
						|
                    remote_addr = request.remote_addr
 | 
						|
                self.remote_addr = remote_addr
 | 
						|
 | 
						|
    Alternatively you can also just test any of the context bound objects
 | 
						|
    (such as :class:`request` or :class:`g`) for truthness::
 | 
						|
 | 
						|
        class User(db.Model):
 | 
						|
 | 
						|
            def __init__(self, username, remote_addr=None):
 | 
						|
                self.username = username
 | 
						|
                if remote_addr is None and request:
 | 
						|
                    remote_addr = request.remote_addr
 | 
						|
                self.remote_addr = remote_addr
 | 
						|
 | 
						|
    .. versionadded:: 0.7
 | 
						|
    """
 | 
						|
    return _cv_request.get(None) is not None
 | 
						|
 | 
						|
 | 
						|
def has_app_context() -> bool:
 | 
						|
    """Works like :func:`has_request_context` but for the application
 | 
						|
    context.  You can also just do a boolean check on the
 | 
						|
    :data:`current_app` object instead.
 | 
						|
 | 
						|
    .. versionadded:: 0.9
 | 
						|
    """
 | 
						|
    return _cv_app.get(None) is not None
 | 
						|
 | 
						|
 | 
						|
class AppContext:
 | 
						|
    """The app context contains application-specific information. An app
 | 
						|
    context is created and pushed at the beginning of each request if
 | 
						|
    one is not already active. An app context is also pushed when
 | 
						|
    running CLI commands.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, app: Flask) -> None:
 | 
						|
        self.app = app
 | 
						|
        self.url_adapter = app.create_url_adapter(None)
 | 
						|
        self.g: _AppCtxGlobals = app.app_ctx_globals_class()
 | 
						|
        self._cv_tokens: list[contextvars.Token[AppContext]] = []
 | 
						|
 | 
						|
    def push(self) -> None:
 | 
						|
        """Binds the app context to the current context."""
 | 
						|
        self._cv_tokens.append(_cv_app.set(self))
 | 
						|
        appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync)
 | 
						|
 | 
						|
    def pop(self, exc: BaseException | None = _sentinel) -> None:  # type: ignore
 | 
						|
        """Pops the app context."""
 | 
						|
        try:
 | 
						|
            if len(self._cv_tokens) == 1:
 | 
						|
                if exc is _sentinel:
 | 
						|
                    exc = sys.exc_info()[1]
 | 
						|
                self.app.do_teardown_appcontext(exc)
 | 
						|
        finally:
 | 
						|
            ctx = _cv_app.get()
 | 
						|
            _cv_app.reset(self._cv_tokens.pop())
 | 
						|
 | 
						|
        if ctx is not self:
 | 
						|
            raise AssertionError(
 | 
						|
                f"Popped wrong app context. ({ctx!r} instead of {self!r})"
 | 
						|
            )
 | 
						|
 | 
						|
        appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)
 | 
						|
 | 
						|
    def __enter__(self) -> AppContext:
 | 
						|
        self.push()
 | 
						|
        return self
 | 
						|
 | 
						|
    def __exit__(
 | 
						|
        self,
 | 
						|
        exc_type: type | None,
 | 
						|
        exc_value: BaseException | None,
 | 
						|
        tb: TracebackType | None,
 | 
						|
    ) -> None:
 | 
						|
        self.pop(exc_value)
 | 
						|
 | 
						|
 | 
						|
class RequestContext:
 | 
						|
    """The request context contains per-request information. The Flask
 | 
						|
    app creates and pushes it at the beginning of the request, then pops
 | 
						|
    it at the end of the request. It will create the URL adapter and
 | 
						|
    request object for the WSGI environment provided.
 | 
						|
 | 
						|
    Do not attempt to use this class directly, instead use
 | 
						|
    :meth:`~flask.Flask.test_request_context` and
 | 
						|
    :meth:`~flask.Flask.request_context` to create this object.
 | 
						|
 | 
						|
    When the request context is popped, it will evaluate all the
 | 
						|
    functions registered on the application for teardown execution
 | 
						|
    (:meth:`~flask.Flask.teardown_request`).
 | 
						|
 | 
						|
    The request context is automatically popped at the end of the
 | 
						|
    request. When using the interactive debugger, the context will be
 | 
						|
    restored so ``request`` is still accessible. Similarly, the test
 | 
						|
    client can preserve the context after the request ends. However,
 | 
						|
    teardown functions may already have closed some resources such as
 | 
						|
    database connections.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        app: Flask,
 | 
						|
        environ: WSGIEnvironment,
 | 
						|
        request: Request | None = None,
 | 
						|
        session: SessionMixin | None = None,
 | 
						|
    ) -> None:
 | 
						|
        self.app = app
 | 
						|
        if request is None:
 | 
						|
            request = app.request_class(environ)
 | 
						|
            request.json_module = app.json
 | 
						|
        self.request: Request = request
 | 
						|
        self.url_adapter = None
 | 
						|
        try:
 | 
						|
            self.url_adapter = app.create_url_adapter(self.request)
 | 
						|
        except HTTPException as e:
 | 
						|
            self.request.routing_exception = e
 | 
						|
        self.flashes: list[tuple[str, str]] | None = None
 | 
						|
        self.session: SessionMixin | None = session
 | 
						|
        # Functions that should be executed after the request on the response
 | 
						|
        # object.  These will be called before the regular "after_request"
 | 
						|
        # functions.
 | 
						|
        self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = []
 | 
						|
 | 
						|
        self._cv_tokens: list[
 | 
						|
            tuple[contextvars.Token[RequestContext], AppContext | None]
 | 
						|
        ] = []
 | 
						|
 | 
						|
    def copy(self) -> RequestContext:
 | 
						|
        """Creates a copy of this request context with the same request object.
 | 
						|
        This can be used to move a request context to a different greenlet.
 | 
						|
        Because the actual request object is the same this cannot be used to
 | 
						|
        move a request context to a different thread unless access to the
 | 
						|
        request object is locked.
 | 
						|
 | 
						|
        .. versionadded:: 0.10
 | 
						|
 | 
						|
        .. versionchanged:: 1.1
 | 
						|
           The current session object is used instead of reloading the original
 | 
						|
           data. This prevents `flask.session` pointing to an out-of-date object.
 | 
						|
        """
 | 
						|
        return self.__class__(
 | 
						|
            self.app,
 | 
						|
            environ=self.request.environ,
 | 
						|
            request=self.request,
 | 
						|
            session=self.session,
 | 
						|
        )
 | 
						|
 | 
						|
    def match_request(self) -> None:
 | 
						|
        """Can be overridden by a subclass to hook into the matching
 | 
						|
        of the request.
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            result = self.url_adapter.match(return_rule=True)  # type: ignore
 | 
						|
            self.request.url_rule, self.request.view_args = result  # type: ignore
 | 
						|
        except HTTPException as e:
 | 
						|
            self.request.routing_exception = e
 | 
						|
 | 
						|
    def push(self) -> None:
 | 
						|
        # Before we push the request context we have to ensure that there
 | 
						|
        # is an application context.
 | 
						|
        app_ctx = _cv_app.get(None)
 | 
						|
 | 
						|
        if app_ctx is None or app_ctx.app is not self.app:
 | 
						|
            app_ctx = self.app.app_context()
 | 
						|
            app_ctx.push()
 | 
						|
        else:
 | 
						|
            app_ctx = None
 | 
						|
 | 
						|
        self._cv_tokens.append((_cv_request.set(self), app_ctx))
 | 
						|
 | 
						|
        # Open the session at the moment that the request context is available.
 | 
						|
        # This allows a custom open_session method to use the request context.
 | 
						|
        # Only open a new session if this is the first time the request was
 | 
						|
        # pushed, otherwise stream_with_context loses the session.
 | 
						|
        if self.session is None:
 | 
						|
            session_interface = self.app.session_interface
 | 
						|
            self.session = session_interface.open_session(self.app, self.request)
 | 
						|
 | 
						|
            if self.session is None:
 | 
						|
                self.session = session_interface.make_null_session(self.app)
 | 
						|
 | 
						|
        # Match the request URL after loading the session, so that the
 | 
						|
        # session is available in custom URL converters.
 | 
						|
        if self.url_adapter is not None:
 | 
						|
            self.match_request()
 | 
						|
 | 
						|
    def pop(self, exc: BaseException | None = _sentinel) -> None:  # type: ignore
 | 
						|
        """Pops the request context and unbinds it by doing that.  This will
 | 
						|
        also trigger the execution of functions registered by the
 | 
						|
        :meth:`~flask.Flask.teardown_request` decorator.
 | 
						|
 | 
						|
        .. versionchanged:: 0.9
 | 
						|
           Added the `exc` argument.
 | 
						|
        """
 | 
						|
        clear_request = len(self._cv_tokens) == 1
 | 
						|
 | 
						|
        try:
 | 
						|
            if clear_request:
 | 
						|
                if exc is _sentinel:
 | 
						|
                    exc = sys.exc_info()[1]
 | 
						|
                self.app.do_teardown_request(exc)
 | 
						|
 | 
						|
                request_close = getattr(self.request, "close", None)
 | 
						|
                if request_close is not None:
 | 
						|
                    request_close()
 | 
						|
        finally:
 | 
						|
            ctx = _cv_request.get()
 | 
						|
            token, app_ctx = self._cv_tokens.pop()
 | 
						|
            _cv_request.reset(token)
 | 
						|
 | 
						|
            # get rid of circular dependencies at the end of the request
 | 
						|
            # so that we don't require the GC to be active.
 | 
						|
            if clear_request:
 | 
						|
                ctx.request.environ["werkzeug.request"] = None
 | 
						|
 | 
						|
            if app_ctx is not None:
 | 
						|
                app_ctx.pop(exc)
 | 
						|
 | 
						|
            if ctx is not self:
 | 
						|
                raise AssertionError(
 | 
						|
                    f"Popped wrong request context. ({ctx!r} instead of {self!r})"
 | 
						|
                )
 | 
						|
 | 
						|
    def __enter__(self) -> RequestContext:
 | 
						|
        self.push()
 | 
						|
        return self
 | 
						|
 | 
						|
    def __exit__(
 | 
						|
        self,
 | 
						|
        exc_type: type | None,
 | 
						|
        exc_value: BaseException | None,
 | 
						|
        tb: TracebackType | None,
 | 
						|
    ) -> None:
 | 
						|
        self.pop(exc_value)
 | 
						|
 | 
						|
    def __repr__(self) -> str:
 | 
						|
        return (
 | 
						|
            f"<{type(self).__name__} {self.request.url!r}"
 | 
						|
            f" [{self.request.method}] of {self.app.name}>"
 | 
						|
        )
 |