基礎用法:
在Flask框架內,使用SQLAlchemy 來進行ORM數據庫查詢,示例如下:
# 從User表中查詢數據
user = User.query.filter_by(username="張三").first()
這種寫法,需要自己對結果進行判空:
# 從User表中查詢數據
user = User.query.filter_by(username="張三").first()
if user:
# do something
但是,Flask提供了更爲便捷的方法:first_or_404
user = User.query.filter_by(username="張三").first_or_404()
當查詢結果不存在時,會主動拋出異常,跳轉到Flask默認的404頁面!返回頁面如下
first_or_404內部實現
此代碼位於 site-packages \ flask_sqlalchemy \ __init__.py
from sqlalchemy import orm
class BaseQuery(orm.Query): # Flask-SQLAlchemy是 SQLAlchemy的進一步封裝
... # 以上部分代碼省略
def first_or_404(self, description=None):
rv = self.first() # Step1 執行firest() 對數據庫進行查詢
if rv is None: # Step2 判斷數據庫返回結果是否爲空
abort(404, description=description)
return rv # Step3 返回firest()查詢結果
初步探尋
當查詢拿結果爲空時,函數執行abort(404, description=description)
代碼,我們來一探究竟。
# site-packages\werkzeug\exceptions.py
def abort(status, *args, **kwargs):
"""
爲給定的狀態碼或WSIG應用報錯HTTPException 錯誤
如果給定狀態代碼,將引發該異常。如果是通過WSGI應用程序發起的請求,
將把它包裝成代理WSGI異常並引發:
abort(404) # 404 Not Found
abort(Response('Hello World'))
"""
return _aborter(status, *args, **kwargs) # 調用Aborter()類的call方法
_aborter = Aborter() # _aborter 爲 Aborter() 函數實例化對象
Aborter()類源碼:
# site-packages\werkzeug\exceptions.py
class Aborter(object):
"""
當傳遞一個字典或code,報出異常時 exception items 能作爲以可回調的函數使用
如果可回調函數的第一個參數爲整數,那麼將會在mapping中虛招對應的值,
如果是WSGI application將在代理異常中引發
其餘的參數被轉發到異常構造函數。
"""
def __init__(self, mapping=None, extra=None): # 構造函數
if mapping is None:
# 由 line 763 得知default_exceptions 爲字典
mapping = default_exceptions
self.mapping = dict(mapping) # 再次進行轉化
if extra is not None:
self.mapping.update(extra)
def __call__(self, code, *args, **kwargs):
if not args and not kwargs and not isinstance(code, integer_types):
raise HTTPException(response=code)
if code not in self.mapping: # 如果code 不在 mapping中
raise LookupError("no exception for %r" % code)
# 報出mapping中對應函數的錯誤
raise self.mapping[code](*args, **kwargs)
此時,整個流程脈絡爲:
查看mapping
# site-packages\werkzeug\exceptions.py
default_exceptions = {}
"""
通過在模塊文件中設置 __all__ 變量,
當其它文件以“from 模塊名 import *”的形式導入該模塊時,
該文件中只能使用 __all__ 列表中指定的成員。
"""
__all__ = ["HTTPException"]
def _find_exceptions(): # 暫且忽略此函數作用
# globals() 函數會以字典類型返回當前位置的全部全局變量。
# iteritems()返回一個迭代器
for _name, obj in iteritems(globals()):
try:
# issubclass() 方法用於判斷參數 obj 是否繼承自 HTTPException
is_http_exception = issubclass(obj, HTTPException)
except TypeError:
is_http_exception = False
if not is_http_exception or obj.code is None:
continue
# 將繼承自HTTPException 的obj 類名添加至__all__
__all__.append(obj.__name__)
old_obj = default_exceptions.get(obj.code, None)
if old_obj is not None and issubclass(obj, old_obj):
continue
# 更新default_exceptions字典 {404: "NotFound"}
default_exceptions[obj.code] = obj
_find_exceptions() # 調用_find_exceptions() 函數
del _find_exceptions # 刪除引用 回收內存
函數_find_exceptions():
globals() 函數會以字典類型返回當前位置的全部全局變量。
class A:
pass
class B:
pass
print(globals())
# 結果如下:
{'__name__': '__main__',
... 省略了一部分,自行嘗試 ...
'A': <class '__main__.A'>, 'B': <class '__main__.B'> # 包含了所有類名與對象
}
僞代碼示例,幫助理解上述_find_exceptions():
from werkzeug._compat import iteritems
default_exceptions = {}
__all__ = []
class Base(object):
code = 1
class A(Base):
code = 2
class B(Base):
code = 3
def _find_exceptions():
for _name, obj in iteritems(globals()):
try:
is_http_exception = issubclass(obj, Base)
except TypeError:
is_http_exception = False
if not is_http_exception or obj.code is None:
continue
__all__.append(obj.__name__)
old_obj = default_exceptions.get(obj.code, None)
if old_obj is not None and issubclass(obj, old_obj):
continue
default_exceptions[obj.code] = obj
if __name__ == '__main__':
_find_exceptions()
print(default_exceptions)
print(__all__)
# 運行結果如下
{1: <class '__main__.Base'>, 2: <class '__main__.A'>, 3: <class '__main__.B'>}
['Test', 'Base', 'A', 'B']
繼承自HTTPException的類們
exceptions.py 中構建了大量與對應error的函數,以下僅貼出403,404類
# site-packages\werkzeug\exceptions.py
...
class Forbidden(HTTPException):
code = 403
description = (
"You don't have the permission to access the requested"
" resource. It is either read-protected or not readable by the"
" server."
)
class NotFound(HTTPException):
code = 404
description = (
"The requested URL was not found on the server. If you entered"
" the URL manually please check your spelling and try again."
)
...
HTTPException
# site-packages\werkzeug\exceptions.py
@implements_to_string
class HTTPException(Exception):
"""
所有HTTP異常的基類。此異常可以被WSGI應用程序跳轉到錯誤頁面時調用,
或者您可以捕獲子類獨立地呈現更好的錯誤消息頁面。
"""
code = None # 錯誤狀態碼,403,404等
description = None # 描述
def __init__(self, description=None, response=None):
super(HTTPException, self).__init__() # 繼承父類的Exception方法
if description is not None:
self.description = description
self.response = response
...省略部分...
def get_description(self, environ=None): # 返回描述內容
"""Get the description."""
return u"<p>%s</p>" % escape(self.description).replace("\n", "<br>")
def get_body(self, environ=None): # 返回頁面主題內容
"""Get the HTML body."""
return text_type(
(
u'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
u"<title>%(code)s %(name)s</title>\n"
u"<h1>%(name)s</h1>\n"
u"%(description)s\n"
)
% {
"code": self.code,
"name": escape(self.name),
"description": self.get_description(environ),
}
)
def get_headers(self, environ=None): # response headers
"""Get a list of headers."""
return [("Content-Type", "text/html; charset=utf-8")]
def get_response(self, environ=None):
"""
返回錯誤響應信息,即開頭所看見的錯誤頁面
"""
from .wrappers.response import Response
if self.response is not None:
return self.response
if environ is not None:
environ = _get_environ(environ)
headers = self.get_headers(environ)
return Response(self.get_body(environ), self.code, headers)
# site-packages\werkzeug\_internal.py
def _get_environ(obj):
env = getattr(obj, "environ", obj)
assert isinstance(env, dict), (
"%r is not a WSGI environment (has to be a dict)" % type(obj).__name__
)
return env
回頭看mapping
mapping爲錯誤狀態碼及其繼承自HTTPException 類對象所構成的字典
raise self.mapping[code](*args, **kwargs)
raise self.mapping[404](*args, **kwargs)
raise NotFound(*args, **kwargs)
return Response(self.get_body(environ), self.code, headers)
對手動處理HTTPException說No
當使用first_or_404() 查詢語句之後,未查找到結果,報出HTTPException 404,將返回Flask默認的404頁面,這是不美觀的
此時,想手動處理HTTPException 404,讓它跳轉到自定義的404頁面。
try:
user = User.query.filter_by(username="張三").first_or_404()
except Exceptions as e:
# do something
FLask提供了
app_errorhandler
裝飾器, 攔截指定的error code,實現自己的方法。
ps:藍圖對象中也有此裝飾器
代碼示例如下:
from flask import Flask
app = Flask(__name__)
@app_errorhandler(404) # 也可以未其他error code
def not_found(e):
return render_template("your 404.html"), 404
END!
如覺有用點個讚唄~