Context request process for Flask source code analysis (request context & application context)

from flask import Flask

app = Flask(__name__)


@app.route("/")
def hello():
    return "Hello World"


if __name__ == "__main__":
    app.run()

When the program is running, executing app.run() will first execute the Flask.__call__ method

    def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)

Call the Flask.wsgi_app method in the call method

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ) # Get the information in environ, encapsulated in request and session
        ctx.push() # Put it in the __storage__ dictionary in Local, the dictionary format is {"stack":[ctx, ]} Here, the stack is stored in the form of a list==stack. For details, see the article "Flask More Detailed explanation of the "stack" of app applications"
        error = None
        try:
            try:
                response = self.full_dispatch_request()  # 请求
            except Exception as e:
                error = e
                response = self.handle_exception(e)  # 报错
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)  # response
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error) # After the request ends, delete the information in __storage__

deep ctx = request_context(environ)

    def request_context(self, environ):
        return RequestContext(self, environ) # self is the Flask object = app, environ is the request information

So here ctx is the RequestContext object!

Next, let's look at what is encapsulated in RequestContext:

Dig deeper into Request Context(self, environ)

The source code can be seen, the RequestContext encapsulates the reqeust, session, so: request = ctx.request, session = ctx.reqeust

class RequestContext(object):
    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request   #  request字段
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None  # session字段
        self._implicit_app_ctx_stack = []
        self.preserved = False
        self._preserved_exc = None
        self._after_request_functions = []
        self.match_request()

1.1 In-depth ctx.push()

    def push(self): # The push method is a method of the RuquestContext class
        
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)
 
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context() # AppContext object, application context
            app_ctx.push()  
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()

        _request_ctx_stack.push(self) # request context,
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()
Application context starts >>>>>>>>>>>>>
In-depth app_ctx = self.app.app_context(), the source code shows that app_ctx is the object of AppContext
    def app_context(self):
        return AppContext(self)

Continue to go deep into AppContext(), which encapsulates some parameters

    def __init__(self, app):
        self.app = app # app object
        self.url_adapter = app.create_url_adapter(None)
        self.g = app.app_ctx_globals_class() # g, remember that there is a g field in it, we will use it later
        self._refcnt = 0

Let's continue our process:

Dig deeper into app_ctx.push()

    def push(self): # push is a method of the AppContext() class
        self._refcnt += 1
        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()
        _app_ctx_stack.push(self) #We need in-depth methods, self is app_ctx
        appcontext_pushed.send(self.app

Dig deeper into _app_ctx_stack.push(self)

_app_ctx_stack = LocalStack()

_app_ctx_stack is: the object of LocalStack, here is the global variable, we will use it below, push is the method of LocalStack

Let's take a look at the construction method of LocalStack

    def __init__(self):
        self._local = Local() # This Local is the core technology of Flask context management. For details, please see "Thredading.local Extension of Flask Context Source Code Analysis"

Let's see what LocalStack.push does

    def push(self, obj):
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = [] # Equivalent to rv = [] self._local.stack = rv, when assigning a value to rv, it will assign a value to self._local.stack
        rv.append(obj)  # self._local.stack = [obj,]
        return rv

self._local.stack is equivalent to executing the __setattr__ method in Local

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]  # __storage__ = {"唯一标识":{"stack": [app_ctx]}}
        except KeyError:
            raise AttributeError(name)

Now we start using the above mentioned g

from flask import Flask,request,g

app = Flask(__name__)

@app.before_request
def before():
    g.permission_code_list = ['list','add'] # Set parameters


@app.route('/',methods=['GET',"POST"])
def index():
    print(g.permission_code_list) # Get parameters
    return "index"


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

The above is a small example, let's take a look at what this g is and why it can get the value like this

g = LocalProxy(partial(_lookup_app_object, 'g'))

g is an object of the LocalProxy class, partial is a partial function, example

from functools import partial

def foo(name):
    print(name)

partial(foo, 'i am pyton') # execute foo and pass parameters in

