Skip to content

Commit 5872db2

Browse files
committed
Add support for callbacks that take more than a single positional argument.
Closes tornadoweb#351.
1 parent 94b483e commit 5872db2

File tree

3 files changed

+77
-14
lines changed

3 files changed

+77
-14
lines changed

tornado/gen.py

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ def get(self):
2727
do_something_with_response(response)
2828
self.render("template.html")
2929
30-
`Task` works with any function that takes a ``callback`` keyword argument
31-
(and runs that callback with zero or one arguments). You can also yield
32-
a list of ``Tasks``, which will be started at the same time and run in parallel;
33-
a list of results will be returned when they are all finished::
30+
`Task` works with any function that takes a ``callback`` keyword
31+
argument. You can also yield a list of ``Tasks``, which will be
32+
started at the same time and run in parallel; a list of results will
33+
be returned when they are all finished::
3434
3535
def get(self):
3636
http_client = AsyncHTTPClient()
@@ -55,9 +55,16 @@ def get(self):
5555
asynchronous operations to be started at different times and proceed
5656
in parallel: yield several callbacks with different keys, then wait
5757
for them once all the async operations have started.
58+
59+
The result of a `Wait` or `Task` yield expression depends on how the callback
60+
was run. If it was called with no arguments, the result is ``None``. If
61+
it was called with one argument, the result is that argument. If it was
62+
called with more than one argument or any keyword arguments, the result
63+
is an `Arguments` object, which is a named tuple ``(args, kwargs)``.
5864
"""
5965

6066
import functools
67+
import operator
6168
import sys
6269
import types
6370

@@ -134,10 +141,7 @@ def is_ready(self):
134141
return True
135142

136143
def get_result(self):
137-
return self.callback
138-
139-
def callback(self, arg=None):
140-
self.runner.set_result(self.key, arg)
144+
return self.runner.result_callback(self.key)
141145

142146
class Wait(YieldPoint):
143147
"""Returns the argument passed to the result of a previous `Callback`."""
@@ -191,24 +195,23 @@ class Task(YieldPoint):
191195
"""
192196
def __init__(self, func, *args, **kwargs):
193197
assert "callback" not in kwargs
194-
kwargs["callback"] = self.callback
195-
self.func = functools.partial(func, *args, **kwargs)
198+
self.args = args
199+
self.kwargs = kwargs
200+
self.func = func
196201

197202
def start(self, runner):
198203
self.runner = runner
199204
self.key = object()
200205
runner.register_callback(self.key)
201-
self.func()
206+
self.kwargs["callback"] = runner.result_callback(self.key)
207+
self.func(*self.args, **self.kwargs)
202208

203209
def is_ready(self):
204210
return self.runner.is_ready(self.key)
205211

206212
def get_result(self):
207213
return self.runner.pop_result(self.key)
208214

209-
def callback(self, arg=None):
210-
self.runner.set_result(self.key, arg)
211-
212215
class Multi(YieldPoint):
213216
"""Runs multiple asynchronous operations in parallel.
214217
@@ -318,3 +321,29 @@ def run(self):
318321
finally:
319322
self.running = False
320323

324+
def result_callback(self, key):
325+
def inner(*args, **kwargs):
326+
if kwargs or len(args) > 1:
327+
result = Arguments(args, kwargs)
328+
elif args:
329+
result = args[0]
330+
else:
331+
result = None
332+
self.set_result(key, result)
333+
return inner
334+
335+
# in python 2.6+ this could be a collections.namedtuple
336+
class Arguments(tuple):
337+
"""The result of a yield expression whose callback had more than one
338+
argument (or keyword arguments).
339+
340+
The `Arguments` object can be used as a tuple ``(args, kwargs)``
341+
or an object with attributes ``args`` and ``kwargs``.
342+
"""
343+
__slots__ = ()
344+
345+
def __new__(cls, args, kwargs):
346+
return tuple.__new__(cls, (args, kwargs))
347+
348+
args = property(operator.itemgetter(0))
349+
kwargs = property(operator.itemgetter(1))

tornado/test/gen_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,35 @@ def f():
191191
self.stop()
192192
self.run_gen(f)
193193

194+
def test_arguments(self):
195+
@gen.engine
196+
def f():
197+
(yield gen.Callback("noargs"))()
198+
self.assertEqual((yield gen.Wait("noargs")), None)
199+
(yield gen.Callback("1arg"))(42)
200+
self.assertEqual((yield gen.Wait("1arg")), 42)
201+
202+
(yield gen.Callback("kwargs"))(value=42)
203+
result = yield gen.Wait("kwargs")
204+
self.assertTrue(isinstance(result, gen.Arguments))
205+
self.assertEqual(((), dict(value=42)), result)
206+
self.assertEqual(dict(value=42), result.kwargs)
207+
208+
(yield gen.Callback("2args"))(42, 43)
209+
result = yield gen.Wait("2args")
210+
self.assertTrue(isinstance(result, gen.Arguments))
211+
self.assertEqual(((42, 43), {}), result)
212+
self.assertEqual((42, 43), result.args)
213+
214+
def task_func(callback):
215+
callback(None, error="foo")
216+
result = yield gen.Task(task_func)
217+
self.assertTrue(isinstance(result, gen.Arguments))
218+
self.assertEqual(((None,), dict(error="foo")), result)
219+
220+
self.stop()
221+
self.run_gen(f)
222+
194223

195224
class GenSequenceHandler(RequestHandler):
196225
@asynchronous

website/sphinx/gen.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@
2121
.. autoclass:: Wait
2222

2323
.. autoclass:: WaitAll
24+
25+
Other classes
26+
-------------
27+
28+
.. autoclass:: Arguments

0 commit comments

Comments
 (0)