1

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?

Baz
  • 12,713
  • 38
  • 145
  • 268

1 Answers1

1

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.

Danila Ganchar
  • 10,266
  • 13
  • 49
  • 75
  • 1
    Thanks, that's a very nice decorator! I realise now though that there is an issue with this second approach. connexion won't call the endpoint function if it fails to validate the data sent to the endpoint according to the yaml file. The first method I mentioned in my OP will still work though in this case. – Baz Aug 29 '23 at 17:06