Flask 中的藍圖管理
在Flask中模組化應用的實現一文中,我們曾分析過Flask 0.2版本中的Module類。這個類能夠實現Flask應用的多模組化管理。在0.7版本中,Flask重新設計了模組化管理的內容,提出了“藍圖”的概念,用來取代Module的功能。
什麼是“藍圖”
官方文件中對“藍圖”的概念是這樣描述的:
Flask uses a concept of blueprints for making application components and supporting common patterns within an application or across applications. Blueprints can greatly simplify how large applications work and provide a central means for Flask extensions to register operations on applications. A Blueprint object works similarly to a Flask application object, but it is not actually an application. Rather it is a blueprint of how to construct or extend an application.
按照以上的描述,可以看出“藍圖”系統在Flask應用的元件化和擴充套件提供了很大的便利。“元件化”是指可以在Flask層上將一個Flask應用進行“分割”,實現模組化管理,這極大地簡化了構建大型應用的流程,也使得應用的維護變得更加容易。另外,“藍圖”還提供了一種Flask擴充套件在應用上註冊操作的核心方法。
“藍圖”和一個Flask應用物件很相似,但是並不是一個Flask應用物件。它是可以註冊到Flask應用上的一系列操作(對於此的理解,後文會詳細講到)。使用“藍圖”,可以實現以下的一些功能:
- 將Flask應用“分割”為一系列“藍圖”的集合,簡化了大型應用工作的方式;
- 在Flask應用上,以 URL 字首和或子域名註冊一個藍圖。可以以不同的URL多次註冊一個藍圖;
- 通過藍圖提供模板過濾器、靜態檔案、模板和其它功能。
建立“藍圖”物件時發生了什麼
“藍圖”和Flask應用的工作方式非常相似。Flask中的“藍圖”系統能夠實現很多“藍圖”物件在應用層上的管理,這些“藍圖”物件可以共享應用配置。這意味著“藍圖”應該和Flask應用有一樣的執行邏輯,這樣一旦藍圖註冊到應用上,就可以完全像Flask應用一樣工作。
在Flask應用中有一些屬性,它們以字典的形式用來存放很多裝飾器執行的結果。例如:view_functions這個字典通常存放route裝飾器裝飾的檢視函式,可以用來進行URL匹配;before_request_functions字典會存放before_request
例如:before_request裝飾器
Python12345678 | defbefore_request(self,f):"""Like :meth:`Flask.before_request` but for a blueprint. This function is only executed before each request that is handled by a function of that blueprint. """self.record_once(lambdas:s.app.before_request_funcs.setdefault(self.name,[]).append(f))returnf |
上面的程式碼中,只要給這個裝飾器傳遞引數s,那麼這個裝飾器就會將裝飾的函式新增到Flask應用的before_request_functions字典當中,並且和該“藍圖”的名字對應起來。這樣就可以實現該函式在Flask應用中可用。
由於“藍圖”的建立過程和Flask應用的建立過程是分離的,所以在“藍圖”中使用裝飾器不會立即對應用產生效果。“藍圖”中裝飾器函式的返回值會經過record_once方法儲存在“藍圖”物件的deferred_functions列表中,這為“藍圖”物件的註冊提供了一個介面:只要在註冊“藍圖”時執行deferred_functions列表中的函式即可。
以下是一個簡單的例子:
Python1234567891011121314151617 | # blueprint>>>fromflask importBlueprint>>>blog=Blueprint('blog',__name__,static_folder='static',template_folder='templates')>>>@blog.route('/')defindex():return"This is blog home page.">>>@blog.before_requestdefbefore_request():return"This is before_request function.">>>@blog.after_requestdefafter_request():return"This is after_request function.">>>@blog.after_app_requestdefafter_app_request():return"This is after_app_request function." |
上面的例子中:
- 首先我們建立了一個“藍圖”物件blog。建立“藍圖”物件時,必須至少傳遞前兩個引數:name和import_name。也可以傳遞static_folder、template_folder、url_prefix等引數,url_prefix引數也可以在註冊藍圖時傳入。
- 之後,我們為blog增加了四條檢視函式,每個函式由藍圖的裝飾器裝飾。此時,我們看一下“藍圖”物件的deferred_functions中有什麼:
Python123456 >>>blog.deferred_functions[<function flask.blueprints.<lambda>>,<function flask.blueprints.<lambda>>,<function flask.blueprints.<lambda>>,<function flask.blueprints.<lambda>>]
可以看出,此時“藍圖”物件的deferred_functions中已經包含了四個匿名函式,分別對應上面例子中的四個檢視函式。一旦“藍圖”被註冊到應用上,會執行這四個函式。
註冊“藍圖”
Flask應用和“藍圖”中都有註冊“藍圖”的介面。
Flask應用 中的介面是:
Python123456789101112131415 | defregister_blueprint(self,blueprint,**options):"""Registers a blueprint on the application. .. versionadded:: 0.7 """first_registration=Falseifblueprint.name inself.blueprints:assertself.blueprints[blueprint.name]isblueprint,\'A blueprint\'s name collision ocurred between %r and '\'%r. Both share the same name "%s". Blueprints that '\'are created on the fly need unique names.'%\(blueprint,self.blueprints[blueprint.name],blueprint.name)else:self.blueprints[blueprint.name]=blueprintfirst_registration=Trueblueprint.register(self,options,first_registration) |
這個方法首先會對“藍圖”進行檢查,如果已經註冊,則會出現一條assert語句。否則,就會呼叫“藍圖”物件的register方法進行註冊。
藍圖物件 中的介面是:
Python123456789101112131415 | defregister(self,app,options,first_registration=False):"""Called by :meth:`Flask.register_blueprint` to register a blueprint on the application. This can be overridden to customize the register behavior. Keyword arguments from :func:`~flask.Flask.register_blueprint` are directly forwarded to this method in the `options` dictionary. """self._got_registered_once=Truestate=self.make_setup_state(app,options,first_registration)ifself.has_static_folder:state.add_url_rule(self.static_url_path+'/<path:filename>',view_func=self.send_static_file,endpoint='static')fordeferred inself.deferred_functions:deferred(state) |
藍圖物件中的register方法首先會生成一個BlueprintSetupState物件,這個物件將當前應用和當前藍圖的相關資訊進行關聯,還將作為引數傳遞到藍圖物件deferred_functions列表中的每一個函式。這樣,藍圖中的相關操作就會對映到當前應用當中。
還是以上面的例子為例:
Python123456789101112131415161718192021222324 | >>>fromflask importFlask>>>app=Flask(__name__)>>>app.url_mapMap([<Rule'/static/<filename>'(HEAD,OPTIONS,GET)->static>])>>>app.blueprints{}>>>app.before_request_funcs{}>>>app.after_request_funcs{}>>>app.register_blueprint(blog,url_prefix='/blog')>>>app.url_mapMap([<Rule'/blog/'(HEAD,OPTIONS,GET)->blog.index>,<Rule'/blog/static/<filename>'(HEAD,OPTIONS,GET)->blog.static>,<Rule'/static/<filename>'(HEAD,OPTIONS,GET)->static>])>>>app.blueprints{'blog':<flask.blueprints.Blueprint at0x896e7f0>}>>>app.before_request_funcs{'blog':[<function __main__.before_request>]}>>>app.after_request_funcs{None:[<function __main__.after_app_request>],'blog':[<function __main__.after_request>]} |
經過上面的例子,可以發現“藍圖”物件註冊到Flask應用時,會在Flask應用對應的地方增加“藍圖”物件的相關資訊。例如,blog物件中有一個before_request裝飾器,註冊成功後,在app.before_request_funcs增加了該資訊,並且以藍圖名作為鍵進行區分。blog物件中還有一個route裝飾器,它為藍圖增加了一條URL規則,最終會在Flask應用的url_map中出現。由於在建立藍圖時我們增加了static_folder引數,所以在url_map中我們還可以看到‘/blog/static/<filename>’這樣的URL規則。