import typing as t from contextvars import ContextVar
from werkzeug.local import LocalProxy
if t.TYPE_CHECKING: # pragma: no cover from .app import Flask from .ctx import _AppCtxGlobals from .ctx import AppContext from .ctx import RequestContext from .sessions import SessionMixin from .wrappers import Request
_no_app_msg = """\ Working outside of application context. This typically means that you attempted to use functionality that needed the current application. To solve this, set up an application context with app.app_context(). See the documentation for more information.\ """ _cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx") app_ctx: AppContext = LocalProxy( # type: ignore[assignment] _cv_app, unbound_message=_no_app_msg ) current_app: Flask = LocalProxy( # type: ignore[assignment] _cv_app, "app", unbound_message=_no_app_msg ) g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment] _cv_app, "g", unbound_message=_no_app_msg )
_no_req_msg = """\ Working outside of request context. This typically means that you attempted to use functionality that needed an active HTTP request. Consult the documentation on testing for information about how to avoid this problem.\ """ _cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx") request_ctx: RequestContext = LocalProxy( # type: ignore[assignment] _cv_request, unbound_message=_no_req_msg ) request: Request = LocalProxy( # type: ignore[assignment] _cv_request, "request", unbound_message=_no_req_msg ) session: SessionMixin = LocalProxy( # type: ignore[assignment] _cv_request, "session", unbound_message=_no_req_msg )
# ctx.py 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.
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 ~flask.Flask.test_request_context and ~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 (~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.
阅读了 globals.py 中的代码后不难看出,为了理解 Flask 上下文的实现,我们的下一个目标就是探索 LocalProxy 这个类。但在此之前,我们需要先从线程上下文的实现方式谈起。为了避免不同线程共用同一上下文时产生竞争或未定义行为,程序往往需要实现一个能够线程隔离的上下文。一个容易想到的实现方式是,使用一个字典存储本线程的上下文,再使用一个以各个线程的线程 ID 为键,以该线程 ID 对应的线程的上下文作为值的字典来存储所有线程的上下文。这样,每个线程只需要以自己的线程 ID 为索引,便可以分别从同一全局变量中访问自己的上下文,且不同线程的上下文彼此隔离。
# app.py defrequest_context(self, environ: WSGIEnvironment) -> RequestContext: """Create a :class:`~flask.ctx.RequestContext` representing a WSGI environment. Use a ``with`` block to push the context, which will make :data:`request` point at this request. See :doc:`/reqcontext`. Typically you should not call this from your own code. A request context is automatically pushed by the :meth:`wsgi_app` when handling a request. Use :meth:`test_request_context` to create an environment and context instead of this method. :param environ: a WSGI environment """ return RequestContext(self, environ)
# ctx.py classRequestContext: """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 isNone: 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]] = []
defmatch_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
defpush(self) -> None: # Before we push the request context we have to ensure that there # is an application context. app_ctx = _cv_app.get(None)
# 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. ifself.session isNone: session_interface = self.app.session_interface self.session = session_interface.open_session(self.app, self.request)
# Match the request URL after loading the session, so that the # session is available in custom URL converters. ifself.url_adapter isnotNone: self.match_request()
defpop(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)
# 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 isnotNone: app_ctx.pop(exc)
if ctx isnotself: raise AssertionError( f"Popped wrong request context. ({ctx!r} instead of {self!r})" )
因此,Flask 上下文的管理逻辑实际上为,当收到请求时,创建当前进程/线程的上下文对象(RequestContext 对象),上下文对象在被创建时,会将 WSGI Server 传递的 environ 参数封装为一个 Request 对象并存储到上下文对象的 request 成员中。然后,Flask 将调用上下文对象的 push 方法,将自身压入到请求上下文的堆栈中