Reputation: 791
I do have simple restful app with Flask-Restful
from flask import Flask
from flask_restful import Api
app = Flask(__name__)
...
api = Api(app)
api.add_resource(ContactList, "/contacts")
if __name__ == '__main__':
from object_SQLAlchemy import db
db.init_app(app)
app.run(port=5000)
class Contact(Resource):
parser = reqparse.RequestParser()
parser.add_argument(
'contact_no',
type=str,
required=True,
help="This field cannot be left blank"
)
@throttling.Throttle("10/m", strategy=2)
def get(self, name):
contact = Contacts.findbyname(name)
if contact:
return contact.json()
return {"message": "Contact does not exist."}, 404
'get' method is decorated with my implementation of throttling (https://github.com/scgbckbone/RESTAPI/blob/master/resources/utils/throttling.py). What is important is that the throttling decorator raises exceptions on some occasions - most importantly when limit is reached. I would like to be able to catch that exception and return some reasonable json message.
But none of following works:
from ..app_alchemy import api, app
@api.errorhandler(Exception)
def handle_error(e):
return {"error": str(e)}
@app.errorhandler(500)
def handle_error_app(e):
return {"error": str(e.args[0])}
@app.handle_exception(Exception)
def handle_it_app(e):
return {"error": str(e.args[0])}
@api.handle_exception(Exception)
def handle_it(e):
return {"error": str(e.args[0])}
I'm still getting back default message
{"message": "Internal Server Error"}
Do I use errorhandlers correctly, or is the issue related to the use of decorator? I truly have no idea.
Upvotes: 2
Views: 4502
Reputation: 31
I override the flask_restulf.Api and override the handle_error() and error_router() inside. The APIException is a subclass of the HttpException, and the UnknownException is a subclass of the APIException. There is no logger in the code, since sentry does not work properly with the flask_restful in my code, the same problem was presented in https://github.com/flask-restful/flask-restful/issues/90.
class Api_modified(Api):
def error_router(self, original_handler, e):
"""This function decides whether the error occured in a flask-restful
endpoint or not. If it happened in a flask-restful endpoint, our
handler will be dispatched. If it happened in an unrelated view, a UnknownException
will be returned.
In the event that the error occurred in a flask-restful endpoint but
the local handler can't resolve the situation, the router will return a
UnknownException.
:param original_handler: the original Flask error handler for the app
:type original_handler: function
:param e: the exception raised while handling the request
:type e: Exception
"""
if self._has_fr_route():
try:
return self.handle_error(e)
except Exception:
pass
else:
if isinstance(e,APIException):
return e.get_response()
else:
return UnknownException("未知原因错误,具体原因请查询日志").get_response()
def handle_error(self, e):
"""Error handler for the API transforms a raised exception into a Flask
response, with the appropriate HTTP and body.The status code is always 200, and
the message and result in the body varies.
:param e: the raised Exception object
:type e: Exception
"""
got_request_exception.send(current_app._get_current_object(), exception=e)
if not isinstance(e, HTTPException) and current_app.propagate_exceptions:
exc_type, exc_value, tb = sys.exc_info()
if exc_value is e:
raise UnknownException("未知原因错误,具体原因请查询日志")
else:
raise UnknownException("未知原因错误,具体原因请查询日志")
headers = Headers()
if isinstance(e, HTTPException):
if e.response is not None:
# If HTTPException is initialized with a response, then return e.get_response().
# This prevents specified error response from being overridden.
# eg. HTTPException(response=Response("Hello World"))
resp = e.get_response()
return resp
code = e.code
default_data = {
'message': getattr(e, 'description', http_status_message(code))
}
headers = e.get_response().headers
else:
resp = UnknownException("未知原因错误,具体原因请查询日志").get_response()
return resp
# Werkzeug exceptions generate a content-length header which is added
# to the response in addition to the actual content-length header
# https://github.com/flask-restful/flask-restful/issues/534
remove_headers = ('Content-Length',)
for header in remove_headers:
headers.pop(header, None)
data = getattr(e, 'data', default_data)
if code and code >= 500:
exc_info = sys.exc_info()
if exc_info[1] is None:
exc_info = None
current_app.log_exception(exc_info)
error_cls_name = type(e).__name__
if error_cls_name in self.errors:
custom_data = self.errors.get(error_cls_name, {})
code = custom_data.get('status', 500)
data.update(custom_data)
if code == 406 and self.default_mediatype is None:
# if we are handling NotAcceptable (406), make sure that
# make_response uses a representation we support as the
# default mediatype (so that make_response doesn't throw
# another NotAcceptable error).
supported_mediatypes = list(self.representations.keys())
fallback_mediatype = supported_mediatypes[0] if supported_mediatypes else "text/plain"
resp = self.make_response(
data,
code,
headers,
fallback_mediatype = fallback_mediatype
)
else:
resp = self.make_response(data, code, headers)
if code == 401:
resp = self.unauthorized(resp)
return resp
Upvotes: 0
Reputation: 3257
There is a Flask-Restful built-in tool for handling exceptions, you can pass a dictionary of exception classes and response fields to Api
constructor:
api = Api(app, errors={
'Exception': {
'status': 400,
'message': 'bad_request',
'some_description': 'Something wrong with request'
}
})
Status is 500 by default, all other field are just turned to JSON and sent in response.
There is a major downside: you cannot use exception text as error message. There is an open issue for it.
Upvotes: 3
Reputation: 1609
Sentry is a great tool for catching exceptions across different platforms and frameworks {Including Python, Django and Flask}. This example give pointers on you can integrate it with your Flask application.
I've used it in production, the feature I liked the most is that it captures context of the error, including Operating System, Browser Version etc along with other information.
Upvotes: 1