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'})
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