flask如何优雅的处理异常

2016/5/15 posted in  Python 黑魔法

如果使用flask编写RESTful风格的程序,不可避免的就是异常的处理问题。flask默认的异常处理方式实在不敢恭维,如果想达到能友好的返回错误信息,自己还要多做不少工作。

什么算优雅的返回错误信息呢?举个例子,下面的代码是获取User token的一段代码,如果密码验证失败将会向前端反馈错误信息,而反馈是通过抛出异常:

def get_user_token(self, data):
  valid(get_token, data)

  with get_session() as session:
  if "username" in data:
      user = session.query(User).filter_by(user_name=data['username']).first()
  elif "email" in data:
      user = session.query(User).filter_by(email=data['email']).first()
  else:
    user = None
    if not user or not user.valid_passwd(data['password']):
      raise APIException("get_token_error", "can't get the token with a wrong user password", 400)

  expiration = data['expiration'] if "expiration" in data else 86400
  return {
    "token": user.generate_auth_token(expiration=expiration),
    "id": user.id,
  }

那么可以在上层逻辑捕获这个异常,并把异常的信息包装一下优雅的反馈给前端,比如这样处理:

try:
  get_user_token(data)
except APIExcepting as e:
  return json.dumps({
    "id": e.error_id,
    "message": e.message,
    "code": e.code,
  })

当然,这只是最简单的包装,而且是http状态码写在了response的body里,而最好应该直接返回相应状态码的response。

每个下层逻辑的异常由上层来处理是很常规的办法,但是有的异常是可以直接抛到最上层的,比如参数错误,密码错误等,这些异常差的只是一个友好的显示,如果有办法对这些异常自动封装一个友好的反馈显示,那么就省不少事情。

而利用flask是可以实现这一点的,flask有一个叫errorhandler的东西,它可以捕捉特定的异常,然后根据这个异常进行自定义操作。那么,我们就可以创建一个可以直接抛到最上层的异常,由errorhandler监听捕捉这个异常,把捕捉到的异常封装成友好的response反馈出去就可以了。

首先是写一个可以被抛到最上层的异常:

class APIException(Base):
    def __init__(self, error_id, message, code=500):
        super(Base, self).__init__()
        self.raw_message = message
        self.error_id = error_id
        self.code = code
        self.message = message

    def to_dict(self):
        result = {
            "id": self.error_id,
            "code": self.code,
            "message": self.message,
        }
        return result

这个异常使用起来也很简单raise APIException(error_id, message, status_code)就可以了,而to_dict方法可以构建一个符合REST风格的错误信息返回body。下面就差一个错误处理句柄来把APIEception的实例封装成友好的response了。

@app.errorhandler(APIException)
def handle_api_exception(error):
    from flask import jsonify
    response = jsonify(error.to_dict())
    response.status_code = error.code
    return response

这样就有了一个处理APIException的句柄,当有这个异常抛出的话,它便会返回一个友好的body,而且状态码也会是在raise时制定的状态码。