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


一 路由系統

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

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

2. 然后通過app實例的route方法裝飾視圖函數,實現路由的配置:

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

3. 所有這里需要關注在Flask類里定義的route方法,以理解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應該是一個空字典。

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

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

在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方法:

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就被設置成立被裝飾視圖函數的函數名

由此可見,如果用戶希望endpoint不是被裝飾視圖函數時,需要在@app.route()里以endpoint關鍵子傳參給定一個函數對象名

處理完之后,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實例綁定。

  

  

 


免責聲明!

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



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