Skip to content

Commit

Permalink
Fix gen.engine docs on decorator order.
Browse files Browse the repository at this point in the history
Add some more test cases that use gen.engine and web.asynchronous together.
  • Loading branch information
bdarnell committed Jan 23, 2012
1 parent 463baf4 commit 7fd1788
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 4 deletions.
10 changes: 6 additions & 4 deletions tornado/gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,12 @@ def engine(func):
Any generator that yields objects from this module must be wrapped
in this decorator. The decorator only works on functions that are
already asynchronous. For `~tornado.web.RequestHandler`
``get``/``post``/etc methods, this means that both the `tornado.gen.engine`
and `tornado.web.asynchronous` decorators must be used (in either order).
In most other cases, it means that it doesn't make sense to use
``gen.engine`` on functions that don't already take a callback argument.
``get``/``post``/etc methods, this means that both the
`tornado.web.asynchronous` and `tornado.gen.engine` decorators
must be used (for proper exception handling, ``asynchronous``
should come before ``gen.engine``). In most other cases, it means
that it doesn't make sense to use ``gen.engine`` on functions that
don't already take a callback argument.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
Expand Down
35 changes: 35 additions & 0 deletions tornado/test/gen_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def f():
self.run_gen(f)

def test_exception_in_task_phase2(self):
# This is the case that requires the use of stack_context in gen.engine
def fail_task(callback):
self.io_loop.add_callback(lambda: 1/0)

Expand Down Expand Up @@ -273,11 +274,36 @@ def get(self):
response.rethrow()
self.finish(b("got response: ") + response.body)

class GenExceptionHandler(RequestHandler):
@asynchronous
@gen.engine
def get(self):
# This test depends on the order of the two decorators.
io_loop = self.request.connection.stream.io_loop
yield gen.Task(io_loop.add_callback)
raise Exception("oops")

class GenYieldExceptionHandler(RequestHandler):
@asynchronous
@gen.engine
def get(self):
io_loop = self.request.connection.stream.io_loop
# Test the interaction of the two stack_contexts.
def fail_task(callback):
io_loop.add_callback(lambda: 1/0)
try:
yield gen.Task(fail_task)
raise Exception("did not get expected exception")
except ZeroDivisionError:
self.finish('ok')

class GenWebTest(AsyncHTTPTestCase, LogTrapTestCase):
def get_app(self):
return Application([
('/sequence', GenSequenceHandler),
('/task', GenTaskHandler),
('/exception', GenExceptionHandler),
('/yield_exception', GenYieldExceptionHandler),
])

def test_sequence_handler(self):
Expand All @@ -287,3 +313,12 @@ def test_sequence_handler(self):
def test_task_handler(self):
response = self.fetch('/task?url=%s' % url_escape(self.get_url('/sequence')))
self.assertEqual(response.body, b("got response: 123"))

def test_exception_handler(self):
# Make sure we get an error and not a timeout
response = self.fetch('/exception')
self.assertEqual(500, response.code)

def test_yield_exception_handler(self):
response = self.fetch('/yield_exception')
self.assertEqual(response.body, b('ok'))

0 comments on commit 7fd1788

Please sign in to comment.