I have a flask website in which I tried implementing an error handler but it does not work.
For example when I raise an abort(400) my error handler is not being called.
The part of my code:
#app.route("/terms", methods=["GET"])
def terms():
abort(400)
return render_template("terms.html")
#app.errorhandler(Exception)
def handle_bad_request():
return render_template("error.html")
You should try
#app.errorhandler(400)
def handle_bad_request(error)
return render_template("error.html"), 400
In this error (def handle_bad_request(error)) is an argument , you can add what ever you want, try adding error or e preferably.
Related
Is there anyway to handle all the type of error with one definition & return the type of error occurred , For example in the below code error handeller is there for 404 but if I am unaware of other possible errors how can I print the error on html page
from flask import Flask, abort
app = Flask(__name__)
#app.route("/")
def hello():
return "Welcome to Python Flask."
#app.errorhandler(404)
def invalid_route(e):
return "Invalid route."
Expected if possible
#app.errorhandler(Type of error) #is there anything I can user here ?
def invalid_route(e):
return f"Type of error occurred is {e}"
Yea there are generic exception handlers
from flask import Flask
from werkzeug.exceptions import HTTPException
app = Flask(__name__)
#app.route("/")
def hello():
return "Welcome to Python Flask."
#app.errorhandler(HTTPException)
def handle_exception(e):
return f'Type of error occurred is {e.code}'
app.run()
Flask==1.1.2
Python==3.8
I am building a restAPI that is serving machine learning model. My co-worker who will be sending request to my restAPI and use the outcome to send it to users wants me to send him appropriate error message as well with status_code.
I've did a lot of searching on how to properly handle errors in Python Flask however I am still stuck on what would be a best practice for scalable and maintainable restAPI.
Currently whenever some error occurs I simply return dictionary with message and status code. There are some problems with this method that I want to mitigate:
If error occurs inside a function it has to return dictionary containing error messages to where the function was called and at need to check if it was actually an error, if yes then return error message
Example:
def add_data(x,y):
"""return addition of x,y. They both need to be integers"""
if type(x) != int:
return "x has wrong datatype"
if type(y) != int:
return "y has wrong datatype"
return x+y
#app.route("/predict", methods=["POST"])
def predict():
data = request.get_json()
result = add_data(data["x"], data["y"])
if type(result) == str:
return {"message":"input error", "status":222}
Cannot break code inside a function.
following some references
Custom Python Exceptions with Error Codes and Error Messages
What is best practice for flask error handling?
I've changed my code to following:
class InputError(Exception):
status_code = 400
def __init__(self, message, status_code=None):
Exception.__init__(self)
self.message = message
if status_code is not None:
self.status_code = status_code
def __str__(self):
return repr(self.status_code)
def add_data(x,y):
if type(x) != int:
raise InputError("x has wrong datatype", status_code=222)
if type(y) != int:
raise InputError("y has wrong datatype", status_code=222)
return x+y
This does break the code where error is found however I cannot find out how to return dictionary just like before.
How can I do this and which practice is considered a best practice?
The solution is to use error handlers
https://flask.palletsprojects.com/en/1.1.x/errorhandling/
In your case:
#app.errorhandler(InputError)
def handle_input_error(e):
return {"message": e["message"], "status": e["status"]}
Now whenever you raise InputError somewhere in the code, flask will know to call this method and return this response
If you have more types of errors I would switch to something more general
class MyErrors(Exception):
status_code: int
def __init__(self, message):
super(MyErrors, self).__init__(message)
self.message = message
def response(self):
return {"message": self.message, "status": self.status_code}
class InputError(MyErrors):
status_code = 222
class SomeOtherError(MyErrors):
status_code = 123
#app.errorhandler(MyErrors)
def handle_errors(e):
return e.response()
For our requirements, I made the following changes
I updated the input validation exception code from 422 to 400.
I also modified the default Json error output.
My issue
My FastAPI generated automatic documentation is sill showing default error code and error message format.
My Question
Is it possible to update the API documentation to reflect my change like the correct error code and the right error output format?
Currently there is no simple way. You have to modify the OpenAPI file as described here. Meaning you have to load the dictionary and remove the references to the error 422. Here is a minimal example:
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
from fastapi.exceptions import RequestValidationError
app = FastAPI()
#app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
#app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
"""Your custom validation exception."""
message =
return JSONResponse(
status_code=400,
content={"message": f"Validation error: {exc}"}
)
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi()
# look for the error 422 and removes it
for method in openapi_schema["paths"]:
try:
del openapi_schema["paths"][method]["post"]["responses"]["422"]
except KeyError:
pass
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
The current way to do it is modifying the generated OpenAPI https://fastapi.tiangolo.com/advanced/extending-openapi/
I have the following error handlers:
#api.errorhandler(DatabaseException)
def handle_database_exception(database_error):
return database_error.query_error, 400
#api.errorhandler(ValidationException)
def handle_validation_exception(validation_error):
return {'fields': validation_error.body_error}, 400
These are simple classes:
class ValidationException(Exception):
def __init__(self, body_error=None, missing_files=None):
Exception.__init__(self, "Validation failed")
self.body_error = body_error
if missing_files is not None:
for missing_file in missing_files:
self.body_error[missing_file] = ['required file']
class DatabaseException(Exception):
def __init__(self, details):
Exception.__init__(self, "Database Error")
self.query_error = details
Here's my problem:
If I call raise the DatabaseException in any of my routes, it falls through and I get a 500 template from flask.
The really funny thing is that the ValidationException, which was implemented earlier works just fine.
I went through in detail what's going on, when the ValidationException is raised, it goes through response.py and ends up in the error handler. I'm unfortunately can't comprehend everything that's going on deep inside flask, but in debug, the DatabaseException is certainly went on a different route.
I expect that the error handlers get called. If I raise a DatabaseException in one of my routes, it should get called.
Sorry, my answer was a bit strange. If you want to return a JSON response, you could do it like this.
class ValidationException(Exception):
def __init__(self, body_error=None, missing_files=None):
Exception.__init__(self, "Validation failed")
self.body_error = body_error
if missing_files is not None:
for missing_file in missing_files:
self.body_error[missing_file] = ['required file']
#api.errorhandler(ValidationException)
def handle_validation_exception(validation_error):
from flask import jsonify, make_response
return make_response(jsonify({'fields': validation_error.body_error}), 400)
This way is also possible.
#api.errorhandler(ValidationException)
def handle_validation_exception(validation_error):
return "{'fields': validation_error.body_error}", 400
The DatabaseException works fine and returns 400 Bad Request with a plaintext body.
Have fun ^2.
DatabaseException worked perfectly for me.
In the second solution, you try to return a Python dictionary. I assume that the missing quotes cause the error. Maybe you could use jsonify and make_response.
#api.errorhandler(DatabaseException)
def handle_validation_exception(validation_error):
from flask import jsonify, make_response
return make_response(jsonify({ 'fields': validation_error.query_error }), 400)
Have fun!
I have simple view like that:
#blueprint.route('/')
def index():
'''CMS splash page!'''
print request.form['no-key-like-that']
return render_template('home/splash.html', title='Welcome')
The goal of that view is to cause BadRequest error on Flask. That happens and I got very generic error page in the process that says:
Bad Request
The browser (or proxy) sent a request that this server could not understand.
However I want to intercept all errors and serve them wrapped in our templates for better look and feel, I do this like that:
#app.errorhandler(TimeoutException)
def handle_timeout(error):
if utils.misc.request_is_xhr(request):
return jsonify({'api_timeout': True}), 503
return redirect(url_for('errors.api_timeout', path=request.path))
However the same cannot be done for BadRequest exception, I tried:
#app.errorhandler(werkzeug.exceptions.BadRequestError)
def handle_bad_request(error):
return render_template(
'errors/bad_request.html',
exception_message=unicode(error),
return_path=request.path), 400
And:
#app.errorhandler(werkzeug.exceptions.BadRequestKeyError)
def handle_bad_request(error):
return render_template(
'errors/bad_request.html',
exception_message=unicode(error),
return_path=request.path), 400
And:
#app.errorhandler(werkzeug.exceptions.HttpError)
def handle_bad_request(error):
return render_template(
'errors/bad_request.html',
exception_message=unicode(error),
return_path=request.path), 400
In each case, instead of my bad_request.html there is raw response I mentioned above.
Bad Request
The browser (or proxy) sent a request that this server could not understand.
What actually works for me:
# NOTE: for some reason we cannot intercpet BadRequestKeyError, didn't
# figure out why. Thus I have added check if given 400 is
# BadRequestKeyError if so display standard api error page. This happens
# often when dev tries to access request.form that does not exist.
#app.errorhandler(400)
def handle_bad_request(error):
if isinstance(error, werkzeug.exceptions.BadRequestKeyError):
if utils.misc.request_is_xhr(request):
return jsonify({
'api_internal_error': True,
'error': unicode(error)
}), 500
return render_template(
'errors/api_internal.html',
exception_message=unicode(error),
return_path=request.path), 500
However as you can see it's far from perfection as error 400 is not necessarily always BadRequestKeyError.
Because exception handling works for any other exception but not BadRequest family it keeps me wondering, is it a bug? Or perhaps I am doing something wrong.