so: This partial function executes _lokkup_app_object, the parameter is g, we now go deep into _locakup_app_object

def _lookup_app_object(name):
    top = _app_ctx_stack.top # This _app_ctx_stack is a LocalStack object again,
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)  #  getattr(app_ctx, "g")

deep top = _app_ctx_stack.top

    def top(self):
        try:
            return self._local.stack[-1] # This is equivalent to executing the __getattr__ method in Local, get __storage__["unique id"]["stack"] = [app_ctx, ],[-1] the last value of the list
        except (AttributeError, IndexError):
            return None
    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]  #__storage__["唯一id"]["stack"] = [app_ctx, ]
        except KeyError:
            raise AttributeError(name)

Through the source code analysis, top is the app_ctx object, app_ctx is the AppContext object, and the parameters encapsulated in it are explained above.

return getattr(top, name)  #  getattr(app_ctx, "g")
So the global variable g actually returns app_ctx.g, so we execute, g.permission_code_list = [xxx] is equivalent to executing the __setattr__ method in LocalProxy, and the __getattr__ method in LocalProxy is executed to obtain the value
End of application context >>>>>>>

=======================Gorgeous dividing line========================


request context start >>>>>>>

Then 1.1 go deep into ctx.push() to start

deep into _request_ctx_stack.push( self )

_request_ctx_stack is also a LocalStack object, push is a method of the LocalStack class, self is ctx, and ctx encapsulates reqeust, session, etc.

The push method has been explained above. The final operation is to put ctx in the dictionary __staorage__ in Local, for example:

__staorage__ = {
	"Uniquely identifies":{
		”stask“:[ctx]
	}
}

request, session application example

from flask import g, Flask, request, session


app = Flask(__name__)


@app.route("/")
def hello():
    print(request.method)
    return "Hello Word"

request source code


request, session are the same as g, they are also LocalProxy objects, and the partial function is also to obtain the request and session in the ctx object.

The assignment executes __setattr__ of LocalProxy, and the value executes __getattr__

End of request context >>>>>>>>>>>>>>>>

End of request context flow

ctx.auto_pop(error)

Going deep into auto_pop, auto_pop finally calls its own pop method

    def auto_pop(self, exc):
        if self.request.environ.get('flask._preserve_context') or \
           (exc is not None and self.app.preserve_context_on_exception):
            self.preserved = True
            self._preserved_exc = exc
        else:
            self.pop(exc)  # exec
    def pop(self, exc=_sentinel):
       
        app_ctx = self._implicit_app_ctx_stack.pop()

        try:
            clear_request = False
            if not self._implicit_app_ctx_stack:
                self.preserved = False
                self._preserved_exc = None
                if exc is _sentinel:
                    exc = sys.exc_info()[1]
                self.app.do_teardown_request(exc)

                
                if hasattr(sys, 'exc_clear'):
                    sys.exc_clear()

                request_close = getattr(self.request, 'close', None)
                if request_close is not None:
                    request_close()
                clear_request = True
        finally:
            rv = _request_ctx_stack.pop() # Clear the application context {"unique id":{"task":[ctx]}}, because it is a dictionary type, so pop

            # 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:
                rv.request.environ['werkzeug.request'] = None

            # Get rid of the app as well if necessary.
            if app_ctx is not None:
                app_ctx.pop(exc) # Clear the application context {"unique id":{"task":[app_ctx]}}, because it is a dictionary type, so pop

            assert rv is self, 'Popped wrong request context.  ' \
                '(%r instead of %r)' % (rv, self)

_request_ctx_stack is the LocalStack object, the LocalStack method pop

def pop(self):
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1: # There is only one request at a time, so in general there is only one
            release_local(self._local)  # 深入release_local
            return stack[-1] # return the last one
        else:
            return stack.pop() # Clear all, next time stack becomes None

release_local(self._local) finally executes the method in the Local class

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None) # Clear the information related to the current request

over~



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324734900&siteId=291194637