Search code examples
pythonpython-3.xflaskflask-restplus

Can I have a user-defined error message next to a restplus model?


I have written a Flask application that provides an API. I am using the RESTplus library.

I use a model to format the data. If a request is successful, the values are inserted into the model and the model is returned. However, if the request is unsuccessful, the model is returned and all values are null. My goal is to return a user-defined error message with multiple key-value pairs. The error message should have a different structure than as Model.

Here is a minimal example:

from flask import Flask
from flask_restplus import Resource, fields, Api


app = Flask(__name__)

api = Api()
api.init_app(app)


books = {'1': {"id": 1, "title": "Learning JavaScript Design Patterns", 'author': "Addy Osmani"},
         '2': {"id": 2, "title": "Speaking JavaScript", "author": "Axel Rauschmayer"}}

book_model = api.model('Book', {
    'id': fields.String(),
    'title': fields.String(),
    'author': fields.String(),
})

@api.route('/books/<id>')
class ApiBook(Resource):

    @api.marshal_with(book_model)
    def get(self, id):
        try:
            return books[id]
        except KeyError as e:
            return {'message': 'Id does not exist'}

if __name__ == '__main__':
    app.run()

Successful output

curl -X GET "http://127.0.0.1:5000/books/1" -H "accept: application/json"

{
  "id": "1",
  "title": "Learning JavaScript Design Patterns",
  "author": "Addy Osmani"
}

Erroneous output

curl -X GET "http://127.0.0.1:5000/books/3" -H "accept: application/json"

{
  "id": null,
  "title": null,
  "author": null
}

Is it possible to have a user-defined error message next to a model? Is there an alternative?


Solution

  • Don't catch the exception in the get method and then return an object; anything you return from the method will be marshalled using the model.

    Instead, follow the error handling documentation and use flask.abort() to set a 404 response with message:

    # at the top of your module
    from flask import abort
    
    # in the resource class
    @api.marshal_with(book_model)
    def get(self, id):
        try:
            return books[id]
        except KeyError as e:
            raise abort(404, 'Id does not exist')
    

    The second argument you give abort() is automatically turned into a JSON object with message key, so {"message": "Id does not exist"}.

    You could also create a @api.errorhandler registration for the KeyError exception and turn that into a 404 response:

    @api.errorhandler(KeyError)
    def handle_keyerror(error):
        return {"message": f"Object with id {error} could not be found"}, 404
    

    and then don't catch the exception in your get() methods:

    @api.marshal_with(book_model)
    def get(self, id):
        return books[id]
    

    Note that when ERROR_404_HELP is set to True (the default) then the message is added to by RestPlus with an alternative route suggestion, tacked on to every 404 response:

    curl -X GET "http://127.0.0.1:5000/books/3" -H "accept: application/json"
    {
        "message": "Object with id '3' could not be found. You have requested this URI [/books/3] but did you mean /books/<id> ?"
    }
    

    This may not be that helpful in your specific case so you may want to disable ERROR_404_HELP.