Flask之基於route裝飾器的路由系統(源碼閱讀解析)

一 路由系統

1. 在flask中配置URL和視圖函數的路由時,首先須要在main.py中實例化一個app對象:python

1 from flask import Flask, render_template 2 
3 app = Flask(__name__)

2. 而後經過app實例的route方法裝飾視圖函數,實現路由的配置:express

1 @app.route('/') 2 def hello_world(): 3     return 'Hellow World!'

3. 全部這裏須要關注在Flask類裏定義的route方法,以理解Flask內部的路由配置邏輯flask

def route(self, rule, **options): def decorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator

可見app實例的route其實是一個帶參數的裝飾器,其中rule是URL規則(字符串形式),而options能夠接收其餘按關鍵字傳參的配置項,在上面Hello World的例子中,options應該是一個空字典。app

這個裝飾器的做用是把URL規則和視圖函數交由app實例的add_url_rule方法處理,並返回被裝飾函數自己,因此在main.py中視圖函數名依然原來的視圖函數對象的引用。dom

4. 下一部須要關注的是add_url_rule方法的內部實現:ide

在add_url_rule方法裏首先處理endpoint,這裏endpoint能夠理解爲和URL規則映射的視圖函數對象函數

1         if endpoint is None: 2             endpoint = _endpoint_from_view_func(view_func) 3         options['endpoint'] = endpoint

因爲在Hellow World例子裏,endpoint是None, 這裏會調用_endpoint_from_view_func方法:ui

1 def _endpoint_from_view_func(view_func):
2     assert view_func is not None, 'expected view func if endpoint ' \ 3                                   'is not provided.'
4     return view_func.__name__ # 返回被裝飾視圖函數的函數名

這裏view_func就是被裝飾的視圖函數,因此endpoint就被設置成立被裝飾視圖函數的函數名url

因而可知,若是用戶但願endpoint不是被裝飾視圖函數時,須要在@app.route()裏以endpoint關鍵子傳參給定一個函數對象名spa

處理完以後,endpoint被添加到options字典中

 

接着add_url_rule方法繼續處理methods, 這裏methods能夠理解爲這條URL和視圖的映射適用於那種Http請求方法:

1 methods = options.pop('methods', None) 2 if methods is None: 3      methods = getattr(view_func, 'methods', None) or ('GET',) 4 if isinstance(methods, string_types): 5       raise TypeError('Allowed methods have to be iterables of strings, '
6                            'for example: @app.route(..., methods=["POST"])') 7         methods = set(item.upper() for item in methods) # 最後methods是一個包含用戶傳入的Http請求方法,或默認GET請求方法的集合

首先從options字典裏取出'methods'對應的值,在Hellow World的例子中,此時methods = None

接着,把methods設置爲視圖函數的‘methods’屬性。

  p.s.:看到這裏使用了getattr函數,咱們能夠發現,app.route裝飾的視圖,並不要求是必定要定義成函數的形式,也能夠定義成一個python模塊導入到main.py中,這樣以來flask的視圖系統就具備了更加靈活的擴展性。因此methods參數既能夠做爲app.route的關鍵字參數,也經過定義視圖的模塊中methods標量來定義。

若是app.route()沒有傳入methods參數,也沒有再視圖模塊中定義methods變量,methods默認賦值爲('GET'),可見flask中路由配置默認是對應HTTP GET請求的。

Flask要求用戶傳入各個的methods方法必須是字符串形式,而且放在符合python協議的可迭代對象中,不然,會拋出異常提示,上面4 - 6行代碼都是在作這一層判斷

最後,methods變量裏的元素被取出並放入集合。

至此用戶定義的URL規則和Http請求方法處理完畢。

 

5. 若是視圖模塊中有定義了'requeire_methods'參數,也須要處理:

1 required_methods = set(getattr(view_func, 'required_methods', ()))
required_methods的做用這裏暫時先不關注,後續再介紹

 

6 接下來以前的處理的methods和required_methos進行並集處理,都添加到methods參數中

methods |= required_methods

 

7. 把處理好的URL規則和methods參數,以及options字典委託給app實例的url_rule_class方法作進一步的處理

1 rule = self.url_rule_class(rule, methods=methods, **options)

url_rule_class其實是一個叫Rule的類,這一步若是處理經過,參數rule會接收一個Rule的實例

 

8. Rule這個類的__init__方法以下:

 1 class Rule(RuleFactory):  2     def __init__(self, string, defaults=None, subdomain=None, methods=None,  3                  build_only=False, endpoint=None, strict_slashes=None,  4                  redirect_to=None, alias=False, host=None):  5         if not string.startswith('/'):  6             raise ValueError('urls must start with a leading slash')  7         self.rule = string  8         self.is_leaf = not string.endswith('/')  9 
