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
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.
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 .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.