]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add support for callbacks that take more than a single positional argument.
authorBen Darnell <ben@bendarnell.com>
Sun, 11 Sep 2011 22:50:34 +0000 (15:50 -0700)
committerBen Darnell <ben@bendarnell.com>
Sun, 11 Sep 2011 22:50:34 +0000 (15:50 -0700)
Closes #351.

tornado/gen.py
tornado/test/gen_test.py
website/sphinx/gen.rst

index 900f61a7d035751fdfb532ac4f96e5f98f8647fa..6f6b49aa5b1880f457d8a31856c3424f130e1bca 100644 (file)
@@ -27,10 +27,10 @@ could be written with ``gen`` as::
             do_something_with_response(response)
             self.render("template.html")
 
-`Task` works with any function that takes a ``callback`` keyword argument
-(and runs that callback with zero or one arguments).  You can also yield
-a list of ``Tasks``, which will be started at the same time and run in parallel;
-a list of results will be returned when they are all finished::
+`Task` works with any function that takes a ``callback`` keyword
+argument.  You can also yield a list of ``Tasks``, which will be
+started at the same time and run in parallel; a list of results will
+be returned when they are all finished::
 
     def get(self):
         http_client = AsyncHTTPClient()
@@ -55,9 +55,16 @@ The ``key`` argument to `Callback` and `Wait` allows for multiple
 asynchronous operations to be started at different times and proceed
 in parallel: yield several callbacks with different keys, then wait
 for them once all the async operations have started.
+
+The result of a `Wait` or `Task` yield expression depends on how the callback
+was run.  If it was called with no arguments, the result is ``None``.  If
+it was called with one argument, the result is that argument.  If it was
+called with more than one argument or any keyword arguments, the result
+is an `Arguments` object, which is a named tuple ``(args, kwargs)``.
 """
 
 import functools
+import operator
 import sys
 import types
 
@@ -134,10 +141,7 @@ class Callback(YieldPoint):
         return True
 
     def get_result(self):
-        return self.callback
-
-    def callback(self, arg=None):
-        self.runner.set_result(self.key, arg)
+        return self.runner.result_callback(self.key)
 
 class Wait(YieldPoint):
     """Returns the argument passed to the result of a previous `Callback`."""
@@ -191,14 +195,16 @@ class Task(YieldPoint):
     """
     def __init__(self, func, *args, **kwargs):
         assert "callback" not in kwargs
-        kwargs["callback"] = self.callback
-        self.func = functools.partial(func, *args, **kwargs)
+        self.args = args
+        self.kwargs = kwargs
+        self.func = func
 
     def start(self, runner):
         self.runner = runner
         self.key = object()
         runner.register_callback(self.key)
-        self.func()
+        self.kwargs["callback"] = runner.result_callback(self.key)
+        self.func(*self.args, **self.kwargs)
     
     def is_ready(self):
         return self.runner.is_ready(self.key)
@@ -206,9 +212,6 @@ class Task(YieldPoint):
     def get_result(self):
         return self.runner.pop_result(self.key)
 
-    def callback(self, arg=None):
-        self.runner.set_result(self.key, arg)
-
 class Multi(YieldPoint):
     """Runs multiple asynchronous operations in parallel.
 
@@ -318,3 +321,29 @@ class Runner(object):
         finally:
             self.running = False
 
+    def result_callback(self, key):
+        def inner(*args, **kwargs):
+            if kwargs or len(args) > 1:
+                result = Arguments(args, kwargs)
+            elif args:
+                result = args[0]
+            else:
+                result = None
+            self.set_result(key, result)
+        return inner
+
+# in python 2.6+ this could be a collections.namedtuple
+class Arguments(tuple):
+    """The result of a yield expression whose callback had more than one
+    argument (or keyword arguments).
+
+    The `Arguments` object can be used as a tuple ``(args, kwargs)``
+    or an object with attributes ``args`` and ``kwargs``.
+    """
+    __slots__ = ()
+
+    def __new__(cls, args, kwargs):
+        return tuple.__new__(cls, (args, kwargs))
+
+    args = property(operator.itemgetter(0))
+    kwargs = property(operator.itemgetter(1))
index bdfe97decc731321bb507365a039af8cb68399da..4c6a4d591b00c2feb90800055dbda7dcd40ce241 100644 (file)
@@ -191,6 +191,35 @@ class GenTest(AsyncTestCase):
             self.stop()
         self.run_gen(f)
 
+    def test_arguments(self):
+        @gen.engine
+        def f():
+            (yield gen.Callback("noargs"))()
+            self.assertEqual((yield gen.Wait("noargs")), None)
+            (yield gen.Callback("1arg"))(42)
+            self.assertEqual((yield gen.Wait("1arg")), 42)
+
+            (yield gen.Callback("kwargs"))(value=42)
+            result = yield gen.Wait("kwargs")
+            self.assertTrue(isinstance(result, gen.Arguments))
+            self.assertEqual(((), dict(value=42)), result)
+            self.assertEqual(dict(value=42), result.kwargs)
+
+            (yield gen.Callback("2args"))(42, 43)
+            result = yield gen.Wait("2args")
+            self.assertTrue(isinstance(result, gen.Arguments))
+            self.assertEqual(((42, 43), {}), result)
+            self.assertEqual((42, 43), result.args)
+
+            def task_func(callback):
+                callback(None, error="foo")
+            result = yield gen.Task(task_func)
+            self.assertTrue(isinstance(result, gen.Arguments))
+            self.assertEqual(((None,), dict(error="foo")), result)
+
+            self.stop()
+        self.run_gen(f)
+
 
 class GenSequenceHandler(RequestHandler):
     @asynchronous
index 2cd322790d16a5d80565966ec83ff4bea31aa442..4281eb358af9405995c882ed83b693ced8718763 100644 (file)
@@ -21,3 +21,8 @@
    .. autoclass:: Wait
 
    .. autoclass:: WaitAll
+
+   Other classes
+   -------------
+
+   .. autoclass:: Arguments