Reputation: 1226
I have a class with plenty static methods with Tornado coroutine decorator. And I want to add another decorator, to catch exceptions and write them to a file:
# my decorator
def lifesaver(func):
def silenceit(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as ex:
# collect info and format it
res = ' ... '
# writelog(res)
print(res)
return None
return silenceit
However, it doesn't work with gen.coroutine
decorator:
class SomeClass:
# This doesn't work!
# I tried to pass decorators in different orders,
# but got no result.
@staticmethod
@lifesaver
@gen.coroutine
@lifesaver
def dosomething1():
raise Exception("Test error!")
# My decorator works well
# if it is used without gen.coroutine.
@staticmethod
@gen.coroutine
def dosomething2():
SomeClass.dosomething3()
@staticmethod
@lifesaver
def dosomething3():
raise Exception("Test error!")
I understand, that Tornado uses raise Return(...)
approach which is probably based on Exceptions, and maybe it somehow blocks try-catches of other decorators... So, how can I used my decorator to handle Exceptions with Tornado coroutines?
Thanks to Martijn Pieters, I got this code working:
def lifesaver(func):
def silenceit(*args, **kwargs):
try:
return func(*args, **kwargs)
except (gen.Return, StopIteration):
raise
except Exception as ex:
# collect info and format it
res = ' ... '
# writelog(res)
print(res)
raise gen.Return(b"")
return silenceit
So, I only needed to specify Tornado Return
. I tried to add @gen.coroutine
decorator to silenceit
function and use yield
in it, but this leads to Future
objects of Future
objects and some other strange unpredictable behaviour.
Upvotes: 1
Views: 734
Reputation: 1124110
You are decorating the output of gen.coroutine
, because decorators are applied from bottom to top (as they are nested inside one another from top to bottom).
Rather than decorate the coroutine, decorate your function and apply the gen.coroutine
decorator to that result:
@gen.coroutine
@lifesaver
def dosomething1():
raise Exception("Test error!")
Your decorator can't really handle the output that a @gen.coroutine
decorated function produces. Tornado relies on exceptions to communicate results (because in Python 2, generators can't use return
to return results). You need to make sure you pass through the exceptions Tornado relies on. You also should re-wrap your wrapper function:
from tornado import gen
def lifesaver(func):
@gen.coroutine
def silenceit(*args, **kwargs):
try:
return func(*args, **kwargs)
except (gen.Return, StopIteration):
raise
except Exception as ex:
# collect info and format it
res = ' ... '
# writelog(res)
print(res)
raise gen.Return(b"")
return silenceit
On exception, an empty Return()
object is raised; adjust this as needed.
Do yourself a favour and don't use a class just put staticmethod
functions in there. Just put those functions at the top level in the module. Classes are there to combine methods and shared state, not to create a namespace. Use modules to create namespaces instead.
Upvotes: 2