10         self.map = None 11         self.strict_slashes = strict_slashes 12         self.subdomain = subdomain 13         self.host = host 14         self.defaults = defaults 15         self.build_only = build_only 16         self.alias = alias 17         if methods is None: 18             self.methods = None 19         else: 20             if isinstance(methods, str): 21                 raise TypeError('param `methods` should be `Iterable[str]`, not `str`') 22             self.methods = set([x.upper() for x in methods]) 23             if 'HEAD' not in self.methods and 'GET' in self.methods: 24                 self.methods.add('HEAD') 25         self.endpoint = endpoint 26         self.redirect_to = redirect_to 27 
28         if defaults: 29             self.arguments = set(map(str, defaults)) 30         else: 31             self.arguments = set() 32         self._trace = self._converters = self._regex = self._argument_weights = None

這裏再回顧一下上面給__init__方法的傳入的參數:

1  rule = self.url_rule_class(rule, methods=methods, **options)

URL規則是第一個位置參數,methods以及options字典裏的鍵值對,都被__init__方法按關鍵字接收

 

首先,若是app.route傳入的URL不是一個以'/'開頭的字符串,會拋出異常

self.is_leaf記錄URL是否沒有以‘/’結尾 

而後,若是methos裏有"GET"方法,而沒有"HEAD",會把'HEAD'添加進入,'HEAD'的做用會把後續筆記中分析。

這裏注意到,__init__裏有一個self.redirect_to = redirect_to,多是能夠直接在app.route()裏設置視圖的跳轉,這個放到後面再具體分析。

能夠發現,flask裏把路由相關的:URL,host,適用的HTTP請求方法,endpoint視圖都保存到了Rule這個類的實例中。

 

9. 獲得Rule的實例後,回到add_url_rule方法,繼續看對rule實例的處理:

1 self.url_map.add(rule)

這裏url_map是Map類的一個實例,是在app實例化的時候綁定到app實例的,下面只須要關注Map類的add方法:

class Map:
... ...無關代碼省略
def add(self, rulefactory): """Add a new rule or factory to the map and bind it. Requires that the rule is not bound to another map. :param rulefactory: a :class:`Rule` or :class:`RuleFactory` """ for rule in rulefactory.get_rules(self): rule.bind(self) self._rules.append(rule) self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule) self._remap = True

能夠看到這裏rulefactory能夠接收Rule實例或者RuleFactory實例,RuleFactory實例對應另外一種設置路由的方法。在咱們這個例子裏,rulefactory應該是一個Rule的實例

因此還須要進一步關注,Rule的實例的get_rules方法:

1     def get_rules(self, map): 2         yield self

get_rules方法接收兩個參數,Rule的實例,和Map的實例,咱們的例子裏,Map實例沒有做用,這個方法直接yield返回了Rule實例

 

下面繼續看rule實例的bind方法:

 1 Class Rule:  2     ... ... 省略無關代碼  3  1     def bind(self, map, rebind=False):  4  2         """Bind the url to a map and create a regular expression based on  5  3 the information from the rule itself and the defaults from the map.  6  4  7  5 :internal:  8  6 """
 9  7         if self.map is not None and not rebind: 10  8             raise RuntimeError('url rule %r already bound to map %r' %
11  9 (self, self.map)) 12 10         self.map = map 13 11         if self.strict_slashes is None: 14 12             self.strict_slashes = map.strict_slashes 15 13         if self.subdomain is None: 16 14             self.subdomain = map.default_subdomain 17 15         self.compile()
View Code

這個實在判斷rule實例的map屬性是否爲None,若是是None,就把map實例綁定到rule實例的map實行,不然報錯,這裏就控制了一個rule實例只能跟一個map實例進行綁定。

 

以後會把rule實例append到這個map實例的self._rules列表中

以後這個map實例的_rules_by_endpoint屬性的會添加這樣一個鍵值對:rule.endpoint: [rule]      也就是 視圖對象:[rule實例]

 

至此,整個經過app,route裝飾視圖,來綁定URL和視圖映射關係的邏輯流程已經結束,此時

app實例的self.map保存的Map類實例裏保存了一個:視圖對象 和 rule實例映射的鍵值對。

總結起來:

--- app.route()裝飾器

  獲取URL, 視圖對象,其餘opeions方法,並調用app實例的add_url_rule方法

--- add_url_rule方法:

 1. 獲取app.route的methods關鍵字參數,視圖模塊裏定義的methods參數等Http 請求方法

    這裏視圖能夠是一個函數,也能夠是一個python模塊

    2. 把URL,視圖對象,Http請求方法,綁定到一個Rule實例(app實例的),經過app實例的url_rule_class方法。

    Rule的__init__方法的其餘參數來自app.route的關鍵字傳參,能夠控制一些URL的匹配規則

    build_only參數可讓URL不綁定任何視圖,實現static文件夾等。

---- url_map.add

  把Rule實例和app實例保存的map實例綁定。

相關文章
相關標籤/搜索