xjq233p_1
xjq233p_1

Reputation: 8070

Custom decorator in flask not working?

I have the following code:

import datetime
from flask.app import Flask

app = Flask(__name__)
app.config.from_object(__name__)
app.debug = True

def track_time_spent(name):
  def decorator(f):
    def wrapped(*args, **kwargs):
      start = datetime.datetime.now()
      ret = f(*args, **kwargs)
      delta = datetime.datetime.now() - start
      print name, "took", delta.total_seconds(), "seconds"
      return ret
    return wrapped
  return decorator

@app.route('/foo')
@track_time_spent('foo')
def foo():
  print "foo"
  return "foo"

@app.route('/bar')
@track_time_spent('bar')
def bar():
  print "bar"
  return "bar"

I am unable to get foo to return 'foo':

$ curl localhost:8888/foo
bar

(flask window) 
bar
bar took 8.2e-05 seconds
127.0.0.1 - - [18/Apr/2013 19:21:31] "GET /foo HTTP/1.1" 200 -

$ curl localhost:8888/bar
bar

(flask window)
bar
bar took 3.5e-05 seconds
127.0.0.1 - - [18/Apr/2013 19:21:35] "GET /bar HTTP/1.1" 200 -

What's going on? Why isn't my decorator working?

EDIT

I don't think you guys seem to be able to see the problem.

When I have @app.route before @track_time_spent, both methods return bar. The error here is that calling localhost:8888/foo results in bar in both the http response as well as the print function.

Upvotes: 8

Views: 11495

Answers (3)

TkTech
TkTech

Reputation: 4996

The other answers seem to be missing that you're getting "bar" as a response from "/foo" when you switch the order of the decorators. You must use @wraps here unless you update the __name__, __module__, and such manually. Flask uses your methods kinda like singletons, and sees your decorator just as the wrapped() method, instead of the methods you actually wrapped. Hence, your routes will keep getting overwritten by the last method to use your decorator.

import datetime
from functools import wraps

from flask.app import Flask

app = Flask(__name__)
app.config.from_object(__name__)
app.debug = True


def track_time_spent(name):
    def decorator(f):
        @wraps(f)
        def wrapped(*args, **kwargs):
            start = datetime.datetime.now()
            ret = f(*args, **kwargs)
            delta = datetime.datetime.now() - start
            print name, "took", delta.total_seconds(), "seconds"
            return ret
        return wrapped
    return decorator


@app.route('/foo')
@track_time_spent('foo')
def foo():
    print "foo"
    return "foo"


@app.route('/bar')
@track_time_spent('bar')
def bar():
    print "bar"
    return "bar"

app.run(host='0.0.0.0', port=8888)

Upvotes: 21

tehasdf
tehasdf

Reputation: 142

Flask's route function is a registering function in the sense you call it for its side effect - it'll register your view function for an endpoint. However, you're registering just the view function, not the decorated view function. Simply switch the order of decorators to register the "time-tracked" function.

Also, you can use @app.before_request and @app.teardown_request-registered functions to track time more reliably (taking into account the time it took to render the template and such).

Upvotes: 3

Thomas Orozco
Thomas Orozco

Reputation: 55303

Why are you saying your decorator isn't working?

In your second example, the decorator is running, and it's realistic that such a simple function would execute in 0.035 ms (3.5e-05 is a notation that means 3.5 times 10 to the power of -5).


For reference, the reason why you needed to reverse the order of the two decorator is because app.route registers the function that it is being passed.

Hence you need to pass it the decorated function, hence the order.

Upvotes: 0

Related Questions