RESTful api design: обработка исключений с помощью вложенных функций (python, flask)



Я хотел бы улучшить свой стиль кодирования с более прочным пониманием try, except и raise в разработке API, и менее подробный код.



У меня есть вложенные функции, и когда одна из них ловит execption, я передаю исключение другой и так далее.



Но таким образом, я мог бы распространить несколько проверок одной и той же ошибки.
Я имею в виду:
[использование try vs if в python
для рассмотрения стоимости пробной операции.



Как бы вы справились с ошибкой только один раз через вложенные функции ?



Например




  • у меня есть функция 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'})
904   1  

1 ответ:

IMO try/except - лучший способ для этого варианта использования. Всякий раз, когда вы хотите справиться с исключительным случаем, введите try/except. Если вы не можете (или не хотите) обрабатывать исключение каким-либо разумным способом, пусть оно всплывает, чтобы быть обработанным дальше по стеку. Конечно, существуют различные причины для применения различных подходов (например, вы действительно не заботитесь об ошибке и можете вернуть что-то другое, не нарушая нормальной работы; вы ожидаете, что "исключительные" случаи будут происходить чаще, чем нет; и т. д.), но здесь try/except, кажется, имеет наибольший смысл:

В вашем примере было бы лучше оставить try/except из f(), если вы не хотите...

Вызовите другую ошибку (будьте осторожны с этим, так как это приведет к сбросу трассировки стека):

try:
    ### Do some stuff
except:
    raise CustomError('Bad things')

Выполните некоторую обработку ошибок (например, ведение журнала; очистка; и т. д.):

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

    ### Re-raise the same error
    raise

В противном случае просто дайте ошибке всплыть.

Последующие функции (напр.g(); h()) будет действовать точно так же. В вашем случае, вы, вероятно, хотели бы иметь некоторые вспомогательные функции jsonify, которые jsonifies, когда это возможно, но также обрабатывает данные, отличные от 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')

Тогда у вас будет обработчик(ы) дальше по стеку для обработки либо исходной ошибки, либо пользовательской ошибки, заканчивающийся глобальным обработчиком, который может обрабатывать любую ошибку. В моем приложении 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

...который расширен для различных ошибок 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

И мой глобальный обработчик выглядит так (я использую 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)

С помощью flask_restful, вы также можете просто определить ваши классы ошибок и передать их в качестве словаря в flask_restful.Api constructor, но я предпочитаю гибкость определения моего собственного обработчика, который может добавлять полезные данные динамично. flask_restful автоматически передает все необработанные ошибки в handle_error. Таким образом, это единственное место, где мне нужно было преобразовать ошибку в данные json, потому что это то, что нужно flask_restful для возврата статуса https и полезной нагрузки клиенту. Обратите внимание, что даже если тип ошибки неизвестен (например, to_dict не определен), я могу вернуть клиенту нормальный http-статус и полезную нагрузку, не прибегая к преобразованию ошибок ниже по стеку.

Опять же, есть причины для преобразования ошибок в некоторые полезные возвращаемые значения в других местах вашего приложения, но для вышеперечисленных, try/except работает хорошо.

Comments

    Ничего не найдено.