Flask source code article: Thoroughly understand the principle of Flask context

Source series:

Flask source code articles: wsgi, Werkzeug and Flask startup workflow
Flask source code articles: Flask routing rules and request matching process

When talking about context in Flask, we need to discuss the following topics.

Understanding ThreadLocal

ThreadLocalIt is a global object based on thread isolation. Each thread can have its own variables without interfering with each other.

For ThreadLocaldetails, please refer to the following:

https://blog.csdn.net/qq_43745578/article/details/129369272?spm=1001.2014.3001.5501

Local class

LocalwerkzeugClasses are located in local files under the Flask package. Its source code is as follows:

class Local:
    __slots__ = ("_storage",)

    def __init__(self) -> None:
        object.__setattr__(self, "_storage", ContextVar("local_storage"))

    @property
    def __storage__(self) -> t.Dict[str, t.Any]:
        warnings.warn(
            "'__storage__' is deprecated and will be removed in Werkzeug 2.1.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self._storage.get({
    
    })  # type: ignore

    @property
    def __ident_func__(self) -> t.Callable[[], int]:
        warnings.warn(
            "'__ident_func__' is deprecated and will be removed in"
            " Werkzeug 2.1. It should not be used in Python 3.7+.",
            DeprecationWarning,
            stacklevel=2,
        )
        return _get_ident  # type: ignore

    @__ident_func__.setter
    def __ident_func__(self, func: t.Callable[[], int]) -> None:
        warnings.warn(
            "'__ident_func__' is deprecated and will be removed in"
            " Werkzeug 2.1. Setting it no longer has any effect.",
            DeprecationWarning,
            stacklevel=2,
        )

    def __iter__(self) -> t.Iterator[t.Tuple[int, t.Any]]:
        return iter(self._storage.get({
    
    }).items())

    def __call__(self, proxy: str) -> "LocalProxy":
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self) -> None:
        __release_local__(self._storage)

    def __getattr__(self, name: str) -> t.Any:
        values = self._storage.get({
    
    })
        try:
            return values[name]
        except KeyError:
            raise AttributeError(name) from None

    def __setattr__(self, name: str, value: t.Any) -> None:
        values = self._storage.get({
    
    }).copy()
        values[name] = value
        self._storage.set(values)

    def __delattr__(self, name: str) -> None:
        values = self._storage.get({
    
    }).copy()
        try:
            del values[name]
            self._storage.set(values)
        except KeyError:
            raise AttributeError(name) from None

(1) Analysis __init__method

First look at the Localinitialization method of the class __init__, the code is as follows:

def __init__(self) -> None:
        object.__setattr__(self, "_storage", ContextVar("local_storage"))

The initialization method just sets a single underscore attribute for him _storage, and the attribute value object is ContextVara class .

Its source code is as follows:

