noirbizarre / flask-restplus

Fully featured framework for fast, easy and documented API development with Flask
http://flask-restplus.readthedocs.org
Other
2.73k stars 507 forks source link

Error handler for a base class of errors does not catch instances of child classes #421

Open codethief opened 6 years ago

codethief commented 6 years ago

In the code example below, I'm defining a custom error handler for errors of type BaseError but if I raise InsecurePasswordError (a child class of BaseError), it won't be caught by said error handler and I end up with an internal server error (500). If I define the error handler for InsecurePasswordHandler directly, though, everything works fine. (Obviously, I'd like to avoid that because I have dozens of error classes.)

from flask import Flask, Blueprint
from flask_restplus import Api, Namespace, Resource

app = Flask(__name__)
blueprint = Blueprint('v0_1', __name__, url_prefix='/v0.1')
api = Api(blueprint)

user_api = Namespace('users', description='User-related operations')

@user_api.route('/')
class Endpoint_UserList(Resource):
    # @user_api.expect(…)
    # @user_api.marshal_with(…)
    def post(self):
        # …
        raise InsecurePasswordError("Password too short")

class BaseError(Exception):
    def __init__(self, status_code, message):
        self.status_code = status_code
        self.message = message

class InsecurePasswordError(BaseError):
    def __init__(self, reason):
        super().__init__(400, "Insecure password: %s " % reason)

# If I replace BaseError with InsecurePasswordError here, everything
# works fine
@api.errorhandler(BaseError)
def my_error_handler(error):
    return { 'message': error.message }, error.status_code

api.add_namespace(user_api)
app.register_blueprint(blueprint)

Output of $ pip list | grep -i flask:

Flask (0.12.2)
flask-restplus (0.10.1)

Temporary workaround: Define error handler for every single subclass of BaseError:

def my_error_handler(error):
    return { 'message': error.message }, error.status_code

for subclass in BaseError.__subclasses__():
    (api.errorhandler(subclass))(my_error_handler)

(Note that this will only work for direct subclasses of BaseError.)

bdmccord commented 6 years ago

I have this same issue, from what I can tell, it appears to have been fixed in December of 2017, however the last release (0.10.1) was in March of 2017.

codethief commented 6 years ago

I see, so it all comes down to issue #418.

Altynai commented 5 years ago

Same issue with 0.12.1.

Basically, the root cause is API.error_handlers is a dict, which means it's not ordered. If you register an error along with its inheritance, you can't tell which comes first if you iterate API.error_handlers.

https://github.com/noirbizarre/flask-restplus/blob/0.12.1/flask_restplus/api.py#L600 indicates only the first matched error type will be processed.