错误处理示例
from pprika import PPrika app = PPrika() @app.error_handler(404) def not_found(e): print(e) return repr(e) if __name__ == '__main__': app.run()
与flask一样,通过error_handler装饰器来注册错误处理函数。该装饰器接受一个HTTP错误码或异常类(Exception)作为参数,会将发生的具体错误对象传递给处理函数。
上述代码指当发生404错误时以not_found作为处理函数,其返回值跟视图函数一样会被处理成响应返回,而error_handler只是 register_error_handler 的装饰器版本,上方错误注册可改写成 app.register_error_handler(404, not_found) ,因此重点讲述register_error_handler。
register_error_handler
def register_error_handler(self, code_or_exception, func, field=None): if isinstance(code_or_exception, Exception): raise ValueError(f""" 不可注册异常实例: {repr(code_or_exception)}, 只能是异常类或HTTP错误码 """) exc_class, code = self._get_exc_class_and_code(code_or_exception)
field参数表示该错误处理规则的适用范围,为None时表示作用于全局(app);为str时是蓝图名,表示仅作用于该蓝图(blueprint)内,非None的情况之后讲蓝图的时候会再提及。
注意到其中调用了 _get_exc_class_and_code,它会根据错误码或者异常类其中一个尝试补出对应的另一个,比如本例中code_or_exception=404,经过该函数处理会补出 werkzeug.exceptions.NotFound。
依据错误码得到异常类主要通过 werkzeug.exceptions 包中的一个字典default_exceptions,它以{http_error_code: exception_class}的形式记录了错误码与对应异常类的映射,反过来若异常类为HTTPException子类则e.code即为错误码,其他异常就统一将错误码当做None。具体实现方法与flask同名函数几乎一样,不再赘述。
handlers = self.error_handlers.setdefault(field, {}).setdefault(code, {})
handlers[exc_class] = func
紧接上文,error_handlers是一个字典:{field: {status: {error: function}}},通过作用范围、错误码、具体错误类三层记录该错误类exc_class具体的处理函数func,这样一个错误处理函数就注册好了。
接下来会讲述错误发生时的处理方式。
handle_user_exception
这是上一篇提到的dispatch_request内部的错误处理函数,也是内层错误处理
dispatch_request内部片段↓↓↓
try: endpoint, args = request.rule.endpoint, request.view_args rv = self.view_functions[endpoint](**args) except Exception as e: rv = self.handle_user_exception(e)
当该过程(尤其指视图函数被调用时)产生的异常会被捕捉交给 handle_user_exception 处理。
def handle_user_exception(self, e): handler = self._find_error_handler(e) if handler is not None: return handler(e) raise e
该函数负责的是像本实例中通过error_handler装饰器或register_error_handler方法注册过的错误,若该错误未被注册将再次将其抛出。
其中 _find_error_handler 内部先通过python内置的type函数得到异常类exc_class
,再通过上述的 _get_exc_class_and_code方法获得错误码code。
由于一个错误可能在不同范围、以不同错误码注册过,因此查找时以错误码取code、None,在此基础上再以field取blueprint(表蓝图范围)、取None(表全局范围)的顺序在 self.error_handlers 中查找处理函数,若找不到则按None返回。具体实现方法同样与flask同名函数几乎一样,暂且略过,之后讲述restful部分时可能会有补充。
handle_exception
该函数作为外层错误处理器受 wsgi_app 调用,处理 handle_user_exception 无法处理的,或处理过程中再次发生的异常
wsgi_app内部片段(去掉了不相关的)↓↓↓
try: rv = self.dispatch_request() response = make_response(rv) except Exception as e: response = self.handle_exception(e)
与handle_user_exception配合形成两层的错误处理,确保错误能正确响应
下方是其实现原理
def handle_exception(self, e): if isinstance(e, HTTPException): return e
首先判断如果是HTTPException或其子类实例则直接返回
server_error = InternalServerError() server_error.original_exception = e handler = self._find_error_handler(e) or self._find_error_handler(server_error)
到了这一步说明该错误可能没有对应的错误处理器,也不是能直接作为response的HTTPException,那就统一当做 '500 InternalServerError' 响应,并在此尝试获取其handler(以原错误先尝试获取是考虑到可能是handle_user_exception处理中的再次发生的异常)
if handler is not None: server_error = handler(server_error) else: print_exception(*exc_info()) return make_response(server_error)
最后若能找到handler则将返回值生成make_response并返回,否则说明不能自行处理该错误,打印出Traceback并直接返回InternalServerError(make_response内部也会把HTTPException这类的直接返回)。
注意此处不能将无法处理的server_error再次raise并同时returnmake_response(server_error),即便使用try...finally也不行,raise与return不共存。但又需要显示错误信息(traceback),故通过exc_info得到traceback信息并由print_excption打印出来。
from sys import exc_info from traceback import print_exception
这点与flask不同,它利用self.propagate_exceptions决定是否再次抛出,self.log_exception、self.logger对该错误进行记录,更加细致,具体的没有深入了...
结语
至此错误处理部分结束,不过这部分之后在将restful时会有进一步的补充,接下来先讲讲请求上下文。
[python] pprika:基于werkzeug编写的web框架(4) ——请求上下文与helpers