flask/ctx.py 分析

请添加图片描述
flask/ctx.py 这部分代码的功能是实现了 Flask 应用程序上下文和请求上下文的基本功能,以及定义了一些在 Flask 应用程序中常用的全局变量和函数。具体实现了以下功能:

  1. 实现了 Flask 应用程序上下文类(AppContext)和请求上下文类(RequestContext),这两个类都是 Flask 上下文对象的基类,提供了一些 Flask 应用程序和请求的相关信息。

  2. 实现了 Flask 应用程序上下文栈类(AppContextStack)和请求上下文栈类(RequestContextStack),用于存储 Flask 应用程序和请求的上下文对象。

  3. 定义了一些全局变量,例如 g、current_app 等,这些变量可以在 Flask 应用程序中全局访问。

  4. 定义了一些常用的函数,例如 get_flashed_messages、send_static_file 等,这些函数可以方便地处理一些常见的功能,例如获取闪现消息、发送静态文件等。

  5. 同时提供了一些辅助函数,例如 after_request、appcontext_pushed 等,这些函数可以方便地处理 Flask 应用程序和请求的上下文信息。

以下是代码具体注释:

# 引入必要的库
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

# 引入 Flask 中的其他模块
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

# 如果是 TYPE_CHECKING 环境,则引入需要用到的类
if t.TYPE_CHECKING:
    from .app import Flask
    from .sessions import SessionMixin
    from .wrappers import Request

# 定义一个单例标记
_sentinel = object()


# 定义命名空间类,用以在应用程序上下文期间存储数据
class _AppCtxGlobals:
    """An object, used as a namespace to store data during application
    context.

    This is automatically created when an application context is pushed, and
    is available as :data:`flask.g`.

    .. describe:: 'key' in g

        Check if an attribute is present.

        .. versionadded:: 0.10

    .. describe:: iter(g)

        Return an iterator over the attribute names.

        .. versionadded:: 0.10
    """

    # 定义 attr 方法,以便 mypy 知道这是一个命名空间对象,用以存储任意属性
    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.  Similar to
        :meth:`dict.get`.

        :param name: the name of the attribute to get.
        :param default: the default value to return if the attribute is
            not present.

        .. versionadded:: 0.10
        """
        return self.__dict__.get(name, default)

    # 用以按名称获取并弹出属性值,如果属性不存在,则输出 default 值
    def pop(self, name: str, default: t.Any = _sentinel) -> t.Any:
        """Get and remove an attribute by name.  Similar to :meth:`dict.pop`.

        :param name: the name of the attribute to pop.
        :param default: the 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, or set it to a default value.
        Similar to :meth:`dict.setdefault`.

        :param name: the name of the attribute to get.
        :param default: the 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__)

    # 用以获取一个可打印 _AppCtxGlobals 类对象
    def __repr__(self) -> str:
        ctx = _cv_app.get(None)
        if ctx is not None:
            return f"<flask.g of '{ctx.app.name}'>"


