RESTful api 设计:通过嵌套函数处理异常(python、flask)

use*_*883 2 python api try-catch restful-architecture

我想提高我的编码风格的一个更强大的把握tryexceptraise在设计API,以及更简洁的代码。

我有嵌套函数,当一个函数捕获到一个 execption 时,我将异常传递给另一个函数,依此类推。

但是像这样,我可以传播对同一错误的多次检查。我指的是:[在python中使用try vs if 来考虑尝试操作的成本。

您将如何在嵌套函数中仅处理一次错误?

例如

  • 我有一个功能f(key)对键进行一些操作;结果传递给其他函数g()h()
  • 如果结果符合预期的数据结构, g() .. h() 将操作并返回更新的结果
  • 装饰器将返回最终结果或返回遇到的 第一个错误,即指出它是在哪个方法中引发的(f(),g()h())。

我正在做这样的事情:

def f(key):
   try:
     #do something
     return {'data' : 'data_structure'}
   except:
     return {'error': 'there is an error'}

@application.route('/')
def api_f(key):
    data = f(k)
    try:
       # do something on data
       return jsonify(data)
    except:
       return jsonify({'error':'error in key'})
Run Code Online (Sandbox Code Playgroud)

Cla*_*ine 5

IMOtry/except是实现此用例的最佳方式。每当您想处理特殊情况时,请将try/except. 如果您不能(或不想)以某种理智的方式处理异常,请让它冒泡以在堆栈中进一步处理。当然,采取不同方法的原因有很多(例如,您并不真正关心错误,可以在不中断正常操作的情况下返回其他内容;您希望“异常”情况经常发生;等等),但在这里try/except似乎最有意义:

在你的榜样,它会是最好的离开try/except了的f(),除非你要...

引发一个不同的错误(小心这个,因为这会重置你的堆栈跟踪):

try:
    ### Do some stuff
except:
    raise CustomError('Bad things')
Run Code Online (Sandbox Code Playgroud)

做一些错误处理(例如日志记录;清理;等等):

try:
    ### Do some stuff
except:
    logger.exception('Bad things')
    cleanup()

    ### Re-raise the same error
    raise
Run Code Online (Sandbox Code Playgroud)

否则,就让错误冒出来。

随后的功能(例如g(); h())将以相同的方式运行。在您的情况下,您可能想要一些 jsonify 辅助函数,在可能的情况下进行 jsonify,但也处理非 json 数据:

def handle_json(data):
    try:
        return json.dumps(data)
    except TypeError, e:
        logger.exception('Could not decode json from %s: %s', data, e)

        # Could also re-raise the same error
        raise CustomJSONError('Bad things')
Run Code Online (Sandbox Code Playgroud)

然后,您将有处理程序进一步向上堆栈以处理原始错误或自定义错误,并以可以处理任何错误的全局处理程序结束。在我的 Flask 应用程序中,我创建了自定义错误类,我的全局处理程序能够解析这些类并对其进行处理。当然,全局处理程序也被配置为处理意外错误。

例如,我可能有一个所有 http 错误的基类......

### Not to be raised directly; raise sub-class instances instead
class BaseHTTPError(Exception):
    def __init__(self, message=None, payload=None):
        Exception.__init__(self)
        if message is not None:
            self.message = message
        else:
            self.message = self.default_message

        self.payload = payload

    def to_dict(self):
        """ 
        Call this in the the error handler to serialize the
        error for the json-encoded http response body.
        """
        payload = dict(self.payload or ()) 
        payload['message'] = self.message
        payload['code'] = self.code
        return payload
Run Code Online (Sandbox Code Playgroud)

...针对各种 http 错误进行了扩展:

class NotFoundError(BaseHTTPError):
    code = 404 
    default_message = 'Resource not found'

class BadRequestError(BaseHTTPError):
    code = 400 
    default_message = 'Bad Request'

class NotFoundError(BaseHTTPError):
    code = 500 
    default_message = 'Internal Server Error'

### Whatever other http errors you want
Run Code Online (Sandbox Code Playgroud)

我的全局处理程序看起来像这样(我正在使用flask_restful,所以它被定义为我扩展flask_restful.Api类上的一个方法):

class RestAPI(flask_restful.Api):
  def handle_error(self, e):
      code = getattr(e, 'code', 500)
      message = getattr(e, 'message', 'Internal Server Error')
      to_dict = getattr(e, 'to_dict', None)

      if code == 500:
          logger.exception(e)

      if to_dict:
          data = to_dict()
      else:
          data = {'code': code, 'message': message}

      return self.make_response(data, code)
Run Code Online (Sandbox Code Playgroud)

使用flask_restful,您也可以只定义错误类并将它们作为字典传递给flask_restful.Api constructor,但我更喜欢定义自己的处理程序的灵活性,该处理程序可以动态添加有效负载数据。flask_restful自动将任何未处理的错误传递给handle_error. 因此,这是我唯一需要将错误转换为 json 数据的地方,因为这是flask_restful将 https 状态和有效负载返回给客户端所需要的。请注意,即使错误类型未知(例如to_dict未定义),我也可以将正常的 http 状态和有效负载返回给客户端,而不必转换堆栈中的错误。

同样,有理由在应用程序的其他地方将错误转换为一些有用的返回值,但对于上述情况,try/except效果很好。