class ContextVar:  # type: ignore
    """A fake ContextVar based on the previous greenlet/threading
    ident function. Used on Python 3.6, eventlet, and old versions
    of gevent.
    """

    def __init__(self, _name: str) -> None:
        self.storage: t.Dict[int, t.Dict[str, t.Any]] = {
    
    }

    def get(self, default: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
        return self.storage.get(_get_ident(), default)

    def set(self, value: t.Dict[str, t.Any]) -> None:
        self.storage[_get_ident()] = value

You can see ContextVarthat the object of the class has gettwo setmethods, and the variables of the thread are maintained in self.storagethe variables, self.storagewhich is a dictionary.

As we said before, ThreadLocalthe thread id will be used as the key, and then the variable of the thread will be saved. The similarity here is that getboth setmethods 属性获取信息,且根据return values ​​from the ``storage_get_ident()` method as keys.

This _get_ident()method is equivalent to getting the current thread id. It’s just a bit more advanced here. Look at the top of the local file and find _get_ident()the import. The code is as follows:

try:
    from greenlet import getcurrent as _get_ident
except ImportError:
    from threading import get_ident as _get_ident


def get_ident() -> int:
    warnings.warn(
        "'get_ident' is deprecated and will be removed in Werkzeug"
        " 2.1. Use 'greenlet.getcurrent' or 'threading.get_ident' for"
        " previous behavior.",
        DeprecationWarning,
        stacklevel=2,
    )
    return _get_ident()  # type: ignore

It can be seen that the local file greenletimports _get_identthe method from the module first, and then uses the thread when it cannot be found, so Localthe high level is that it can be used not only in the thread, but also in the coroutine.

( 2) Analysis property setting, acquisition and deletion methods

According to the source code, we can see Localthat the class implements three methods for attribute setting ( __setattr__), getting ( __getattr__) and deleting ( __delattr__).

And note that the above three methods actually call self._storagethe ContextVarobjects getand setmethods to complete data maintenance.

LocalStack class

LocalStackIt is also located in the local file under the Flask werkzeugpackage, which is the combination of the above Local and the stack. The source code is as follows:

class LocalStack:
    def __init__(self) -> None:
        self._local = Local()

    def __release_local__(self) -> None:
        self._local.__release_local__()

    @property
    def __ident_func__(self) -> t.Callable[[], int]:
        return self._local.__ident_func__

    @__ident_func__.setter
    def __ident_func__(self, value: t.Callable[[], int]) -> None:
        object.__setattr__(self._local, "__ident_func__", value)

    def __call__(self) -> "LocalProxy":
        def _lookup() -> t.Any:
            rv = self.top
            if rv is None:
                raise RuntimeError("object unbound")
            return rv

        return LocalProxy(_lookup)

    def push(self, obj: t.Any) -> t.List[t.Any]:
        """Pushes a new item to the stack"""
        rv = getattr(self._local, "stack", []).copy()
        rv.append(obj)
        self._local.stack = rv
        return rv  # type: ignore

    def pop(self) -> t.Any:
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, "stack", None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self) -> t.Any:
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

One of the private variables self._local = Local()maintains an Localobject.

There are also 3 methods related to the stack: push, pop, top. You can see that the LocalStack class self._localmaintains a property in it stack, and defaults to the first empty list. Then the three methods of push, pop, and top are to push an item from the top of the list, take an item from the top of the list, and get an item from the top of the list, and the list is still there.

So in fact, the data structure LocalStackmaintained inside the object self._localis similar to the following:

self._local = {
    
    
  "2491238714": {
    
      # 线程获取协程id
       "stack": []  # 栈信息
  }
}

The most important thing is that LocalStack uses Local to maintain a stack (the name of the key is stack), so this stack is still isolated from threads or coroutines! That is, using LocalStackclasses can maintain a stack of its own for each thread or coroutine!

LocalProxy class

Before understanding LocalProxy, we can briefly understand the lower partial function:

Partial functions are actually functoolsimplemented using the partial method of the module. Its main usage scenario is to preset parameters for a function, see the following example:

from functools import partial


def my_func(name, age):
    print(f'my_func:name-{
      
      name},age-{
      
      age}')


new_func = partial(my_func, 'ethan')
print(new_func)
# functools.partial(<function my_func at 0x7fb9c00e8040>, 'ethan')

new_func(18)
# my_func:name-ethan,age-18

It can be seen that when using the partial function, the first parameter is a function name or more broadly speaking, a callable object, and the second parameter is a preset parameter for the function passed in. In the above example, the name parameter of my_func is given The value of ethan is preset, and when new_func is called later, the name will always be ethan.

After understanding the partial function, let's look at it again LocalProxy.

LocalProxyIt is mainly used for proxy Localobjects or LocalStackobjects, and is responsible for forwarding all operations on itself to internal Localor LocalStackobjects.

In the above Localclass and LocalStackthe source code of the class, we can see that both classes have __call__methods and both return LocalProxyobjects.

code show as below:

def __call__(self, proxy: str) -> "LocalProxy":
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

The first parameter is Localthe object, and the second is the name to be proxied.

LocalProxyThe main source code is as follows (deleted):

class LocalProxy:
    """A proxy to the object bound to a :class:`Local`.

    .. code-block:: python

        from werkzeug.local import Local
        l = Local()

        # a proxy to whatever l.user is set to
        user = l("user")

        from werkzeug.local import LocalStack
        _request_stack = LocalStack()

        # a proxy to _request_stack.top
        request = _request_stack()

        # a proxy to the session attribute of the request proxy
        session = LocalProxy(lambda: request.session)

    :param local: The :class:`Local` or callable that provides the
        proxied object.
    :param name: The attribute name to look up on a :class:`Local`. Not
        used if a callable is given.
    """

    __slots__ = ("__local", "__name", "__wrapped__")

    def __init__(
        self,
        local: t.Union["Local", t.Callable[[], t.Any]],
        name: t.Optional[str] = None,
    ) -> None:
        object.__setattr__(self, "_LocalProxy__local", local)
        object.__setattr__(self, "_LocalProxy__name", name)

        if callable(local) and not hasattr(local, "__release_local__"):
            # "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, "__wrapped__", local)

    def _get_current_object(self) -> t.Any:
        """Return the current object.  This is useful if you want the real
        object behind the proxy at a time for performance reasons or because
        you want to pass the object into a different context.
        """
        if not hasattr(self.__local, "__release_local__"):  # type: ignore
            return self.__local()  # type: ignore

        try:
            # 这里主要是从local对象里拿药代理的对象数据
            return getattr(self.__local, self.__name)  # type: ignore
        except AttributeError:
            name = self.__name  # type: ignore
            raise RuntimeError(f"no object bound to {
      
      name}") from None
    
    # 以下是一些操作符定义,都转为_ProxyLookup对象了
    __str__ = _ProxyLookup(str)  # type: ignore
    __bytes__ = _ProxyLookup(bytes)
    __format__ = _ProxyLookup()  # type: ignore

In the initialization method, two double-underlined properties and the sum object.__setattr__()of the double underscore are set for the current object through the method , which are the name of the object and the agent to be proxied. In fact, these two properties are to set private properties for the current object for use inside the class._LocalProxy__local_LocalProxy__nameLocal

LocalProxyThe constructor has a callable parameter, which needs to return an Localinstance , and all subsequent attribute operations will be forwarded to the object returned by the callable.

LocalProxyThe _get_current_object()method gets self._localthe object to be proxied corresponding to the current thread or coroutine. The main code is return getattr(self.__local, self.__name) .

At the bottom of the source code, some underlined and underlined attributes are defined. When a class is used _ProxyLookup, this class will not be expanded. Its main function is to call the method of the object LocalProxywhen operating on the object, so as to obtain the proxy object, and then dialing the object. Take the appropriate action.LocalProxy_get_current_object()

From the above, LocalProxyit seems that it has nothing to do with partial functions, so let's look at the following example:

from werkzeug.local import LocalStack, LocalProxy
from functools import partial


def my_proxy(name):
    rv = ls.top
    return rv[name]


# 创建一个LocalStack对象
ls = LocalStack()
# 调用push方法,则会在内部变量self._local里维护我们的数据
ls.push({
    
    "request": {
    
    "method": "GET", "path": "/"}})

# 创建一个LocalProxy对象,传了一个偏函数,设置name参数为request,则表示这个代理对象要代理request
# 执行my_proxy函数,并且name参数为request,则从LocalStack对象pop方法获取栈最顶的数据,再获取request
l_proxy = LocalProxy(partial(my_proxy, "request"))

# 看一下我们的代理对象
print(l_proxy)
# {'method': 'GET', 'path': '/'}
# 最后我们的代理对象就成了如上一个字典,那么直接对代理对象的操作就是代替对原对象的操作
print(l_proxy.get("method"))
# GET

It is clear from the comments that when used in combination with the partial function, the operation on the object LocalProxyis realized instead of the operation on the proxy object.LocalProxy

In the context of Flask, such as the request context, in fact, the content of the above push is replaced by our Requestobject from the dictionary. Explain in detail below.

context globals file

Several global variables are defined in advance in flask.globalsthe module, which are the context objects in Flask. The source code is as follows:

import typing as t
from functools import partial

from werkzeug.local import LocalProxy
from werkzeug.local import LocalStack

if t.TYPE_CHECKING:
    from .app import Flask
    from .ctx import _AppCtxGlobals
    from .sessions import SessionMixin
    from .wrappers import Request

_request_ctx_err_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.\
"""
_app_ctx_err_msg = """\
Working outside of application context.

This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context().  See the
documentation for more information.\
"""


def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)


def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app


# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app: "Flask" = LocalProxy(_find_app)  # type: ignore
request: "Request" = LocalProxy(partial(_lookup_req_object, "request"))  # type: ignore
session: "SessionMixin" = LocalProxy(  # type: ignore
    partial(_lookup_req_object, "session")
)
g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g"))  # type: ignore

_request_ctx_stackFirst, two objects and are initialized _app_ctx_stack, both of which are LocalStackobjects. As mentioned earlier, LocalStackobjects are Localthe combination of stack and can be used to store variables of different threads without interfering with each other.

Then LocalProxy4 proxy objects are created by combining with the partial function:

  1. current_app_find_app: The object is proxied through the function _app_ctx_stack.top;
  2. request: The object _lookup_req_objectproxied _request_ctx_stack.topby the function request;
  3. session: The object _lookup_req_objectproxied _request_ctx_stack.topby the function session;
  4. g: The object _lookup_app_objectproxied _app_ctx_stack.topby the function g;

The creation of these four is very similar to our example in the previous section. They are all used to proxy the LocalProxydesired objects and complete the variable isolation between threads.

Among them request, and sessionbelong to the request context and are related to the request, current_appand gbelong to the application context and are related to the Flask app application.

request context

Now go back and look at what the request context is.

In flask, the view function needs to know the request information of its execution (request url, parameters, methods, etc.) and application information (database initialized in the application, etc.) in order to run correctly.

The most intuitive way is to encapsulate this information into an object and pass it to the view function as a parameter. But in this case, all view functions need to add corresponding parameters, even if it is not used inside the function.

Flask's approach is to use this information as something similar to global variables , which can be from flask import requestobtained . But the difference between these objects and global variables is that they must be dynamic, because in the case of multi-threads or multi-coroutines, each thread or coroutine gets its own unique object and will not interfere with each other.

This is what our Local, LocalStack, and LocalProxyask for cooperation to accomplish.

Now look back at the process of our request, you can review the following article: Flask source code: Flask routing rules and request matching process .

The relationship between the request process and the upper and lower levels is mainly reflected in the following steps:

(1)wsgi_app

As mentioned in the previous article, when a request comes in, it will go through wsgi_appthe method of the Falsk object, and then look at the source code.

Among them are only the main codes related to this analysis:

class Flask(Scaffold):
  	# 有几个类变量
    # 请求对象类
  	request_class = Request
    # 响应对象类
    response_class = Response
    # 配置类
    config_class = Config
    # 上篇文章分析过Rule类
    url_rule_class = Rule
    # 上篇文章分析过 Map类
    url_map_class = Map
    
    def __init__(
        self,
        import_name: str,
        static_url_path: t.Optional[str] = None,
        static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
        static_host: t.Optional[str] = None,
        host_matching: bool = False,
        subdomain_matching: bool = False,
        template_folder: t.Optional[str] = "templates",
        instance_path: t.Optional[str] = None,
        instance_relative_config: bool = False,
        root_path: t.Optional[str] = None,
    ):
    		# 有几个成员变量
        # 这里放被before_first_request装饰器装饰的函数
        self.before_first_request_funcs: t.List[BeforeFirstRequestCallable] = []
        
        # 初始化Map对象
        self.url_map = self.url_map_class()
        
    def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
        # 这里初始化了一个RequestContext对象,上一章也分析过这个类
        ctx = self.request_context(environ)
        error: t.Optional[BaseException] = None
        try:
            try:
                # 调用了push方法
                ctx.push()
                # 执行请求分配,拿到请求
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
          	# 这里一定会执行的方法
            if self.should_ignore_error(error):
                error = None
            # 在执行完请求,拿到响应对象后,这个方法是把栈给自动清空了,防止栈内存过大溢出
            ctx.auto_pop(error)
    
    def request_context(self, environ: dict) -> RequestContext:
        return RequestContext(self, environ)
      
    
    def full_dispatch_request(self) -> Response:
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)
    
    def dispatch_request(self) -> ResponseReturnValue:
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule
        if (
            getattr(rule, "provide_automatic_options", False)
            and req.method == "OPTIONS"
        ):
            return self.make_default_options_response()
        return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
     

See that the code first gets an RequestContextobject and then executes its pushmethods. Let's analyze it in detail below.

(2)ctx.push()

Let's take a closer look at what this method does that is related to the context:

class RequestContext:
  	def __init__(
        self,
        app: "Flask",
        environ: dict,
        request: t.Optional["Request"] = None,
        session: t.Optional["SessionMixin"] = None,
    ) -> None:
      	# Flask应用对象
      	self.app = app
        # 这里是初始化request对象,调用了app.request_class方法,由上面的代码Flask源码看到就是实例化了Request对象
        if request is None:
            request = app.request_class(environ)
        self.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
        # 初始化session对象
        self.session = session
    
    def push(self) -> None:
        # _request_ctx_stack局势我们的全局上下文变量(LocalStack对象)
        # 先从栈顶拿一个元素
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        # 在把请求上下文压栈之前,必须保证有应用上下文,没有的话会初始化一个并执行其push方法
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        # 执行请求上下文的push方法,传的参数是RequestContext对象自己,把自己压入到_request_ctx_stack的栈中了
        _request_ctx_stack.push(self)

				# 匹配请求,找到rule对象和参数对象并放到请求上下文的request属性中
        if self.url_adapter is not None:
            self.match_request()

One of the steps is _request_ctx_stack.push(self): execute the push method of the request context, the parameter passed is the RequestContext object itself, and push itself into the stack of _request_ctx_stack.

(3)app.full_dispatch_request

This method is ctx.push()executed later, and its code is as follows:

def full_dispatch_request(self) -> Response:
    self.try_trigger_before_first_request_functions()
    try:
        request_started.send(self)
        rv = self.preprocess_request()
        if rv is None:
           # 执行dispatch_request方法
            rv = self.dispatch_request()
    except Exception as e:
        rv = self.handle_user_exception(e)
    return self.finalize_request(rv)

The most important thing is to execute self.dispatch_request()the method to get the response. Its code is as follows.

(4)app.dispatch_request

def dispatch_request(self) -> ResponseReturnValue:
    # 从请求下文中拿到Request对象
    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    # 从请求下文中拿到Rule对象,这也是在ctx.push时放进请求下文中的
    rule = req.url_rule
    if (
        getattr(rule, "provide_automatic_options", False)
        and req.method == "OPTIONS"
    ):
        return self.make_default_options_response()
    # 找到匹配的路由函数并执行,返回结果
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)

This method mainly does the following things:

  1. Get the Request object from the request context;
  2. Get the Rule object from the request context (this is also put into the request context when ctx.push);
  3. Find the matching routing function and execute it, and return the result.

So the request context also plays a very important role in our route matching.

(5) Use request context

The following example uses the request context in a view function:

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def hello_world():
    # 当导入我们的request对象,就可以拿到当前请求线程的变量了
    print(request)
    return 'Hello World!'


if __name__ == '__main__':
    app.run('127.0.0.1', 9000)

When we import our requestobject in the view function, we can get the variables of the current request thread. Note that requestFlask globalsdefines global variables in the file to proxy request objects, as mentioned above.

request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) 

(6)ctx.auto_pop(error)

At wsgithe end of the method, when the request is processed and the response is obtained, this method is called to delete the contents of the stack to prevent overflow of stack data.

application context

(1) Source code analysis

In addition to the request context mentioned above, there is also an application context application context.

The application context is mainly used to determine the application where the request is located and to store variables in the application.

globalsDefine two objects _app_ctx_stackand their proxy objects in the Flask global variable file current_app. When there is still gan object, it will be discussed in the next section.

Similar to the request context, the application context also uses Localand LocalProxyto complete the push and pop of data.

It's worth noting that the application context is request contexta proxy to the app in the context. Its role is mainly to help the request get the current application. In Flask, the application-related information is taken out separately to form an "application context" object. This object can be used together with the "request context", or it can be used separately.

I still remember that when I talked about the request up and down above, I said that before pushing the request context onto the stack, there must be an application context. If not, one will be initialized and its pushmethod will be executed. code show as below:

class RequestContext:
  	def push(self) -> None:
      	# 省略前面的...
        # 这里在push之前保证有应用上下文,没有的话会初始化一个并执行其`push`方法
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            # 初始化一个app_ctx
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)
       # 执行请求上下文栈的push方法
        _request_ctx_stack.push(self)

The self.app.app_context()method is used to return an application context object, the code is as follows:

def app_context(self) -> AppContext:
    return AppContext(self)

AppContextThe source code is as follows:

class AppContext:
    """The application context binds an application object implicitly
    to the current thread or greenlet, similar to how the
    :class:`RequestContext` binds request information.  The application
    context is also implicitly created if a request context is created
    but the application is not on top of the individual application
    context.
    """

    def __init__(self, app: "Flask") -> None:
        self.app = app
        self.url_adapter = app.create_url_adapter(None)
        self.g = app.app_ctx_globals_class()

        # Like request context, app contexts can be pushed multiple times
        # but there a basic "refcount" is enough to track them.
        self._refcnt = 0

    def push(self) -> None:
        """Binds the app context to the current context."""
        self._refcnt += 1
        _app_ctx_stack.push(self)
        appcontext_pushed.send(self.app)

    def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None:  # type: ignore
        """Pops the app context."""
        try:
            self._refcnt -= 1
            if self._refcnt <= 0:
                if exc is _sentinel:
                    exc = sys.exc_info()[1]
                self.app.do_teardown_appcontext(exc)
        finally:
            rv = _app_ctx_stack.pop()
        assert rv is self, f"Popped wrong app context.  ({
      
      rv!r} instead of {
      
      self!r})"
        appcontext_popped.send(self.app)

    def __enter__(self) -> "AppContext":
        self.push()
        return self

    def __exit__(
        self, exc_type: type, exc_value: BaseException, tb: TracebackType
    ) -> None:
        self.pop(exc_value)

It can be seen from the above code: "application context" is also a context object, and it also implements methods such as push, popand so on.

self.gis a global object that stores values. Then the application context is also stored LocalStack().

The constructor of the application context is also similar to the request context, and has attributes such as app, url_adapterand so on.

(2) Application context usage

Which application the current context is in can also be obtained by calling _request_ctx_stack.top.appor .current_app

You can also use withstatements to construct a context, automatically execute pushthe method after with, and automatically execute popthe method after the end:

from flask import Flask
from flask.globals import current_app


app = Flask(__name__)

print(current_app)
print(current_app.name)


if __name__ == '__main__':
    app.run(debug=True)

Start the program and find that the program can start successfully, but when executing the code in the with statement, an error will be reported:

RuntimeError: Working outside of application context.

This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context().  See the
documentation for more information.

This is because the application context in the current main thread has not been pushed onto the stack. Then why can we use it directly in the view function? as follows:

from flask import Flask
from flask.globals import current_app


app = Flask(__name__)

@app.route('/')
def hello_world():
    print(current_app)
    return 'Hello World!'


if __name__ == '__main__':
    app.run(debug=True)

This is because of what we analyzed above: before requesting the context, pushit will check whether there is an application context, and if it does not, it will automatically initialize the application context and call pushthe method for us.

Then we have no request environment, such as offline files, there are two ways to solve the above problems:

(1) Manually call the push method

from flask import Flask
from flask.globals import current_app


app = Flask(__name__)
app.app_context().push()

print(current_app)  # <Flask 'test'>:当前的app对象
print(current_app.name)  # test:app的名字


if __name__ == '__main__':
    app.run(debug=True)

(2) Use the with statement

After with, the automatic execution pushmethod helps us push the stack, and the automatic execution method after the end pop:

from flask import Flask
from flask.globals import current_app


app = Flask(__name__)

with app.app_context():
    print(current_app)  # <Flask 'test'>
    print(current_app.name)  # test


if __name__ == '__main__':
    app.run(debug=True)

(3) Why do you need an application context

Another question is, since there is already a request context, why do we need an application context?

This is because Flask supports multi-application, that is, multiple apps. To achieve app isolation, Flask uses the LocalStack data structure to record the context of each app.

When in the request context of an application, it is necessary to nest and process related operations of another application, how can the request find the "correct" application?

as follows:

First build the first app:

from flask import Flask
from flask.globals import current_app


app = Flask(__name__)

app.mysql_conn = "mysql1"


if __name__ == '__main__':
    app.run(debug=True)

Start the application.

Then build the second app:

from flask import Flask
from flask.globals import current_app
from app_ctx_test1 import app


app2 = Flask("app2")

app2.mysql_conn = "mysql2"


@app2.route('/')
def hello_world():
    print(current_app)
    print(current_app.mysql_conn)
    with app.app_context():
        print(current_app)
        print(current_app.mysql_conn)
    return 'Hello World!'


if __name__ == '__main__':
    app2.run(debug=True, port=8000)

After startup, the request comes in and prints as follows:

<Flask 'app2'>  # 第二个应用
mysql2  # 第二个应用的mysql信息
<Flask 'app_ctx_test1'>  #  # 第一个应用
mysql1  # 第一个应用的mysql信息

It can be seen that the information of the first app can be obtained through the second app current_app, which is the information isolation of the app.

So it is necessary to use the application context.

g object

gObjects are also a type of application context. As you can see from the source code of the above application context, this is an Flask.app_ctx_globals_classinstance of default ctx._AppCtxGlobals.

def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)
  
g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g")) 

It can be seen that gthe object is used to represent _app_ctx_stackthe g attribute of the application context app in the proxy.

gObjects are generally used to store resources during a request and act as an intermediary, through which some data is passed between multiple functions called by a request. This variable is reset on every request.

from flask import Flask, g
 
app = Flask(__name__)
 
 
def db_query():
    user_id = g.user_id
    user_name = g.user_name
    print('user_id={} user_name={}'.format(user_id, user_name))
 
 
@app.route('/')
def get_user_profile():
    g.user_id = 123
    g.user_name = 'itcast'
    db_query()
    return 'hello world'
 
 
app.run()

reference:

https://cizixs.com/2017/01/13/flask-insight-context/

https://www.cnblogs.com/hq82/p/10451483.html

https://zhuanlan.zhihu.com/p/26097310

https://www.jianshu.com/p/7a7efbb7205f

Guess you like

Origin blog.csdn.net/qq_43745578/article/details/129574039