美文网首页
Request/App Context.(草稿版)(deprec

Request/App Context.(草稿版)(deprec

作者: 黄智勇atTAFinder | 来源:发表于2016-08-24 17:10 被阅读0次
     def wsgi_app(self, environ, start_response):
            ctx = self.request_context(environ)
            ctx.push()
            error = None
            try:
                try:
                    response = self.full_dispatch_request()
                except Exception as e:
                    error = e
                    response = self.make_response(self.handle_exception(e))
                return response(environ, start_response)
            finally:
                if self.should_ignore_error(error):
                    error = None
                ctx.auto_pop(error)
    
    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
            self.url_adapter = app.create_url_adapter(self.request)
            self.flashes = None
            self.session = None
        
            # 暂时先忽略
            self._implicit_app_ctx_stack = []
            self.preserved = False
            self._preserved_exc = None
            self._after_request_functions = []
    
            self.match_request()
            
        #其它函数
    
        # with语句协议
        def __enter__(self):
            self.push()
            return self
        def __exit__(self, exc_type, exc_value, tb):
            # do not pop the request stack if we are in debug mode and an
            # exception happened.  This will allow the debugger to still
            # access the request object in the interactive shell.  Furthermore
            # the context can be force kept alive for the test client.
            # See flask.testing for how this works.
            self.auto_pop(exc_value)
    
            if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
                reraise(exc_type, exc_value, tb)
    

    init函数我们可以看到有这些东西将被保存到RequestContext中:

    • app
    • request
    • url_adapter
    • flashes
    • session

    其中request = app.request_class(environ)中的request_class是Flask类的一个class variable. 默认情况下,request_class = Request, 在wrappers.py 我们查看Request类:

    class Request(RequestBase):
    
       url_rule = None
       view_args = None
       routing_exception = None
       _is_old_module = False
    
       @property
       def max_content_length(self):
          ...
       @property
       def endpoint(self):
          ...
       @property
       def module(self):
          ...
       @property
       def blueprint(self):
          ...
       @property
       def json(self):
          ...
       @property
       def is_json(self):
          ...
       def get_json(self, force=False, silent=False, cache=True):
          ...
       def on_json_loading_failed(self, e):
          ...
    

    Request继承自werkzeug的Request类(示例),通过传入environ进行初始化,获得url_rule, view_args, endpoint等信息(示例)

    init函数最后会调用match_request():

        def match_request(self):
            """Can be overridden by a subclass to hook into the matching
            of the request.
            """
            try:
                url_rule, self.request.view_args = \
                    self.url_adapter.match(return_rule=True)
                self.request.url_rule = url_rule
            except HTTPException as e:
                self.request.routing_exception = e
    

    match_request函数配置了路由信息,可参考werkzeug路由示例
    下面我们做实验看看request里面到底有些啥,
    至此,ctx = self.request_context(environ) 执行完毕。ctx为一个RequestContext对象。
    接下来ctx.psuh():

        def push(self):
            """Binds the request context to the current context."""
    
            # 这3行代码跟调试异常有关,暂时忽略。
            top = _request_ctx_stack.top
            if top is not None and top.preserved:
                top.pop(top._preserved_exc)
    
            # 在push Request Context之前先保证push App Context。
            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)
    
            if hasattr(sys, 'exc_clear'):
                sys.exc_clear()
    
            _request_ctx_stack.push(self)
    
            # Open the session at the moment that the request context is
            # available. This allows a custom open_session method to use the
            # request context (e.g. code that access database information
            # stored on `g` instead of the appcontext).
            self.session = self.app.open_session(self.request)
            if self.session is None:
                self.session = self.app.make_null_session()
    

    首先获取app_ctx, app_ctx = self.app.app_context():

        def app_context(self):
            return AppContext(self)
    
    class AppContext(object):
        
        def __init__(self, app):
            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):
            """Binds the app context to the current context."""
            self._refcnt += 1
            if hasattr(sys, 'exc_clear'):
                sys.exc_clear()
            _app_ctx_stack.push(self)
            appcontext_pushed.send(self.app)
    

    与Request Context相比 AppContext含有的内容只有:

    • app
    • url_adapter
    • g

    相比RequestContext中的url_adapter,AppContext中提供给app.create_url_adapter的参数是None,那么AppContext只需要server name信息就足够了。

        def create_url_adapter(self, request):
            if request is not None:
                return self.url_map.bind_to_environ(request.environ,
                    server_name=self.config['SERVER_NAME'])
            # We need at the very least the server name to be set for this
            # to work.
            if self.config['SERVER_NAME'] is not None:
                return self.url_map.bind(
                    self.config['SERVER_NAME'],
                    script_name=self.config['APPLICATION_ROOT'] or '/',
                    url_scheme=self.config['PREFERRED_URL_SCHEME'])
    

    对于g变量,app_ctx_globals_class = _AppCtxGlobals,即g变量是_AppCtxGlobals的一个实例:

    class _AppCtxGlobals(object):
        """A plain object."""
    
        def get(self, name, default=None):
            return self.__dict__.get(name, default)
    
        def pop(self, name, default=_sentinel):
            if default is _sentinel:
                return self.__dict__.pop(name)
            else:
                return self.__dict__.pop(name, default)
    
        def setdefault(self, name, default=None):
            return self.__dict__.setdefault(name, default)
    
        def __contains__(self, item):
            return item in self.__dict__
    
        def __iter__(self):
            return iter(self.__dict__)
    
        def __repr__(self):
            top = _app_ctx_stack.top
            if top is not None:
                return '<flask.g of %r>' % top.app.name
            return object.__repr__(self)
    

    换句话说,g就是一个普通的对象,具体用法可参考flask.g.
    app_ctx = self.app.app_context()执行完毕,执行app_ctx.push(),app context可以推送多次,但是一个基本的refcount足以来跟踪它们。_app_ctx_stack.push(self), 在globals.py中,我们看到:

    # context locals
    _request_ctx_stack = LocalStack()
    _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'))
    

    无论是_request_ctx_stack,还是_app_ctx_stack都是werkzeug中的LocalStack 实例,而current_app, request, session, g都是LocalProxy实例,关于LocalStack和LocalProxy的用法可参见另一篇文章。与AppContext相关的是_app_ctx_stack, current_app, g.

    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
    

    由于current_app是个代理对象,当我们对current_app进行操作时,实际上是对_find_app函数中返回的top.app进行操作的,即 _app_ctx_stack.top.app, 也即是AppContext().app(一个Flask类实例)进行操作。同样 ,对g进行操作,会转交给getattr(_app_ctx_stack.top, 'g')也即是_AppCtxGlobals类的实例。至此AppContext push完毕。
    回到push RequestContext过程中,_request_ctx_stack.push(self),过程同上,request, session是与RequestContext相关的变量。
    因此,当我们写from flask import current_app, request, session, g时,这些变量看似是全局变量,实际上则是LocalProxy类实例。最后push()函数中还有几行与session有关的代码,暂时忽略。

    相关文章

      网友评论

          本文标题:Request/App Context.(草稿版)(deprec

          本文链接:https://www.haomeiwen.com/subject/lemnsttx.html