Search code examples
pythonflaskconnexion

Different error handling for different endpoints


I have error handlers in my app. I add these using connexion's add_error_handler which is calling flask's register_error_handler. I wish to restructure the error data that is returned by my endpoints in this handler. However, since I have so many unit tests reliant on the old structure, I wish to implement the new error structure for a subset of endpoints first. I believe I can do this as follows:

from flask import request

new_endpoints = ("/new_endpoint",)

def is_new_endpoint():
    return request.path in new_endpoints


def my_error_handler(e):
    if is_new_endpoint():
        return FlaskApi.response(new_error_response(e))
    else:
        return FlaskApi.response(old_error_response(e))

Is there another approach to doing this? The problem I have is that I believe that the is_new_endpoint function might get messy.

I define my endpoints in a yaml file, and for each endpoint, I have an operationId which specifies the python function associated with the endpoint. Maybe I could decorate these functions to define them as new and have this information available in the error handler. Could this be a possible alternative approach? Could I use flask.g for this?


Solution

  • Not sure if this is what you are looking for. But as far as I understood the main idea was to avoid describing routes paths in the application config(new_endpoints = ...).

    Yes. Looks like you can use flask.g + custom decorator. Here is an example:

    from functools import wraps
    from flask import Flask, request, g
    
    
    app = Flask(__name__)
    
    
    def new_endpoint_decorator():
        def _new_endpoint(f):
            @wraps(f)
            def __new_endpoint(*args, **kwargs):
                # register request path
                if not hasattr(g, 'new_endpoints'):
                    g.new_endpoints = set()
                g.new_endpoints.add(request.path)
                return f(*args, **kwargs)
            return __new_endpoint
        return _new_endpoint
    
    
    @app.route('/old_endpoint')
    def old_endpoint():
        raise ValueError('old_endpoint')
    
    
    @app.route('/new_endpoint')
    @new_endpoint_decorator()  # endpoint will use a new error format
    def new_endpoint():
        raise ValueError('new_endpoint')
    
    
    @app.errorhandler(ValueError)
    def error_handler(error):
        # checking flask.g on new_endpoints
        if hasattr(g, 'new_endpoints') and request.path in g.new_endpoints:
            return f'new error: {error}'
        return f'legacy error: {error}'
    
    
    if __name__ == '__main__':
        app.run('localhost', debug=True)
    

    Let's check:

    curl http://localhost:5000/new_endpoint  # new error: new_endpoint
    curl http://localhost:5000/old_endpoint  # legacy error: old_endpoint
    

    It's probably best to replace @new_endpoint_decorator() to @legacy_response() because old routes usually don't change. So you can just add decorator to all legacy routes 1 time and forget it.