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

RESTful api design: handle exceptions through nested functions (python, flask)

我想改进我的编码风格,在设计API时更好地掌握tryexceptraise,并且减少冗长的代码。

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

但是像这样,我可以对同一个错误进行多次检查。我指的是:[在python中使用try与if考虑试运行成本。

如何在嵌套函数中只处理一次错误?

例如。

  • 我有一个函数f(key)对键执行一些操作;结果是传递给其他函数g()h()
  • 如果结果符合需要数据结构,g()。h()将操作并返回更新结果
  • 装饰器将返回最终结果或返回所遇到的第一个错误,指出提出方法(f()g()h())。

我正在做这样的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'})


imo try/except是这个用例的最佳方式。每当你想处理一个特殊的情况时,就输入一个try/except。如果您不能(或不想)以某种明智的方式处理异常,那么就让它冒泡起来,以便在堆栈中进一步处理。当然,采取不同的方法有多种原因(例如,您不真正关心错误,可以在不中断正常操作的情况下返回其他内容;您希望"异常"情况经常发生;等等),但在这里,try/except似乎最有意义:

在你的例子中,最好把try/exceptf()中去掉,除非你想……

引发另一个错误(请小心,因为这将重置堆栈跟踪):

1
2
3
4
try:
    ### Do some stuff
except:
    raise CustomError('Bad things')

执行一些错误处理(例如日志记录、清理等):

1
2
3
4
5
6
7
8
try:
    ### Do some stuff
except:
    logger.exception('Bad things')
    cleanup()

    ### Re-raise the same error
    raise

否则,就让错误冒泡。

后续功能(如g()h())的操作方式相同。在您的例子中,您可能希望有一些jsonify助手函数,该函数在可能的情况下进行jsonify,但也处理非json数据:

1
2
3
4
5
6
7
8
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')

然后,您将在堆栈的更上一层拥有处理程序来处理原始错误或自定义错误,最后是一个可以处理任何错误的全局处理程序。在我的flask应用程序中,我创建了自定义的错误类,我的全局处理程序能够解析这些类并用它们做一些事情。当然,全局处理程序也被配置为处理意外错误。

例如,我可能有一个用于所有HTTP错误的基类…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
### 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

…针对各种HTTP错误进行扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

使用flask_restful,您也可以定义错误类,并将它们作为字典传递给flask_restful.Api constructor,但我更喜欢灵活地定义自己的处理程序,它可以动态地添加有效负载数据。flask_restful自动将任何未处理的错误传递给handle_error。因此,这是我唯一需要将错误转换为JSON数据的地方,因为这正是flask_restful需要的,以便向客户机返回HTTPS状态和有效负载。注意,即使错误类型未知(如to_dict未定义),我也可以向客户机返回一个健全的HTTP状态和有效负载,而无需将错误转换到更低的堆栈。

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