I have a custom exception shown below where functions are raising these exceptions.
class UnauthorizedToSendException(HTTPException):
code = 400
description = 'Unauthorized to Send.'
They are then defined in Flask
's create_app()
as follows:
def handle_custom_exceptions(e):
response = {"error": e.description, "message": ""}
if len(e.args) > 0:
response["message"] = e.args[0]
# Add some logging so that we can monitor different types of errors
app.logger.error(f"{e.description}: {response['message']}")
return jsonify(response), e.code
app.register_error_handler(UnauthorizedToSendException, handle_custom_exceptions)
When this exception is raised below:
class LaptopStatus(Resource):
@staticmethod
def get():
raise UnauthorizedToSendException('you are unauthorized to send')
However, the output is always this way:
{
"message": "you are unauthorized to send"
}
Is there something missing here?
CodePudding user response:
flask_restful.Api
has its own error handling implementation that mostly replaces Flask's errorhandler
functionality, so using the Flask.errorhandler
decorator or Flask.register_error_handler
won't work.
There are a couple solutions to this.
Don't Inherit from HTTPException
and set PROPAGATE_EXCEPTIONS
The flask-restful error routing has short circuits for handling exceptions that don't inherit from HTTPException
, and if you set your Flask app to propagate exceptions, it will send the exception to be handled by normal Flask's handlers.
import flask
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
# Note that this doesn't inherit from HTTPException
class UnauthorizedToSendException(Exception):
code = 400
description = 'Unauthorized to Send'
@app.errorhandler(UnauthorizedToSendException)
def handle_unauth(e: Exception):
rsp = {"error": e.description, "message": ""}
if len(e.args) > 0:
rsp["message"] = e.args[0]
app.logger.error(f"{e.description}: {rsp['message']}")
return flask.jsonify(rsp), e.code
class LaptopStatus(Resource):
@staticmethod
def get():
raise UnauthorizedToSendException("Not authorized")
api = Api(app)
api.add_resource(LaptopStatus, '/status')
if __name__ == "__main__":
# Setting this is important otherwise your raised
# exception will just generate a regular exception
app.config['PROPAGATE_EXCEPTIONS'] = True
app.run()
Running this I get...
#> python flask-test-propagate.py
# ... blah blah ...
#> curl http://localhost:5000/status
{"error":"Unauthorized to Send","message":"Not authorized"}
Replace flask_restul.Api.error_router
This will override the behavior of the Api
class's error_router
method, and just use the original handler that Flask would use.
You can see it's just a monkey-patch of the class's method with...
Api.error_router = lambda self, hnd, e: hnd(e)
This will allow you to subclass HTTPException
and override flask-restful's behavior.
import flask
from flask import Flask
from flask_restful import Resource, Api
from werkzeug.exceptions import HTTPException
# patch the Api class with a custom error router
# that just use's flask's handler (which is passed in as hnd)
Api.error_router = lambda self, hnd, e: hnd(e)
app = Flask(__name__)
class UnauthorizedToSendException(HTTPException):
code = 400
description = 'Unauthorized to Send'
@app.errorhandler(UnauthorizedToSendException)
def handle_unauth(e: Exception):
print("custom!")
rsp = {"error": e.description, "message": ""}
if len(e.args) > 0:
rsp["message"] = e.args[0]
app.logger.error(f"{e.description}: {rsp['message']}")
return flask.jsonify(rsp), e.code
class LaptopStatus(Resource):
@staticmethod
def get():
raise UnauthorizedToSendException("Not authorized")
api = Api(app)
api.add_resource(LaptopStatus, '/status')
if __name__ == "__main__":
app.run()
Switch to using flask.views.MethodView
Documentation:
Just thought of this, and it's a more drastic change to your code, but flask has facilities for making building REST APIs easier. flask-restful
isn't exactly abandoned, but the pace of changes have slowed down the last several years, and various components like the error handling system have become too inflexible.
If you're using flask-restful
specifically for the Resource
implementation, you can switch to the MethodView
, and then you don't need to do any kind of workarounds.
import flask
from flask import Flask
from flask.views import MethodView
from werkzeug.exceptions import HTTPException
app = Flask(__name__)
class UnauthorizedToSendException(HTTPException):
code = 400
description = 'Unauthorized to Send'
@app.errorhandler(UnauthorizedToSendException)
def handle_unauth(e: Exception):
rsp = {"error": e.description, "message": ""}
if len(e.args) > 0:
rsp["message"] = e.args[0]
app.logger.error(f"{e.description}: {rsp['message']}")
return flask.jsonify(rsp), e.code
class LaptopStatusApi(MethodView):
def get(self):
raise UnauthorizedToSendException("Not authorized")
app.add_url_rule("/status", view_func=LaptopStatusApi.as_view("laptopstatus"))
if __name__ == "__main__":
app.run()
CodePudding user response:
read the https://flask.palletsprojects.com/en/2.1.x/errorhandling/
but: See werkzeug.exception for default HTTPException classes. For HTTP400 (werkzeug.exception.BadRequest)