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!