# 定义一个装饰器 'after_this_request'
def after_this_request(f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
    """
    在当前请求之后执行一个函数。用于修改响应对象。该函数接收响应对象作为参数,
    必须返回新的响应对象或修改后的原响应对象。

    例如:
        @app.route('/')
        def index():
            @after_this_request
            def add_header(response):
                response.headers['X-Foo'] = 'Parachute'
                return response
            return 'Hello World!'

    如果需要在视图函数之外修改响应对象,这种方法会更加方便。
    例如:某个装饰器希望在不转换返回值成响应的情况下添加某些头部信息。

    .. versionadded:: 0.9
    """
    # 获取当前请求上下文
    ctx = _cv_request.get(None)

    if ctx is None:
        # 如果请求上下文不存在,则抛出错误
        raise RuntimeError(
            "'after_this_request' 仅可在请求上下文被激活的时候使用,例如在视图函数中。"
        )

    ctx._after_request_functions.append(f)
    return f


# 定义一个装饰器 'copy_current_request_context'
def copy_current_request_context(f: t.Callable) -> t.Callable:
    """
    一个装饰器函数,用于保存当前请求上下文。在使用协程的时候非常有用。当函数被装饰时,
    复制了请求上下文并保存下来,当函数调用时使得请求上下文变得可用,并在复制的请求上下文中包括会话。

    例如:
        import gevent
        from flask import copy_current_request_context

        @app.route('/')
        def index():
            @copy_current_request_context
            def do_some_work():
                # 在函数中可以正常访问 flask.request 和 flask.session,就像在视图函数中一样。
                ...
            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' 仅可在请求上下文被激活的时候使用,例如在视图函数中。"
        )

    # 复制请求上下文
    ctx = ctx.copy()

    def wrapper(*args, **kwargs):
        # 将请求上下文作为当前上下文
        with ctx:
            # 将异步函数转换成同步函数并执行
            return ctx.app.ensure_sync(f)(*args, **kwargs)

    return update_wrapper(wrapper, f)


# 定义一个函数 'has_request_context'
def has_request_context() -> bool:
    """
    如果你的代码需要判断请求环境中是否存在请求上下文,那么可以使用这个函数。
    例如,当前请求可用时需要获取请求信息,如果请求不可用则需要在静默失败后返回。

    例如:
        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

    或者你也可以直接测试绑定的对象(例如 request 或 g)是否真实存在。

    例如:
        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


# 定义一个函数 'has_app_context'
def has_app_context() -> bool:
    """与 :func:`has_request_context` 类似,但是针对应用上下文。
    您也可以只对 :data:`current_app` 对象进行布尔检查。

    .. versionadded:: 0.9
    """
    # 判断是否存在应用上下文
    return _cv_app.get(None) is not None


class AppContext:
    """应用上下文包含应用程序特定的信息。如果没有活动的应用上下文,则会在每个请求的开始时创建并推送一个应用上下文。
    运行 CLI 命令时也会推送应用上下文。
    """

    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] = []

    def push(self) -> None:
        """将应用上下文绑定到当前上下文。"""
        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
        """将应用上下文弹出。"""
        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:
    """请求上下文包含每个请求的信息。 Flask 应用程序在请求开始时创建并推送它,然后在请求结束时取消推送。它将为提供的 WSGI 环境创建 URL 适配器和请求对象。

    不要直接尝试使用此类,而是使用` Flask.test_request_context `和` Flask.request_context `来创建此对象。

    当请求上下文被取消推送时,它将评估应用程序上注册的所有功能以执行拆卸(' teardown_request`)。

    请求上下文在请求结束时会自动取消推送。 在使用交互式调试器时,上下文将被恢复,因此“请求”仍然可以访问。 类似地,测试客户端在请求结束后可以保留上下文。 但是,拆卸功能可能已关闭一些资源,例如数据库连接。
    """

    def __init__(
        self,
        app: Flask,
        environ: dict,
        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
        # 在响应对象上执行请求后应执行的函数。这些函数将在常规'after_request'函数之前调用。
        self._after_request_functions: list[ft.AfterRequestCallable] = []

        self._cv_tokens: list[tuple[contextvars.Token, AppContext | None]] = []

    def copy(self) -> RequestContext:
        """创建具有相同请求对象的此请求上下文的副本。这可用于将请求上下文移动到不同的 greenlet。由于实际请求对象相同,因此不能将此方法用于将请求上下文移动到不同的线程,除非锁定对请求对象的访问。

        .. versionadded:: 0.10

        .. versionchanged:: 1.1
           使用当前会话对象而不是重新加载原始数据。这可以防止' flask.session`指向过时的对象。
        """
        return self.__class__(
            self.app,
            environ=self.request.environ,
            request=self.request,
            session=self.session,
        )

    def match_request(self) -> None:
        """可以由子类重写以挂钩到请求的匹配中。
        """
        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:
        # 在我们推送请求上下文之前,我们必须确保存在应用程序上下文。
        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_session方法使用请求上下文。仅在第一次推送请求时打开新会话,否则stream_with_context将丢失会话。
        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)

        # 在加载会话之后匹配请求URL,以便在自定义URL转换器中可用会话。
        if self.url_adapter is not None:
            self.match_request()

    def pop(self, exc: BaseException | None = _sentinel) -> None:  # type: ignore
        """弹出请求上下文并通过这样做解绑定。也将触发由'meth ~ flask.Flask.teardown_request`修饰符注册的函数的执行。

        .. versionchanged:: 0.9
           添加了'exc`参数。
        """
        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)

            # 在请求结束时消除循环依赖,以便不需要激活 GC。
            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}>"
        )

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
报错如下: Traceback (most recent call last): File "/usr/local/lib64/python3.6/site-packages/flask/app.py", line 2091, in __call__ return self.wsgi_app(environ, start_response) File "/usr/local/lib64/python3.6/site-packages/flask/app.py", line 2076, in wsgi_app response = self.handle_exception(e) File "/usr/local/lib64/python3.6/site-packages/flask/app.py", line 2073, in wsgi_app response = self.full_dispatch_request() File "/usr/local/lib64/python3.6/site-packages/flask/app.py", line 1518, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/local/lib64/python3.6/site-packages/flask/app.py", line 1516, in full_dispatch_request rv = self.dispatch_request() File "/usr/local/lib64/python3.6/site-packages/flask/app.py", line 1502, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "/temp/py/app-07240001.py", line 16, in display_yaml return render_template('index.html', highlighted_data=highlighted_data, css=css) File "/usr/local/lib64/python3.6/site-packages/flask/templating.py", line 150, in render_template ctx.app, File "/usr/local/lib64/python3.6/site-packages/flask/templating.py", line 128, in _render rv = template.render(context) File "/usr/local/lib/python3.6/site-packages/jinja2/environment.py", line 1291, in render self.environment.handle_exception() File "/usr/local/lib/python3.6/site-packages/jinja2/environment.py", line 925, in handle_exception raise rewrite_traceback_stack(source=source) File "/temp/py/templates/index.html", line 16, in top-level template code var originalData = {{ data|tojson|safe }}; File "/usr/local/lib/python3.6/site-packages/jinja2/filters.py", line 1673, in do_tojson return htmlsafe_json_dumps(value, dumps=dumps, **kwargs) File "/usr/local/lib/python3.6/site-packages/jinja2/utils.py", line 736, in htmlsafe_json_dumps dumps(obj, **kwargs) File "/usr/local/lib64/python3.6/site-packages/flask/json/__init__.py", line 139, in dumps rv = _json.dumps(obj, **kwargs) File "/usr/lib64/python3.6/json/__init__.py", line 238, in dumps **kw).encode(obj) File "/usr/lib64/python3.6/json/encoder.py", line 199, in encode chunks = self.iterencode(o, _one_shot=True) File "/usr/lib64/python3.6/json/encoder.py", line 257, in iterencode return _iterencode(o, 0) File "/usr/local/lib64/python3.6/site-packages/flask/json/__init__.py", line 57, in default return super().default(o) File "/usr/lib64/python3.6/json/encoder.py", line 180, in default o.__class__.__name__) TypeError: Object of type 'Undefined' is not JSON serializable
07-25
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

不打赏也没关系,点点关注呀

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值