When registering a handler for werkzeug.exceptions.HTTPException, it has no effect when an HTTP error is raised.
Assume the following handler:
@app.errorhandler(HTTPException)
def http_err_handler(error):
response = jsonify({
"success": False,
"message": error.name
})
response.status_code = error.code
return response
When requesting a page for which no route exists, a JSON response should be returned by the error handler, but instead, the usual Flask-generated HTTP error page is returned.
On the other hand, if the error handler is defined to handle a specific error code (by passing the error code to the app.errorhandler decorator), the exception is trapped and the JSON message returned.
As wekzeug.exceptions.HTTPException is the class raised internally by the abort() function, why isn't it possible to create a "catch-all" handler like this? Am I missing something?
Long story short: It is a bug, it's a flaw in the design, it's easy to work around as a user, so it's rather low priority to fix. :S
Do you know where the bug exactly lies? If this is an already known bug, with some informations about it maybe I could help by trying to fix it by myself and then submit a pull-request.
I think it would require rewriting most of the error handling system for Flask, the code for this is in flask/app.py
Fixed by #839
HTTPException.Could you provide a testcase that fails?
import unittest
from werkzeug.exceptions import HTTPException, NotFound
import flask
class ErrorHandlerTest(unittest.TestCase):
def setUp(self):
app = flask.Flask(__name__)
@app.errorhandler(HTTPException)
def http_exception(e):
return 'generic', 500
@app.errorhandler(NotFound)
def notfound_exception(e):
return 'not found', 404
@app.route('/error')
def error():
flask.abort(500)
self.app = app
def test_notfound_handler(self):
rv = self.app.test_client().get('/')
self.assertEqual(rv.status_code, 404)
self.assertEqual(rv.data, 'not found')
def test_http_handler(self):
rv = self.app.test_client().get('/error')
self.assertEqual(rv.status_code, 500)
self.assertEqual(rv.data, 'generic')
if __name__ == '__main__':
unittest.main()
$ venv/bin/python app.py
F.
======================================================================
FAIL: test_http_handler (__main__.ErrorHandlerTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "app.py", line 35, in test_http_handler
self.assertEqual(rv.data, 'generic')
AssertionError: '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n<title>500 ...' != 'generic'
----------------------------------------------------------------------
Ran 2 tests in 0.021s
FAILED (failures=1)
Code 500 is an InternalServerError (subclass of HTTPException). Since there is no specific handler for it (unlike NotFound), one would hope it would be handled by @app.errorhandler(HTTPException)
Ahh, I think this is because HTTPException doesn't have a HTTP code, but the error handling logic tries to find an errorhandler _with the HTTP code_.
cc @flying-sheep
Has anybody reviewed/tested #1383? I was not very sure by looking at the changes.
Yeah this slipped through the cracks!
I wonder whether we shouldn't remove the code/no-code distinction because of this bug though.
right, i didn鈥檛 think of this, sorry!
so the legal classes to register handlers for: HTTPException, the finite set of all subclasses of it (synonymous with error codes), and subclasses of subclasses (e.g. class ForbiddenBecauseNotRegistered(Forbidden): ...
and the legal instances of exceptions to raise are instances of subclasses of HTTPException but not direct instances of HTTPException.
this means the following assumptions hold:
if isinstance(e_instance, HTTPException):
assert e_instance.code is not None
if issubclass(e_class, HTTPException):
assert e_class is HTTPException or e_class.code is not None
we should add a way to add a handler for HTTPExceptions without allowing users to raise an instance of HTTPException.
@Bekt that PR should be closed, it was written against the old broken behavior that I replaced.
For those who simply want to override the default behavior of the _default_ HTTPException subclasses, you can do something like this:
import flask
from werkzeug.exceptions import default_exceptions
def create_app():
app = flask.Flask(__name__)
....
for code, ex in default_exceptions:
app.errorhandler(code)(_handle_http_exception)
def _handle_http_exception(error):
if flask.request.is_json:
return jsonify({
'status_code': error.code,
'message': str(error),
'description': error.description
}), error.code
raise error.get_response()
Resolved by https://github.com/pallets/flask/pull/2314
@Bekt I'm a bit late to the party, hope you're around... I'm trying to get flask to return JSON errors when there are exceptions. Your code works great except for raise error.get_response(). According to werkzeug docs:
If you call get_response() on an exception you will get back a regular BaseResponse object, even if you are using a custom subclass.
This results in raise error.get_response() returning TypeError: exceptions must derive from BaseException. Did I miss something? Is there a better way of doing this in 2019 as opposed to 2015.
@antgel i would be lying if i said i remember even remotely what this thread is about... i miss my flask days.
I mean, the error message is pretty clear: error.get_response returns a Response, not an Exception, so you probably just have to replace raise with return
Long story short: It is a bug, it's a flaw in the design, it's easy to work around as a user, so it's rather low priority to fix. :S
Hi
So late, but it is NOT a bug, this is a design decision. HTTPException is the base type for all http exceptions in werkzeug.
and werkzeug or flask themselve, never raise an instance of HTTPException, instead they raise different subclasses of it.
so this is implemented this way to enforce that HTTPException is the base type, and should not be raised directly. but you
could register an error handler for HTTPException and handle all subclasses of it without any problem. the only thing
that you have to do is NOT to raise a direct instance of HTTPException, instead raise its subclasses.
Most helpful comment
For those who simply want to override the default behavior of the _default_
HTTPExceptionsubclasses, you can do something like this: