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()
Related
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.
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'm using pynic framework to handle my APIs endpoints, but I guess this would be the same logic with Flask or Django.
I've got a few endpoints, and I was wondering if there were anyway to handle all the exceptions at the same place.
For instance:
class Pnorm(Handler):
def post(self):
logger = logging.getLogger(constants.loggerName)
template_exception = "Exception {0} in class {1} ({2})."
try:
myJson = DoThings()
return myJson
except HTTP_400 as e:
logger.critical(message)
raise e
except Exception as e:
# unknown exception raise 500
logger.critical(message)
raise HTTP_500(message)
Is there anyway I can make all my endpoints handle the exceptions the same way or do I hacve to repeat my "exception block" at the end of each point ?
(I don't mean in the same class only but through the project.)
Cheers,
Julien
Edited:
My main class:
class app(WSGI):
DataStructureHelper.set_dsh()
setup_logging.setup_logging(logger_name=constants.loggerName, console_level=logging.INFO)
routes = [
('/allocator', Allocator()),
('/data/([0-9]+)/([0-9]+)/([0-9]+)/([0-9]+)', InstrumentData()),
('/pnorm', Pnorm()),
('/portfolios')
]
I think the right approach would be decorators, since it fits the needs perfectly. Following is working piece of code w.r.t flask.
A word of caution is you need return the control back to handlers from decorator.
from functools import wraps
from flask import Flask, request
app = Flask(__name__)
def http_error_codes(method_name):
#wraps(method_name)
def handle_exceptions(*args):
try:
print("Inside the exceptions")
return method_name(*args)
except Exception as e:
print("HAHAHAHA")
raise e
return handle_exceptions
def do_the_login():
return "Testing is fun"
def show_the_login_form():
raise ValueError('The day is too frabjous.')
#app.route('/login', methods=['GET', 'POST'])
#http_error_codes
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()
if __name__ == '__main__':
app.run()
Hope this helps
If you want to handle all the exceptions in a single place, you can keep an general exceptional block like as follows..
try:
#Code part may give error
except Exception:
#If error what to do..
Here Exception is the General class, which will handle all exceptions irrespective of the error.
I want to get a GET parameter in my Flask app. I would like to check that this parameter is an integer if it was submitted.
If it was not submitted I would like to set a default value.
If it was submitted but is invalid, I would like to throw an error
I came up with the following code which works but seems ugly and unpythonic to me. What is the most pythonic way of doing this?
#app.route('/')
def index():
page = request.args.get('page') or 0
try:
page = int(page)
except:
return abort(500)
You can tell the request.args.get() method to test for your type, and set a default:
#app.route('/')
def index():
page = request.args.get('page', type=int, default=0)
If type conversion fails, the default is used. In a web environment that's a much, much better idea than to raise a 500 response (which is reserved for server errors, while not providing a valid page number is really a client error).
request.args.get() will otherwise never raise an exception; it returns None instead if no other default was set. If you must have an exception on type conversion, you are stuck with your approach, but I'd return a 400 Bad Request error code instead:
#app.route('/')
def index():
page_str = request.args.get('page', default=0)
try:
page = int(page_str)
except ValueError:
abort(400)
abort(...) raises an exception, no need to use return there. Because an exception is raised, you could make that whole block of 4 lines a separate function:
def convert_or_400(value, type):
"""Convert a value to the given type, or raise a 400 client error"""
try:
return type(value)
except ValueError:
abort(400)
Now the route becomes:
#app.route('/')
def index():
page = convert_or_400(request.args.get('page', default=0), int)
def index():
page = input("enter the value")
try:
if (page != ""):
page = int(page)
else:
page = 0
print("Input value..",page)
except:
print("wrong value given.Input numeric value")
index()
A simple way
This is the method in ReportRunner class in report_runner.py in my Flask-Restful app:
class ReportRunner(object):
def __init__(self):
pass
def setup_routes(self, app):
app.add_url_rule("/run_report", view_func=self.run_report)
def request_report(self, key):
# code #
def key_exists(self, key):
# code #
def run_report(self):
key = request.args.get("key", "")
if self.key_exists(key):
self.request_report(report_type, key)
return jsonify(message = "Success! Your report has been created.")
else:
response = jsonify({"message": "Error => report key not found on server."})
response.status_code = 404
return response
and the nose test calls the URL associated with that route
def setUp(self):
self.setup_flask()
self.controller = Controller()
self.report_runner = ReportRunner()
self.setup_route(self.report_runner)
def test_run_report(self):
rr = Report(key = "daily_report")
rr.save()
self.controller.override(self.report_runner, "request_report")
self.controller.expectAndReturn(self.report_runner.request_report("daily_report"), True )
self.controller.replay()
response = self.client.get("/run_report?key=daily_report")
assert_equals({"message": "Success! Your report has been created."}, response.json)
assert_equals(200, response.status_code)
and the test was failing with the following message:
AttributeError: 'Response' object has no attribute 'json'
but according to the docs it seems that this is how you do it. Do I change the return value from the method, or do I need to structure the test differently?
The test is now passing written like this:
json_response = json.loads(response.data)
assert_equals("Success! Your report has been created.", json_response["message"])
but I'm not clear on the difference between the two approaches.
According to Flask API Response object doesn't have attribute json (it's Request object that has it). So, that's why you get exception. Instead, it has generic method get_data() that returns the string representation of response body.
json_response = json.loads(response.get_data())
assert_equals("Success! Your report has been created.", json_response.get("message", "<no message>"))
So, it's close to what you have except:
get_data() is suggested instead of data as API says: This should not be used and will eventually get deprecated.
reading value from dictionary with get() to not generate exception if key is missing but get correct assert about missing message.
Check this Q&A also.