3

I am trying to use global error handlers(app_errorhandler) in my APIs but I am getting some problems. I have a blueprint for errors where I define global errors defined as:

from werkzeug.exceptions import NotFound
from flask_app.errors import error_bp

@error_bp.app_errorhandler(NotFound)
def not_found_error(error):
    return "Error 404", 404

and this works fine for all app routes, but not for APIs created by flask_restplus nor flask_restful(tried both). When I try to raise NotFound in them I get:

"The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. You have requested this URI [/user/info/1/] but did you mean /user/info// or /user/edit// or /user/login ?"

When I define a errorhandler in my API as:

@user_api.errorhandler(NotFound)
def new_error(error):
    return "Error handler in API 404"

It does not use this errorhandler in API but the global one(why? how?), so I got a way to use app_errorhandler but this is not a solution as I do not want to define for every API an errorhandler if I have global set up. All my APIs are created using the blueprint they are in.

So my question is: How do I use the app_errorhandler in APIs without defining errorhandler in every API?

Answers can be flask_restplus or flask_restful because I have no problem with switching from one to the other. Thanks in advance.

NOTE: There are a lot of workarounds but I think that apis should be able to use apps errors by default because blueprints can use them and apis inherit Blueprints

Project structure and code as per Mehdi Sadeghi request:

.
|____flask_test_app
| |____errors
|   |___ __init__.py
|   |___ handlers.py
| |____test_found
|   |___ __init__.py
|   |___ apis.py
| |_____ __init__.py

__init__.py:

from flask import Flask

def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config_name)

    from .errors import error_bp
    app.register_blueprint(error_bp)

    from .test_found import test_found
    app.register_blueprint(test_found)

    return app

errors.__init__.py:

from flask import Blueprint
error_bp = Blueprint('error_bp', import_name='error_bp')

from flask_test_app.errors import handlers

errors.handlers.py:

from werkzeug.exceptions import NotFound
from flask_test_app.errors import error_bp

@error_bp.app_errorhandler(NotFound)
def not_found_error(error):
    return "Error 404", 404

test_found.__init__py:

from flask import Blueprint
from flask_restplus import Api

test_found = Blueprint('test_found', import_name='test_found',
                       url_prefix='/test_found')

test_found_api = Api(test_found)
from flask_test_app.test_found import apis

test_found.apis.py:

from flask_restplus import Resource
from flask_test_app.test_found import test_found, test_found_api
from werkzeug.exceptions import NotFound

@test_found.route('/test_not_found', methods=['GET'])
def test_not_found():
    raise NotFound
    return "Not found does not work"

class TestAPINotFound(Resource):
    def get(self):
        raise NotFound
        return "TEST NOT FOUND FOR APIs"

test_found_api.add_resource(TestAPINotFound,
                      '/test_api_not_found',
                      endpoint='user_test')
kemis
  • 4,404
  • 6
  • 27
  • 40
  • In case of `flask_restplus` I can't reproduce your problem. When I add the event handler to my top most `api` it gets called for all the api errors. Perhaps you can share your initialization code and I'll try again. – mehdix Dec 17 '18 at 16:46
  • @MehdiSadeghi I have added the structure of my project – kemis Dec 17 '18 at 19:38
  • This might help you https://github.com/flask-restful/flask-restful/issues/280#issuecomment-280648790 – Luv Dec 17 '18 at 19:46
  • I'd suggest you reconsider your design. If you create one Api instance per blueprint, you will end up with multiple root api endpoints. You can leave your Resource subclasses in blueprint packages and import them in your `create_app` factory method and add them to your single top level `api` instance. Then you simply add your global api error handler using `api.errorhandler`. – mehdix Dec 17 '18 at 23:22
  • @MehdiSadeghi That structure would work for smaller projects, but for bigger i would want to have every `api` their blueprint beacuse there will be more then one resource per blueprint and just import blueprints in factory. Thank you for your answers. – kemis Dec 18 '18 at 09:30
  • 1
    What happens if you add the error handler directly to the flask app instead of in a blueprint? – Luis Orduz Dec 18 '18 at 15:32
  • @LuisOrduz Still does not work, i still have to define `errorhandler` in `api` for the app to use the `errorhandler` from `app` – kemis Dec 19 '18 at 09:24
  • Subclassing the API (like the last option in Mehdi Sadeghi's answer) solves the problem (as far as I understand it), you could change the `handle_error` method (in both restful or restplus) so that for NotFound errors you call the same function you decorated with `app_errorhandler`, for any other, you use `super()`. Is there a reason why you can't use a subclass of Api? – Luis Orduz Dec 19 '18 at 15:08
  • No, I will probably end up using Mehdi Sadeghi's answer, I was just wondering if it was in some way natively supported by `restful`/`restplus` and I was doing something wrong. – kemis Dec 19 '18 at 15:40

1 Answers1

3

In flask-restful, as described in the docs you can pass an errors dictionary to the api and customize the returned message:

errors = {
    'NotFound': {
        'message': "Something is missing.",
        'status': 404,
    }
}

api = Api(app, errors=errors)

Moreover it provides flask_restful.abort that you can use anywhere in your API:

class HelloWorld(Resource):
    def get(self):
        if not self.has_permission():
            return flask_restful.abort(403)
        return {'hello': 'world'}

You can also catch your exceptions such as NotFound and use flask_restful.abort as above.

However, if you really need to customize the error handling you can subclass the Api and implement your own error handing before calling flask_restful.abort:

class CustomApi(Api):
    def handle_error(self, e):
        # Do something and then abort
        flask_restful.abort('some error code...', 'error message')

api = CustomApi(app, errors=errors)
mehdix
  • 4,984
  • 1
  • 28
  • 36
  • Thanks you for the answer but this does not solve the issue.I knew that you can pass errors in `flask_restful` like that sorry for not adding it to in question. This does not address the problem because I want to define errors with `app_errorhandler` and not worry about it after that(not define in every api). `api`s should be able to use `app`s errors because `Blueprint`s can use them and `api`s and inherit `Blueprints`. – kemis Dec 17 '18 at 20:00
  • @kemis please see my comment under your post. – mehdix Dec 17 '18 at 23:23