2018年2月24日 星期六

Python Flask endpoint

Python Flask endpoint

tags: python endpoint

說明

在寫登入後帳號是否啟動判斷時發現,『flask web開發』的作者在裡面用了request.endpoint條件式,讓原預計僅使用登入狀態與啟動狀態來判斷的我好奇了起來,到底在flask內endpoint是什麼!
首先,我看了一下app.route這個裝飾器。
A decorator that is used to register a view function for a  
given URL rule.  This does the same thing as :meth:\`add\_url\_rule\`  
but is intended for decorator usage::
在route這個function的說明提到,使用裝飾器的效果跟使用add_url_rule一樣,這讓我想起了一開始在學flask的時候有看到前輩寫到,flask也可以跟django一樣,將所有的route寫在一個文件中,靠的就是add_url_rule。
@app.route('/fine/<name>')
def fine(name):
    return 'My name is %s.' % name
舉例範例來說,上面這個標準寫法,也可以改寫成下面這樣
def fine(name): 
    return 'My name is %s.' % name
    
app.add_url_rule('/fine/<name>', 'fine', fine)
為什麼?
我們繼續追下去看route的內容就不難發現原因了,在route內的
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
第3行:如果你沒有給endpoint的話,就賦值None
第4行:調用add_url_rule這個method,那我們就看一下這個method的參數。
def add_url_rule(self, rule, endpoint=None, view_func=None, **options): """ Connects a URL rule. Works exactly like the :meth:`route` decorator. If a view_func is provided it will be registered with the endpoint. :param rule: the URL rule as string :param endpoint: the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint :param view_func: the function to call when serving a request to the provided endpoint"""
rule:指路由名稱,就如同@app.route(’/abcd’)內的這個/abcd
endpoint:指endpoint,flask將view function的名稱視為endpoint
view_func:指view function,就是@app.route下的function,上例來看就是fine。
關於endpoint的部份,參數說明上提到了,沒有特別設置情況下,名稱同view function。如下可發現一二:
if endpoint is None: endpoint = _endpoint_from_view_func(view_func) options['endpoint'] = endpoint
目前為止,還是無法知道endpoint是幹嘛的,追到最後面會找到到兩句重點。
rule = self.url_rule_class(rule, methods=methods, **options) rule.provide_automatic_options = provide_automatic_options self.url_map.add(rule)
路由的部份,似乎做了一點動作,一樣的,我們追蹤url_rule_class看看。
`endpoint`
    The endpoint for this rule. 
    This can be anything. A reference to a
    function, a string, a number etc.  
    The preferred way is using a string
    because the endpoint is used for URL generation.
在class上對於endpoint的說明是,The endpoint for this rule。
這令人振奮,因為我們看到了一個關聯,接著rule被add進去了url_map,我們看url_map.add做了什麼。
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
從上面的說明可以看的出來,透過url_map.add,讓rule(url)與endpoint有了關聯。
第10行:_rules_by_enpoint是一個字典檔,透過字典檔記錄了rule(url)跟endpoint的關聯。
最後,我們繼續看就可以發現,做完了rule(url)與endpoint的關聯之後,flask開始處理view_function與endpoint的關聯。
if view_func is not None: old_func = self.view_functions.get(endpoint) if old_func is not None and old_func != view_func: raise AssertionError('View function mapping is overwriting an ' 'existing endpoint function: %s' % endpoint) self.view_functions[endpoint] = view_func
第6行:似乎是個字典檔,觀其說明如下:
#: A dictionary of all view functions registered.  The keys will
#: be function names which are also used to generate URLs and
#: the values are the function objects themselves.
#: To register a view function, use the :meth:`route` decorator.
self.view_functions = {}
這個字典檔,就是用來記錄endpoint與view_function的對應。
其實,如果去查詢url_for的話會發現,它的第一個參數放的是endpoint,那也可以明白為什麼可以利用url_for(‘view_function’)來回到route,因為其實是url_for(‘endpoint’)。
直觀的理解就是,flask透過了endpoint來記錄了跟rule(url)與view_function的關聯,所以有endpoint,就有辦法找到相關的rule與view_function。
因此,透過了url_for有辦法直接的尋找到相對應的route與view_function,以及利用route能夠找到相對應的view_function。

範例

知道了道理,就可以開始寫一個測試程式確認我們心中的想法。
首先,我們設置一個route
@app.route('/urlroot', endpoint="root") def func_root(): # print(url_for('func_root')) 先註解掉這行 print(url_for('root')) print("View function: {view}. Endpoint: {endpoint}".format(view="func_root", endpoint=flask.request.endpoint))
第1行:指定endpoint為root
第4行:利用url_for來看root會定位到那
第5行:列印request.endpoint確認
得到的結果如下:
/urlroot
View function: func_root. Endpoint: root
可以確認到,利用endpoint我們確實的找到了rule,來源request.endpoint為root。
接著註解掉print(url_for(‘root’)),改換用view_function做參數測試,這時候得到的回應是一個錯誤,利用view_function是沒有辦法找到相對應的rule。

在查詢資料的期間也學習到另一種方式
# 新增一個rule,但不指定view_function app.add_url_rule(rule='/rule', endpoint="enpo", methods=["GET", "DELETE"]) # 手動讓endpoint跟view_function關聯,就跟我們操作dict一樣,加入而以。 app.view_functions['enpo'] = func_root
第1行:手動加入路由,但不指定view_function
第4行:手動關聯endpoint與view_function
得到的結果如下
View function: func_root. Endpoint: enpo
一個路由,可以讓多個endpoint去關聯。
以上學習記錄。

學習參考

stackoverflow
stackoverflow翻譯_感謝對岸同好
對岸討論區_segmentfault
這篇的案例可以讓我們更直觀的理解到endpoint的用途。

沒有留言:

張貼留言