Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flask hit decorator before before_request signal fires

I'm using Flask and using the before_request decorator to send information about requests to an analytics system. I'm now trying to create a decorator that would prevent sending these events on a few routes.

The problem I'm running into is getting my decorator to get called before the before_request signal gets fired.

def exclude_from_analytics(func):

    @wraps(func)
    def wrapped(*args, **kwargs):
        print "Before decorated function"
        return func(*args, exclude_from_analytics=True, **kwargs)

    return wrapped

# ------------------------

@exclude_from_analytics
@app.route('/')
def index():
    return make_response('..')

# ------------------------

@app.before_request
def analytics_view(*args, **kwargs):
    if 'exclude_from_analytics' in kwargs and kwargs['exclude_from_analytics'] is True:
       return
like image 290
NFicano Avatar asked Oct 24 '13 19:10

NFicano


People also ask

How do you use before request in Flask?

To run your code before each Flask request, you can assign a function to the before_request() method, this can be done using decorators to make it a little simpler. This function will run before each and every endpoint you have in your application.

How do decorators work in Flask?

The decorators check to make sure a user is authenticated to use a resource before the actual code of that resource executes. The authenticate function will run first. And then if the user is authenticated, it will call hello to execute the rest, otherwise, it will send an error back to the user.

What is the purpose of a function decorator for Flask?

A decorator is a function that wraps and replaces another function. Since the original function is replaced, you need to remember to copy the original function's information to the new function. Use functools. wraps() to handle this for you.

What is the use of G in Flask?

g is an object for storing data during the application context of a running Flask web app. g can also be imported directly from the flask module instead of flask. globals , so you will often see that shortcut in example code.


2 Answers

You can use the decorator to simply put an attribute on the function (in my example below, I'm using _exclude_from_analytics as the attribute). I find the view function using a combination of request.endpoint and app.view_functions.

If the attribute is not found on the endpoint, you can ignore analytics.

from flask import Flask, request

app = Flask(__name__)

def exclude_from_analytics(func):
    func._exclude_from_analytics = True
    return func

@app.route('/a')
@exclude_from_analytics
def a():
    return 'a'

@app.route('/b')
def b():
    return 'b'

@app.before_request
def analytics_view(*args, **kwargs):
    # Default this to whatever you'd like.
    run_analytics = True

    # You can handle 404s differently here if you'd like.
    if request.endpoint in app.view_functions:
        view_func = app.view_functions[request.endpoint]
        run_analytics = not hasattr(view_func, '_exclude_from_analytics')

    print 'Should run analytics on {0}: {1}'.format(request.path, run_analytics)

app.run(debug=True)

The output (ignoring static files...)

Should run analytics on /a: False
127.0.0.1 - - [24/Oct/2013 15:55:15] "GET /a HTTP/1.1" 200 -
Should run analytics on /b: True
127.0.0.1 - - [24/Oct/2013 15:55:18] "GET /b HTTP/1.1" 200 -

I have not tested to see if this works with blueprints. Additionally, a decorator that wraps and returns a NEW function could cause this to not work since the attribute might be hidden.

like image 102
Mark Hildreth Avatar answered Sep 21 '22 09:09

Mark Hildreth


Here's a variation on @Mark Hildreth's answer that does wrap and return a function:

from functools import wraps
from flask import Flask, request, g

app = Flask(__name__)

def exclude_from_analytics(*args, **kw):
    def wrapper(endpoint_method):
        endpoint_method._skip_analytics = True

        @wraps(endpoint_method)
        def wrapped(*endpoint_args, **endpoint_kw):
            # This is what I want I want to do. Will not work.
            #g.skip_analytics = getattr(endpoint_method, '_skip_analytics', False)
            return endpoint_method(*endpoint_args, **endpoint_kw)
        return wrapped
    return wrapper

@app.route('/')
def no_skip():
    return 'Skip analytics? %s' % (g.skip_analytics)

@app.route('/skip')
@exclude_from_analytics()
def skip():
    return 'Skip analytics? %s' % (g.skip_analytics)

@app.before_request
def analytics_view(*args, **kwargs):
    if request.endpoint in app.view_functions:
        view_func = app.view_functions[request.endpoint]
        g.skip_analytics = hasattr(view_func, '_skip_analytics')
        print 'Should skip analytics on {0}: {1}'.format(request.path, g.skip_analytics)

app.run(debug=True)

The reason why it does not work quite as simply as I expected and hoped has to something do with the Flask context stack and the order in which callbacks are applied. Here is a timeline of method calls (based on some debug statements since removed):

$ python test-flask-app.py
# Application Launched
DECORATOR exclude_from_analytics
DECORATOR wrapper
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

# REQUEST: /
DECORATOR app.before_request: analytics_view
> Should skip analytics on /: False
ENDPOINT no_skip
127.0.0.1 - - [14/May/2016 16:10:39] "GET / HTTP/1.1" 200 -

# REQUEST: /skip
DECORATOR app.before_request: analytics_view
> Should skip analytics on /skip: True
DECORATOR wrapped
ENDPOINT skip
127.0.0.1 - - [14/May/2016 16:12:46] "GET /skip HTTP/1.1" 200 -

I would prefer to set g.skip_analytics from within the wrapped function. But because that is not called until after the analytics_view @app.before_request hook, I had to follow Mark's example and set the _skip_analytics attr on the endpoint method loaded in what I'm calling the application (as opposed to request) context which gets invoked only at launch.

For more on flask.g and app context, see this StackOverflow answer.

like image 23
klenwell Avatar answered Sep 18 '22 09:09

klenwell