Handling Errors for Flask Restful API
Flask provides an effective method for managing application-specific and common errors by registering error handlers for different errors and exceptions. Unfortunately, the Flask-RESTful extension does not support error handling in non-debug mode (i.e., when app.run(debug=False)
). In this article, we will explore a scenario to gain a better understanding of the issue and then implement a workaround to handle our application-specific errors.
UserNotFound Exception
Consider a scenario where we have a list of users and a RESTful endpoint to retrieve users by user ID. If a user does not exist, we will raise a UserNotFound
(application-specific) exception. This exception inherits from the ApiException
class, which serves as the base class for all application-specific exceptions. Below is the structure for application-specific exceptions:
class ApiException(Exception): def __init__(self, error_message=None, error_code=None, details=None): """ :type error_message: str :type error_code: int :type additional_error_info: dict[str, T] """ Exception.__init__(self) self.message = error_message self.status_code = None if error_code is not None: self.status_code = error_code self.details = details def to_dict(self): error_dict = {'error': {}} if self.status_code: error_dict['error']['code'] = self.status_code or self.__class__.http_status_code() if self.message: error_dict['error']['message'] = self.message if self.details: for field_name, field_value in self.details.items(): error_dict['error'][field_name] = field_value return error_dict @classmethod def http_status_code(cls): return 500 class UserNotFound(ApiException): @classmethod def http_status_code(cls): return 404 class UnauthorizedUser(ApiException): @classmethod def http_status_code(cls): return 401
For demonstration purposes, let's assume we have two RESTful endpoints, one for any user and one for an admin.
from flask import Flask, json, Response, request from flask_restful import Api, Resource app = Flask(__name__) @app.errorhandler(500) def internal_server_error_handler(e): response = { 'error': { 'message': 'Internal Server Error: %s' % e.message } } return json.dumps(response) @app.errorhandler(ApiException) def exception_error_handler(e): return Response(json.dumps(e.to_dict()), content_type='application/json', status=500) api = Api(app) users = { '1': {'id': 1, 'name': 'Zohaib'}, '2': {'id': 2, 'name': 'Adam'}, '3': {'id': 3, 'name': 'Osman'}, 'admin': {'id': 5, 'name': 'Admin'} } class UserResource(Resource): def get(self, user_id): if str(user_id) in users: return users[str(user_id)] else: raise UserNotFound('User not found', 404) class AdminUserResource(Resource): def get(self): if request.values.get('pass') == '123456': return users['admin'] else: raise UnauthorizedUser('Not allowed.') api.add_resource(UserResource, '/user/<int:user_id>') api.add_resource(AdminUserResource, '/user/admin') if __name__ == '__main__': app.run(port=8001, debug=False)
JSON Response Examples
Now, when we execute http://127.0.0.1:8001/user/1
, we will receive the expected JSON response from UserResource:
{ "id": 1, "name": "Zohaib" }
However, attempting to retrieve a non-existent user (http://127.0.0.1:8001/user/7) raises a UserNotFound
exception. Ideally, this exception should be handled by the app's handler for ApiException
, returning a JSON response like:
{ "error": { "message": "User not found", "code": 404 } }
Instead, the app returns an unexpected response:
{ "message": "Internal Server Error" }
The issue arises because the Flask-RESTful extension does not handle errors in this manner. According to Flask-RESTful documentation, a solution involves passing a dictionary of errors and their corresponding responses and status codes in the Api()
constructor:
errors = { 'UserNotFound': { 'message': "User does not exist with this ID", 'status': 404, }, 'UnauthorizedError': { 'message': "Not allowed", 'status': 401 } } api = Api(app, errors=errors)
Now, when accessing http://127.0.0.1:8001/user/7
, i.e., a user that does not exist, we receive a clear error message:
{ "status": 404, "message": "User does not exist with this ID" }
CustomApi Class
While this solution works, modifying error messages for specific exceptions is challenging. The built-in solution lacks flexibility. Additionally, app error handlers are not functioning as expected. After debugging, it was revealed that the handle_error method of the flask.ext.restful.Api class catches exceptions and returns an unhelpful response like "Internal Server Error."
To address this, the CustomApi class is introduced to override the handle_error method:
class CustomApi(Api): def handle_error(self, e): if isinstance(e, ApiException): raise else: return super(CustomApi, self).handle_error(e)
Now, accessing a non-existing user (http://127.0.0.1:8001/user/7) yields a custom response:
{ "error": { "message": "User not found", "code": 404 } }
Conclusion
That concludes the discussion. I hope this proves helpful for those utilizing Flask-RESTful
for developing RESTful APIs. Your valuable comments and questions are most welcomed!