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

 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) 執(zhí)行完畢跌前。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()執(zhí)行完畢铡原,執(zhí)行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有關的代碼啰挪,暫時忽略信不。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市亡呵,隨后出現的幾起案子抽活,更是在濱河造成了極大的恐慌,老刑警劉巖政己,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酌壕,死亡現場離奇詭異掏愁,居然都是意外死亡,警方通過查閱死者的電腦和手機卵牍,發(fā)現死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門果港,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人糊昙,你說我怎么就攤上這事辛掠。” “怎么了释牺?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵萝衩,是天一觀的道長。 經常有香客問我没咙,道長猩谊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任祭刚,我火速辦了婚禮牌捷,結果婚禮上,老公的妹妹穿的比我還像新娘涡驮。我一直安慰自己暗甥,他們只是感情好,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布捉捅。 她就那樣靜靜地躺著撤防,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棒口。 梳的紋絲不亂的頭發(fā)上寄月,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音陌凳,去河邊找鬼剥懒。 笑死,一個胖子當著我的面吹牛合敦,可吹牛的內容都是我干的。 我是一名探鬼主播验游,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼充岛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了耕蝉?” 一聲冷哼從身側響起崔梗,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎垒在,沒想到半個月后蒜魄,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年谈为,在試婚紗的時候發(fā)現自己被綠了旅挤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡伞鲫,死狀恐怖粘茄,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情秕脓,我是刑警寧澤柒瓣,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站吠架,受9級特大地震影響秩命,放射性物質發(fā)生泄漏。R本人自食惡果不足惜疹蛉,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一箱蟆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧怔檩,春花似錦褪秀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乙埃,卻和暖如春闸英,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背介袜。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工甫何, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人遇伞。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓辙喂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸠珠。 傳聞我的和親對象是個殘疾皇子巍耗,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內容