31

It is easy to propagate error messages with flask-restful to the client with the abort() method, such as

abort(500, message="Fatal error: Pizza the Hutt was found dead earlier today
in the back seat of his stretched limo. Evidently, the notorious gangster
became locked in his car and ate himself to death.")

This will generate the following json output

{
  "message": "Fatal error: Pizza the Hutt was found dead earlier today
       in the back seat of his stretched limo. Evidently, the notorious gangster
       became locked in his car and ate himself to death.", 
  "status": 500
}

Is there a way to customise the json output with additional members? For example:

{
  "sub_code": 42,
  "action": "redirect:#/Outer/Space"
  "message": "You idiots! These are not them! You've captured their stunt doubles!", 
  "status": 500
}
Derek
  • 3,295
  • 3
  • 24
  • 31

7 Answers7

45

People tend to overuse abort(), while in fact it is very simple to generate your own errors. You can write a function that generates custom errors easily, here is one that matches your JSON:

def make_error(status_code, sub_code, message, action):
    response = jsonify({
        'status': status_code,
        'sub_code': sub_code,
        'message': message,
        'action': action
    })
    response.status_code = status_code
    return response

Then instead of calling abort() do this:

@route('/')
def my_view_function():
    # ...
    if need_to_return_error:
        return make_error(500, 42, 'You idiots!...', 'redirect...')
    # ...
Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152
  • 4
    I agree this is nice and clean, but sometimes you do really need an abort mechanism (with exceptions) to avoid writing those if+return everywhere. – tokland Jul 07 '17 at 10:10
  • 2
    This is just too simple, and literally useless in nested functions. – Fusion Apr 10 '20 at 11:11
28

I don't have 50 reputation to comment on @dappiu, so I just have to write a new answer, but it is really related to "Flask-RESTful managed to provide a cleaner way to handle errors" as very poorly documented here

It is such a bad document that took me a while to figure out how to use it. The key is your custom exception must inherit from flask_restful import HTTPException. Please note that you cannot use Python Exception.

from flask_restful import HTTPException

class UserAlreadyExistsError(HTTPException):
    pass

custom_errors = {
    'UserAlreadyExistsError': {
        'message': "A user with that username already exists.",
        'status': 409,
    }
}

api = Api(app, errors=custom_errors)

Flask-RESTful team has done a good job to make custom exception handling easy but documentation ruined the effort.

Crespo Wang
  • 469
  • 4
  • 6
  • 7
    you dont need to extend from HTTPException. Using Exception works fine, you only have to remember to set debug to FALSE in order to see the proper error response, otherwhise it will enter into debug and show the html stacktrace error. – Sebastian Oct 16 '15 at 14:23
  • Yes. To be more explicit: ```class UserAlreadyExistsError(Exception)``` is enough without importing HTTPException from flask_restful – colidyre Feb 15 '16 at 15:39
  • This will not allow you to send a custom error message, will it? (I mean at time of exception.) – Kelvin Jun 02 '16 at 16:07
  • As @Sebastian stated - Flask **debug mode** must be disabled (``false``). – Eido95 May 02 '19 at 09:19
  • 1
    Can I please know how to raise `UserAlreadyExistsError` from resources code ? – Shankar Guru Oct 23 '19 at 07:15
  • I want to return a json using abort and custom errors. is it possible ? – Roy Assis Aug 15 '22 at 15:12
12

As @Miguel explains, normally you shouldn't use exceptions, just return some error response. However, sometimes you really need an abort mechanism that raises an exception. This may be useful in filter methods, for example. Note that flask.abort accepts a Response object (check this gist):

from flask import abort, make_response, jsonify

json = jsonify(message="Message goes here")
response = make_response(json, 400)
abort(response)
tokland
  • 66,169
  • 13
  • 144
  • 170
6

I disagree with @Miguel on the pertinence of abort(). Unless you're using Flask to build something other than an HTTP app (with the request/response paradigm), I believe that you should use as much of the HTTPExceptions as possible (see the werkzeug.exceptions module). It also means using the aborting mechanism (which is just a shortcut to these exceptions). If instead you opt to explicitly build and return your own errors in views, it leads you into a pattern where you need to check values with a series of if/else/return, which are often unnecessary. Remember, your functions are more than likely operating in the context of a request/response pipeline. Instead of having to travel all the way back to the view before making a decision, just abort the request at the failing point and be done with it. The framework perfectly understands and has contingencies for this pattern. And you can still catch the exception in case you need to (perhaps to supplement it with additional messages, or to salvage the request).

So, similar to @Miguel's but maintaining the intended aborting mechanism:

 def json_abort(status_code, data=None):
    response = jsonify(data or {'error': 'There was an error'})
    response.status_code = status_code
    abort(response)

# then in app during a request

def check_unique_username(username):
    if UserModel.by__username(username):
        json_abort(409, {'error': 'The username is taken'})

def fetch_user(user_id): 
    try:
        return UserModel.get(user_id)
    except UserModel.NotFound:
        json_abort(404, {'error': 'User not found'})
Michael Ekoka
  • 19,050
  • 12
  • 78
  • 79
3

I had to define attribute code to my subclassed HttpException for this custom error handling to work properly:

from werkzeug.exceptions import HTTPException
from flask_restful import Api
from flask import Blueprint

api_bp = Blueprint('api',__name__)

class ResourceAlreadyExists(HTTPException):
    code = 400

errors = {
    'ResourceAlreadyExists': {
        'message': "This resource already exists.",
        'status': 409,
    },
}

api = Api(api_bp, errors=errors)

and then later, raise the exception

raise ResourceAlreadyExists
Crispin
  • 2,070
  • 1
  • 13
  • 16
1

It's obviously late, but in the meanwhile Flask-RESTful managed to provide a cleaner way to handle errors, as pointed out by the docs.

Also the issue opened to suggest the improvement can help.

dappiu
  • 926
  • 10
  • 11
  • 5
    Actually the documentation doesn't make clear (to me at least) how to define custom error messages. There's no example of code using it. – Francis Davey Apr 07 '15 at 08:08
  • @FrancisDavey Pass to Api constructor a dict with your custom errors, like the example in the docs, then just "raise MyCustomError" when you need to and let flask-restful do the rest. What flask-restful does is setup an @app.errorhandler(MyCustomError) for every exception you specify in the errors dict. – dappiu Apr 08 '15 at 20:54
  • 1
    Thanks. What wasn't clear is that the names in the dictionary are the name of exceptions that you also create elsewhere by subclassing Exception. At least that's what I think it is supposed to do. As I said, there's no example code making use of it, so it is hard to tell. Re-reading that page carefully still leaves me a bit puzzled. Hence why I asked the question. – Francis Davey Apr 09 '15 at 05:52
  • 1
    @dappiu - I am getting `NameError: global name 'InvalidEmailError' is not defined`. I have my errors defined in my package's __init__.py file and I am trying to `raise InvalidEmailError` in a views.py file that lives directly next to my __init__.py - any idea why I am getting `NameError` ? – Alex Daro Jun 02 '15 at 06:02
  • 1
    I agree with Francis---the example in the documentation is very terse, it is unclear if the keys are strings or should really be Exceptions, and I can't get it to work. Actually functioning example code would be greatly appreciated. – davidav Sep 02 '15 at 13:22
0

Using Flask-RESTful (0.3.8 or higher)

from flask_restful import Api
customErrors = {
    'NotFound': {
        'message': "The resource that you are trying to access does not exist",
        'status': 404,
        'anotherMessage': 'Another message here'
    },
    'BadRequest': {
        'message': "The server was not able to handle this request",
        'status': 400,
        'anotherMessage': 'Another message here'
    }
}
app = Flask(__name__)
api = Api(app, catch_all_404s=True, errors=customErrors)

The trick is to use the exceptions from Werkzeug Docs

So for instance, if you want to handle a 400 request, you should add BadRequest to the customErrors json object.

Or if you want to handle 404 errors, then use NotFound in your json object and so on

Selyst
  • 111
  • 1
  • 12