flask多app和棧的應用


一、簡介

    flask的藍圖可以實現url的分發,當有多個app時也可以利用app進行url分發,這里介紹下使用方式和內部原理以及棧的應用。

二、多app使用

使用示例

from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
from flask import Flask

app01 = Flask('app01')
app02 = Flask('app02')

@app01.route('/index')
def index():
    return "app01"


@app02.route('/index')
def index2():
    return "app02"


app = DispatcherMiddleware(app01, {
    '/app01': app01,
    '/app02': app02,
})
#默認使用app01的路由,也就是訪問 http://127.0.0.1:5000/index 返回app01
#當以app01開頭時候使用app01的路由,也就是http://127.0.0.1:5000/app01/index 返回app01
#當以app02開頭時候使用app02的路由,也就是http://127.0.0.1:5000/app02/index 返回app02

if __name__ == "__main__":
    run_simple('127.0.0.1', 5000, app)

實現原理

多app使用借助於DispatcherMiddleware,讓我們看看其源碼類的定義:

class DispatcherMiddleware(object):

    """Allows one to mount middlewares or applications in a WSGI application.
    This is useful if you want to combine multiple WSGI applications::

        app = DispatcherMiddleware(app, {
            '/app2':        app2,
            '/app3':        app3
        })
    """

    def __init__(self, app, mounts=None):
        self.app = app
        self.mounts = mounts or {}

    def __call__(self, environ, start_response):
        script = environ.get('PATH_INFO', '')
        path_info = ''
        while '/' in script:
            if script in self.mounts:
                app = self.mounts[script]
                break
            script, last_item = script.rsplit('/', 1)
            path_info = '/%s%s' % (last_item, path_info)
        else:
            app = self.mounts.get(script, self.app)
        original_script_name = environ.get('SCRIPT_NAME', '')
        environ['SCRIPT_NAME'] = original_script_name + script
        environ['PATH_INFO'] = path_info
        return app(environ, start_response)

從源碼中可以看到,該類實例化接受兩個參數,一個是app,第二個是mounts,此時我們運行app的時候使用的是run_simple('127.0.0.1', 5000, app),其中app就是DispatcherMiddleware對象,在flask上下文中有提及到run_simple會執行第三個參數的__call__方法,也就是以上DispatcherMiddleware的__call__方法,以我們的示例為列子,self.app=app01,self.mounts={‘/app01’:app01,’/app02’:app02},script是請求路徑例如我們請求 http://127.0.0.1:5000/index,script就是/index,接着看以下代碼: 
def __call__(self, environ, start_response):
        script = environ.get('PATH_INFO', '')
        path_info = ''
        while '/' in script:
            if script in self.mounts:
                app = self.mounts[script]
                break
            script, last_item = script.rsplit('/', 1)
            path_info = '/%s%s' % (last_item, path_info)
        else:
            app = self.mounts.get(script, self.app)
        original_script_name = environ.get('SCRIPT_NAME', '')
        environ['SCRIPT_NAME'] = original_script_name + script
        environ['PATH_INFO'] = path_info
        return app(environ, start_response)

當script = /index是,while條件成立,同時對判斷 /index是否在self.mounts,顯然此時不在,分割/index,修改script為空,此時while不成立,執行app = self.mounts.get(script, self.app),返回self.app也就是app01,接着運行app01(),這也就是和在上下文中流程一樣了。究其本質,就是通過url匹配出是哪個app,在運行該app的__call__方法。

三、棧是使用

   flask中的請求數據存放實際利用列表構造的棧來存儲的,每次pop都從最后pop出棧。當我們在進行測試或者寫flask離線腳本時候可能會使用到上下文嵌套,例如:

from flask import Flask, current_app, _app_ctx_stack

app1 = Flask('app01')
app1.debug = False


app2 = Flask('app02')
app2.debug = True



with app1.app_context():

    print(_app_ctx_stack._local.__storage__)
    #{<greenlet.greenlet object at 0x10dc79e88>: {'stack': [<flask.ctx.AppContext object at 0x10dda7b00>]}}
    print(current_app.config['DEBUG']) # False

    
    with app2.app_context():
        print(_app_ctx_stack._local.__storage__) 
        #{<greenlet.greenlet object at 0x10dc79e88>: {'stack': [<flask.ctx.AppContext object at 0x10dda7b00>, <flask.ctx.AppContext object at 0x10dda7c18>]}}
        print(current_app.config['DEBUG']) # True

    print(current_app.config['DEBUG'])  # False

在以上示例中在app01的上下文中嵌套了app02的上下文,所以在棧中會有兩個app_ctx,但是在各自取上下文的時候都不會沖突,因為app02的上下文在最后,也就是第二個with中top是app02的app_ctx。

四、關於with 

  with語法在python中非常多見,比如文件操作中打開文件,實時上with常常用於做一些先操作后清理的工作,比如文件操作最后需要關閉文件,數據庫操作先進行拿數據庫連接進行查詢,最后關閉連接等等。
如何工作:
  被with作用的對象必須有一個__enter__()方法和一個__exit__()方法,緊跟with后面的語句被調用,並返回對象的__enter__()方法被調用,這個方法的返回值將被賦值給as后面的變量。當with后面的代碼塊全部被執行完之后,將調用前面返回對象的__exit__()方法。
以下是一個with語句的示例:
class Foo(object):
    def __init__(self,name):
        self.name=name
    def __enter__(self):
        print('run __enter__')
        return self.name

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('run __exit__')


with Foo('wd') as myname:
    print("vars:",myname)

結果:
run __enter__
vars: wd
run __exit__

可能你會注意在到,在以上的demo中__exit__方法中多了三個參數,即exc_type、exc_val、exc_tb這是對應着在with語句中代碼出現異常時也會執行__exit__並接受異常,分別對應着:異常類型、異常值、以及異常的traceback。示例:

class Foo(object):
    def __init__(self,name):
        self.name=name
    def __enter__(self):
        print('run __enter__')
        return self.name

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('run __exit__')
        print('exc_type:',exc_type)
        print('exc_val:',exc_val)
        print('exc_tb:',exc_tb)


with Foo('wd') as myname:
    print("vars:", myname)
    a=[]
    v=a[1]
    print(v)

結果:
run __enter__
vars: wd
run __exit__
exc_type: <class 'IndexError'>
exc_val: list index out of range
exc_tb: <traceback object at 0x10ec98508>
Traceback (most recent call last):
  File "dbapi.py", line 21, in <module>
    v=a[1]
IndexError: list index out of range

最后我們回過頭來看看app_context()對象中的__enter__方法和__exit__方法:

def __enter__(self):
    self.push()
    return self

def __exit__(self, exc_type, exc_value, tb):
    self.pop(exc_value)

    if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
        reraise(exc_type, exc_value, tb)

實際上非常簡單,執行__enter__時候調用push壓棧,執行__exit__時pop出棧,這樣就使得每個with里面都是當前app的上下文,而不會沖突。

  

 

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM