flask/ctx.py 这部分代码的功能是实现了 Flask 应用程序上下文和请求上下文的基本功能,以及定义了一些在 Flask 应用程序中常用的全局变量和函数。具体实现了以下功能:
-
实现了 Flask 应用程序上下文类(AppContext)和请求上下文类(RequestContext),这两个类都是 Flask 上下文对象的基类,提供了一些 Flask 应用程序和请求的相关信息。
-
实现了 Flask 应用程序上下文栈类(AppContextStack)和请求上下文栈类(RequestContextStack),用于存储 Flask 应用程序和请求的上下文对象。
-
定义了一些全局变量,例如 g、current_app 等,这些变量可以在 Flask 应用程序中全局访问。
-
定义了一些常用的函数,例如 get_flashed_messages、send_static_file 等,这些函数可以方便地处理一些常见的功能,例如获取闪现消息、发送静态文件等。
-
同时提供了一些辅助函数,例如 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}>"
)