Search code examples
pythonflaskwerkzeug

Flask app.add_url_rule in decorator error


I have a bunch of decorators in my Flask routes that I am trying to condense into one (including @app.route).

I have the following @route function:

from functools import wraps
def route(route, method):
    def decorator(f):
        print 'decorator defined'
        print 'defining route'
        app.add_url_rule(route, methods=method, view_func=f)
        print 'route defined'
        @wraps(f)
        def wrapper(*args, **kwargs):
            print 'Hello'
            # do stuff here such as authenticate, authorise, check request json/arguments etc.
            # these will get passed along with the route and method arguments above.
            return f(*args, **kwargs)
        return wrapper
    return decorator

and a sample route:

@route('/status', ['GET'])
def status():
    return Response('hi', content_type='text/plain')

The route is getting defined, but wrapper() never gets called, which is really odd. When I move app.add_url_rule outside of the decorator to the end of the file, then wrapper() gets called; so decorator defined statement prints on Flask startup, and Hello prints when I hit the GET /status route as expected.

However, when I put app.add_url_rule back into the decorator as shown above, decorator defined prints on startup but when I call GET /status, it does not print Hello as if app.add_url_rule overrides the wrapper() function that I defined somehow.

Why does this happen? It looks like app.add_url_route hijacks my function in some odd/unexpected way.

How can I get wrapper() to be called once the route is hit, while defining app.add_url_rule in the decorator?


Solution

  • You registered the original function, not the wrapper, with Flask. Whenever the route matches, Flask calls f, not wrapper, because that's what you registered for the route.

    Tell Flask to call wrapper when the route matches, instead:

    def route(route, method):
        def decorator(f):
            print 'decorator defined'
            print 'defining route'
            @wraps(f)
            def wrapper(*args, **kwargs):
                print 'Hello'
                # do stuff here such as authenticate, authorise, check request json/arguments etc.
                # these will get passed along with the route and method arguments above.
                return f(*args, **kwargs)
    
            app.add_url_rule(route, methods=method, view_func=wrapper)
            print 'route defined'
            return wrapper
        return decorator