0

I have a tornado method like below and I tried to decorate method to cache stuff. I have the following setup

def request_cacher(x):
    def wrapper(funca):
        @functools.wraps(funca)
        @asynchronous
        @coroutine
        def wrapped_f(self, *args, **kwargs):
            pass

        return wrapped_f
    return wrapper

class PhotoListHandler(BaseHandler):
    @request_cacher
    @auth_required
    @asynchronous
    @coroutine
    def get(self):
        pass

I am receiving the error, AttributeError: 'PhotoListHandler' object has no attribute '__name__' Any ideas?

tuna
  • 6,211
  • 11
  • 43
  • 63
  • You do realise that you are applying the `@asynchronous` and `@coroutine` decorators twice? – holdenweb Aug 02 '16 at 11:23
  • yes, i do. this cache callable also have to work asynchronously – tuna Aug 02 '16 at 11:27
  • Please post the full traceback, not just the exception. – A. Jesse Jiryu Davis Aug 02 '16 at 12:09
  • This is the only trackeback @A.JesseJiryuDavis `File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.py", line 33, in update_wrapper setattr(wrapper, attr, getattr(wrapped, attr)) AttributeError: 'PhotoListHandler' object has no attribute '__name__'` – tuna Aug 02 '16 at 12:13

3 Answers3

5

The issue is that you defined your request_cacher decorator as a decorator with arguments but you forgot to pass the argument!

Consider this code:

import functools


def my_decorator_with_argument(useless_and_wrong):
    def wrapper(func):
        @functools.wraps(func)
        def wrapped(self):
            print('wrapped!')
        return wrapped
    return wrapper


class MyClass(object):
    @my_decorator_with_argument
    def method(self):
        print('method')


    @my_decorator_with_argument(None)
    def method2(self):
       print('method2')

When you try to use method in an instance you get:

>>> inst = MyClass()
>>> inst.method    # should be the wrapped function, not wrapper!
<bound method MyClass.wrapper of <bad_decorator.MyClass object at 0x7fed32dc6f50>>
>>> inst.method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "bad_decorator.py", line 6, in wrapper
    @functools.wraps(func)
  File "/usr/lib/python2.7/functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'MyClass' object has no attribute '__name__'

With the correct usage of the decorator:

>>> inst.method2()
wrapped!

Alternative fix is remove one layer from the decorator:

def my_simpler_decorator(func):
    @functools.wraps(func)
    def wrapped(self):
        print('wrapped!')
    return wrapped


class MyClass(object):

    @my_simpler_decorator
    def method3(self):
        print('method3')

And you can see that it does not raise the error:

>>> inst = MyClass()
>>> inst.method3()
wrapped!
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
0

i think this might work for you,

import tornado.ioloop
import tornado.web
from tornado.gen import coroutine
from functools import wraps


cache = {}

class cached(object):
    def __init__ (self, rule, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        self.rule = rule
        cache[rule] = 'xxx'

    def __call__(self, fn):
        def newf(*args, **kwargs):
            slf = args[0]
            if cache.get(self.rule):
                slf.write(cache.get(self.rule))
                return
            return fn(*args, **kwargs)
        return newf 


class MainHandler(tornado.web.RequestHandler):

    @coroutine
    @cached('/foo')
    def get(self):
        print "helloo"
        self.write("Hello, world")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()
ybrs
  • 24
  • 1
  • 1
0

Your code throws from functools.wraps(funca), so funca must be a PhotoListHandler instance instead of a wrapped get method as you intend. I believe this means that the next decorator down the stack, auth_required, is written incorrectly: auth_required is returning self instead of returning a function.

While I'm here: stacking a cache on top of an authenticated function looks wrong to me. Won't the first authenticated user's photo list be cached and then shown to all subsequent users?

A. Jesse Jiryu Davis
  • 23,641
  • 4
  • 57
  • 70