# 应用上下文

在请求上下文的时候,我们将所有请求数据封装在ctx中,而下面我们将介绍应用上下文,依旧针对wsgi_app这个函数进行介绍

  • # 打开 wsgi_app 函数的源码
    def wsgi_app(self, environ, start_response):
     		ctx = self.request_context(environ)  #请求上下进行封装,访问所有的封装的请求的属性 
            error = None
            try:
                try:
                    ctx.push() #将ctx添加到请求数据中,除此之外还有添加应用app,以及session
                    
                    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)
    
  • # 打开 ctx.push 函数源码
    	def push(self):	
    		top = _request_ctx_stack.top  # 通过self,在top属性中取出stack[-1],前提是有,我们就可以向取出来的ctx对象添加新属性,使用push
            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(self)对象
                app_ctx.push()  #将得到的app_ctx对象添加到全局的
                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)  #这里进行的ctx的添加,
    .
            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)
    
            if self.url_adapter is not None:
                self.match_request()
    
    
  • 打开 app_context() 函数的源码

    1、该函数放回一个AppContext类对象,最后赋值给app_ctx
    	return AppContext(self)  #shlf就是app
    
    2、打开AppContext类,执行初始化函数
     def __init__(self, app):
            self.app = app  #传入的self就是app
            self.url_adapter = app.create_url_adapter(None)
            self.g = app.app_ctx_globals_class() #去app中执行app_ctx_globals_class全局取数据,g就是一个空字典,可以通过它来传递参数
            self._refcnt = 0
            
    3、我们可以打开app_ctx_globals_class函数,里面返回了一个_AppCtxGlobals的类,其实就是一个字典的功能,
    
    • 怎么将装饰器中的数据,传递给视图函数中?

      from flask import request,Flask,session,g
      app = Flask('app01') # 不一定要传__name__
      app.secret_key ='DSDSASD'
      
      @app.before_request
      def before():
          request.a = 'xxxx'  #  方式一:直接放在request中
          session['x'] = 'sdfa'  # 方式二: 放在session中
          g.xxx= 'ok' #方式三:因为request中本来就有很多值了,可能会发生覆盖,所以可以使用g来携带参数
      
      @app.route('/',methods=['GET'])
      def index():
          print(request.a)
          print(session['x'])
          print(g.xxx)  
          return 'index'
      
      if __name__ == '__main__':
          app.run()
      
  • app_ctx.push() 打开函数源码

    1、使用app_push函数给 _app_ctx_stack 添加app对象 
      def push(self):  #self是app_ctx
            """Binds the app context to the current context."""
            self._refcnt += 1
            if hasattr(sys, "exc_clear"):
                sys.exc_clear()
            _app_ctx_stack.push(self) #给全局_app_ctx_stack添加app_ctx
            appcontext_pushed.send(self.app)
    
    2、点击_app_ctx_stack查看源码:
    	_app_ctx_stack = LocalStack()  #应用上下文,也就是说全局有两个Local
    
        _app_ctx_stack = LocalStack()
        current_app = LocalProxy(_find_app)
        request = LocalProxy(partial(_lookup_req_object, "request"))
        session = LocalProxy(partial(_lookup_req_object, "session"))
        g = LocalProxy(partial(_lookup_app_object, "g"))
        
    3、换句话说,ctx.push()执行之后会有两个local,大致的结构:
      _request_ctx_stack.__storage__ = {  #请求上下文
                    唯一标识:{
                          'stack':[ctx,]
                      }
              }   
      _app_ctx_stack.__storage__ = {  #应用上下文
                    唯一标识:{
                          'stack':[app_ctx,]
                      }
              }  
    
  • ctx.auto_pop(error) 将删除相应的ctx、app_ctx。

# 问题1:多线程是如何体现的?
当多个请求到来时,将自己的数据存储在,项目启动市创建的local数据中
# 问题2:为什么讲上下文管理?
1、通过请求进入,Flask会记录每个请求的携带数据,并以堆栈的方法保存在Local中,且有两个local类,分别保存request、app

2、关键:还记得上下文管理吗?
	2.1:从所有的app.__call__中调用了wsgi_app函数:
	    def wsgi_app(self, environ, start_response):
            ctx = self.request_context(environ) #在这里实例化了 RequestContext(self, environ) ,这里面会找到__enter__和__exit__函数,也就是说支持with app.request_context()
            error = None
            try:
                try:
                    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)
    2.2:同理,在ctx.push()中的app_ctx = self.app.app_context() 中的app_context函数返回的是一个
AppContext(self)类,里面也是实现了__enter__和__exit__函数,也就是说支持with app.app_context() 

	  def __enter__(self):
        self.push()  #也就是说在app_context()、request_context()函数调用类的时候就会执行push函数
        return self

      def __exit__(self, exc_type, exc_value, tb):
        self.pop(exc_value)
  • 小提示:

    from flask import Flask,_app_ctx_stack
    app = Flask('app01')
    
    with app.app_context():
        print(_app_ctx_stack._local.__storage__) #{<greenlet.greenlet object at 0x000001D0CC4E3B90>: {'stack': [<flask.ctx.AppContext object at 0x000001D0CB52DD30>]}}
    

# 多APP应用

  • 使用run_simple 结合 DispatcherMiddleware 类实现

    from flask import Flask
    from werkzeug.wsgi import DispatcherMiddleware
    from werkzeug.serving import run_simple
    
    app = Flask('app01')
    
    app2 = Flask('app02')  #生成两个app
    
    @app.route('/index',methods=['GET'])
    def home():
        return 'index'
    
    @app2.route('/index2',methods=['GET'])
    def index():
        return 'index2'
    
    app3 = DispatcherMiddleware(app,{
        '/sec':app2,  # 做了 分发,必须要加'/sec/index2' 才能触发app2
    })
    
    if __name__ == '__main__':
        run_simple('localhost',9000,app3)
    

# 离线脚本

  • 使用 with 实现上下文的用法

    from flask import Flask,_app_ctx_stack,current_app
    
    app = Flask('app01')
    app.debug = True
    
    with app.app_context():
        print(_app_ctx_stack._local.__storage__) #{<greenlet.greenlet object at 0x000001D0CC4E3B90>: {'stack': [<flask.ctx.AppContext object at 0x000001D0CB52DD30>]}}
        print(current_app.config['DEBUG']) #True
        print(current_app) #<Flask 'app01'>
    
    
  • 实际使用

    from flask import Flask,_app_ctx_stack,current_app
    
    app = Flask('app01')
    app.debug = True
    
    app2 = Flask('app2')
    app2.debug = True
    
    with app.app_context(): #执行了app的push
        print(_app_ctx_stack._local.__storage__) #{<greenlet.greenlet object at 0x000001D0CC4E3B90>: {'stack': [<flask.ctx.AppContext object at 0x000001D0CB52DD30>]}}
        print(current_app.config['DEBUG']) #True
        print(current_app) #<Flask 'app01'>
    
        with app2.app_context():  # 会执行app2的push
            #{<greenlet.greenlet object at 0x00000142342D3B90>: {'stack': [<flask.ctx.AppContext object at 0x00000142344704E0>, <flask.ctx.AppContext object at 0x0000014234470748>]}}
            print(_app_ctx_stack._local.__storage__)
            print(current_app)  #<Flask 'app2'>,此时stack中有两个app