I'm trying to get my head around Tornado. I'm writing a chat application backed by mongodb and I'm using motor
for non-blocking access to it.
What I'm trying to achieve is:
motor
to asynchronously pull the user's record from mongomotor
to retrieve the 'ChatRoom' record.I have decorator 1. working (basically taken from http://tornadogists.org/5251927/):
def authenticated_async(f):
@functools.wraps(f)
@gen.engine
def wrapper(self, *args, **kwargs):
self.current_user = yield gen.Task(self.get_current_user_async)
if self.current_user:
logging.info("User '%s' (%s) successfully authenticated" %
(self.current_user['username'],
self.current_user['_id']))
f(self, *args, **kwargs)
else:
raise tornado.web.HTTPError(401, "User not authenticated, "
"aborting")
return wrapper
The trouble is that for the second decorator, I need to access self.current_user
. Because this is set in an asynchronous callback, it's not available when I get into my validation
decorator (i.e the validation decorator is called before the auth decorator completes).
Is it just not possible for me to use decorators in this way with asynchronous functions? Do I just need to call the validation method inside the above method after making sure that self.current_user
is True so it's more like a callback?
I'd ideally like to have my methods in my Handler wrapped with both of these decorators so I can reuse them elsewhere, i.e.:
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
@gen.coroutine
@validate_invitation_access_async
@authenticated_async
def open(self, invitation_id):
# do stuff here...
Update
In fact, there is no dependency. user_id is provided as a parameter, and that could be used to run both decorators in parallel - one to confirm authentication, the other to see whether the user with that ID is allowed access to the room. The open()
method would then only proceed if self.auth_check == True and self.room_check == True
.
Could open()
ever be called before the async decorators complete though?
You need to switch the order of the decorators so your validate_invitation_access_async
wrapper has access to current_user:
@authenticated_async
@validate_invitation_access_async
def open(self, invitation_id):
# do stuff here...
Then the wrapper inside validate_invitation_access_async
is the "f" in authenticated_async
: it's called after self.current_user is set. Note that you don't need an additional gen.engine
decorator, all the wrappers are already engines. Your wrapper could be like:
def validate_invitation_access_async(f):
@gen.engine
def wrapper(self, *args, **kwargs):
# ... use Motor to see if user is authorized ...
# if not, log and redirect. otherwise:
f(self, *args, **kwargs)
You should also update your code to use Tornado 3's gen.coroutine instead of gen.engine: it's much cleaner. But I leave that as an exercise.