在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页面!返回页面如下
此代码位于 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)
# 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 # 删除引用 回收内存
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']
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."
)
...
# 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"%s
" % escape(self.description).replace("\n", "
")
def get_body(self, environ=None): # 返回页面主题内容
"""Get the HTML body."""
return text_type(
(
u'\n'
u"%(code)s %(name)s \n"
u"%(name)s
\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为错误状态码及其继承自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)
当使用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!
如觉有用点个赞呗~