序言
restful作为本框架pprika的特色,其文件大小比之前介绍的其他模块(不包括核心部分app.py)加起来都大...而它的部分功能,指自动将dict、list类型返回值处理为json响应,也由make_response实现了。那么接下来准备谈谈restful中较为亮眼的部分——错误处理,对,又是错误处理,除此之外还有类似flask-restful一般用于组织代码的Resource类、便于验证参数的RequestParser类等。
接下来先从示例开始。
异常的处理
from pprika import Api v1 = Api('v1') @v1.route('/cats') def cat(): raise TypeError('这里没有猫')
请求该url将返回http500错误: {"message":"TypeError('这里没有猫')"},好像跟flask-restful相比没有优势?
flask-restful可以在初始化Api实例时传入error字典处理异常,如 v1 = Api(v1, errors=errors) ,但这个错误字典编写复杂还不易修改,而且键message是写死的,也就是说响应必然带有一个"message": "xx"
若通过抛出HTTPException及其子类的方式一定程度可以定制响应,但响应格式还是无法跟其他异常类统一
还有一点,flask-restful的restful功能与app、blueprint并不对等,相当于依附于后者上。此时若使用app或blueprint的错误注册,如 @main.error_handler(500) ,将无法成功应用该handler,原因就是flask_restful.Api有默认的json形式错误处理,其优先于flask.handle_user_exception,导致error_handler基本失效。
而在pprika中这些问题都不存在,错误可以被轻易且形式统一地定制,如上例可变为 {"code":500,"msg":"TypeError('这里没有猫')"} 。而且pprika.Api是Blueprint子类,拥有blueprint的注册方式,通过 @v1.error_handler 注册的处理器会优先于默认错误处理器调用,还可以在Api实例化时传入自定义异常类改变默认处理器的行为,如 v1 = Api('v1', exception_cls=CustomException)
那么pprika.Api是如何做到这些的?实际上也很简单↓
Api
class Api(Blueprint): exception_cls = ApiException def __init__(self, name, url_prefix=None, exception_cls=None): super().__init__(name, url_prefix) if exception_cls is not None: self.exception_cls = exception_cls self._deferred_funcs.append(lambda a: self._init_app(a))
ApiException是用于错误处理的基础类,可通过更改exception_cls替换。
这里做三件事:初始化父类Blueprint的属性、尝试替换exception_cls、延迟调用 _init_app。与蓝图一样,_deferred_funcs内的函数将在app.register_blueprint时被调用。
_init_app
from functools import partial def _init_app(self, app): if not app.api_set: app.handle_exception = partial(self._error_router, app.handle_exception) # 若自身未设置对应错误处理器,则错误由handle_user_exception中再抛出 app.api_set.add(self.name)
该函数负责Api的初始化,但它所做的其实只是把app.handle_exception换成了Api内部的self._error_router,(相当于在错误处理器外多加了一层用于处理器选择),这样之后进行错误处理时就有机会根据错误来源,对错误运用不同的处理方式。
而app.api_set即是PPrika的一个属性,是存储了每个注册到app上的Api名称的集合,通过对该集合判断来确保多个Api注册到app上时只将handle_exception 替换了一次。
ps:在flask-restful中将app的两层处理器(handle_user_exception、handle_exception)都进行了partial操作,也是因为它同时改变了内层处理器行为,导致发生于Api内部的错误总会被Api的默认处理器处理,没有机会使用该Api所附着的app/blueprint设置的处理器。
_error_router
def _error_router(self, original_handler, e): if request.blueprint == self.name: return self.handle_error(e) # api中的错误不使用original_handler return original_handler(e)
这个函数将根据错误的是否发生在Api内部来选择使用Api上的错误处理handle_error,还是原来的处理器(app上的handle_exception ),不同于flask-restful,这是二选一的问题,因此Api内部的错误总能按json方式响应。
ps:在flask-restful中该函数行为是若错误发生于Api内部,则先用Api内部的处理器,不行再使用原有的
handle_error
1 def handle_error(self, e): 2 if isinstance(e, self.exception_cls): 3 pass 4 elif isinstance(e, HTTPException): 5 e = self.exception_cls(e.code, e.description) 6 elif isinstance(e, ApiException): 7 e = self.exception_cls(e.status, e.message) 8 else: 9 print_exception(*exc_info()) 10 e = self.exception_cls(500, repr(e)) 11 return e.get_response()
看着一大坨,但目的只有一个:根据错误实例e的不同type,将不同的e都统一实例化为 self.exception_cls 类对象,并使用该对象上的方法get_response形成响应对象。
其中第8行开始的else语句意味着e为一般的异常实例,统一按照http码500响应。
ps:之前在请求上下文ctx那里也提到,类似于404/405的路由错误不会被任何蓝图(当然保存Api)的处理器捕捉,除非是在视图函数中人为抛出的。若对统一的json响应有严格要求可以继承Api类,重写_error_router函数,捕捉所有的404/405。
ApiException
class ApiException(Exception): status = None message = None def __init__(self, status=None, message=None): self.status = status or self.status self.message = message or self.message def get_response(self): body = {'message': self.message} rv = body, self.status return make_response(rv)
这个类就是Api.handle_error中用到的self.exception_cls,如果exception_cls属性未被设置的话。它根据响应码status与响应内容message调用make_response构造响应对象。
该类实现了__str__与__repr__方法,实现方式没有特殊要求,有就行,在此略过
通过继承该类(或完全重新写一个)可自定义统一的错误响应格式 ↓
from pprika import ApiException class CustomException(ApiException): pass v1 = Api('v1', exception_cls=CustomException)
具体示例参见源码中 pprika/app/v1/ 这一部分。
通过这个方法几乎可随意更改格式,但要注意self.exception_cls需要用于 self.handle_error 去转化其他的异常,而这种实例化总是会提供两个参数,因此定制的exception_cls若有新增属性并且用于get_response中,那么要在__init__中初始化这个属性,同时要记住__init__要考虑只接收两个参数的情况。
结语
这样restful的错误处理就完成了,接下来谈谈如何像flask-restful那般使用一个Resource类组织一个url下的所有method,以及简单参数验证RequestParser。
[python] pprika:基于werkzeug编写的web框架(7) ——restful的结构化与参数解析