]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Implement async iteration in WaitIterator.
authorBen Darnell <ben@bendarnell.com>
Mon, 3 Aug 2015 02:03:20 +0000 (22:03 -0400)
committerBen Darnell <ben@bendarnell.com>
Mon, 3 Aug 2015 02:03:20 +0000 (22:03 -0400)
tornado/gen.py
tornado/test/gen_test.py

index 9d4a59dc5010e5e8212337f3d03a0aecc366ff43..c11c1bd9bac398b1e5a3dc8f60c6123f102fa591 100644 (file)
@@ -333,7 +333,19 @@ class WaitIterator(object):
     arguments were used in the construction of the `WaitIterator`,
     ``current_index`` will use the corresponding keyword).
 
+    On Python 3.5, `WaitIterator` implements the async iterator
+    protocol, so it can be used with the ``async for`` statement (note
+    that in this version the entire iteration is aborted if any value
+    raises an exception, while the previous example can continue past
+    individual errors)::
+
+      async for result in gen.WaitIterator(future1, future2):
+          print("Result {} received from {} at {}".format(
+              result, wait_iterator.current_future,
+              wait_iterator.current_index))
+
     .. versionadded:: 4.1
+
     """
     def __init__(self, *args, **kwargs):
         if args and kwargs:
@@ -390,6 +402,16 @@ class WaitIterator(object):
         self.current_future = done
         self.current_index = self._unfinished.pop(done)
 
+    @coroutine
+    def __aiter__(self):
+        raise Return(self)
+
+    def __anext__(self):
+        if self.done():
+            # Lookup by name to silence pyflakes on older versions.
+            raise globals()['StopAsyncIteration']()
+        return self.next()
+
 
 class YieldPoint(object):
     """Base class for objects that may be yielded from the generator.
index ecb972bec89199bd835dce109a76e8e51d10b2c0..d37e093651556e5f6343cbf945d79b31b0e33625 100644 (file)
@@ -1285,6 +1285,46 @@ class WaitIteratorTest(AsyncTestCase):
                     self.assertEqual(g.current_index, 3, 'wrong index')
             i += 1
 
+    @skipBefore35
+    @gen_test
+    def test_iterator_async_await(self):
+        # Recreate the previous test with py35 syntax. It's a little clunky
+        # because of the way the previous test handles an exception on
+        # a single iteration.
+        futures = [Future(), Future(), Future(), Future()]
+        self.finish_coroutines(0, futures)
+        self.finished = False
+
+        namespace = exec_test(globals(), locals(), """
+        async def f():
+            i = 0
+            g = gen.WaitIterator(*futures)
+            try:
+                async for r in g:
+                    if i == 0:
+                        self.assertEqual(r, 24, 'iterator value incorrect')
+                        self.assertEqual(g.current_index, 2, 'wrong index')
+                    else:
+                        raise Exception("expected exception on iteration 1")
+                    i += 1
+            except ZeroDivisionError:
+                i += 1
+            async for r in g:
+                if i == 2:
+                    self.assertEqual(r, 42, 'iterator value incorrect')
+                    self.assertEqual(g.current_index, 1, 'wrong index')
+                elif i == 3:
+                    self.assertEqual(r, 84, 'iterator value incorrect')
+                    self.assertEqual(g.current_index, 3, 'wrong index')
+                else:
+                    raise Exception("didn't expect iteration %d" % i)
+                i += 1
+            self.finished = True
+        """)
+        yield namespace['f']()
+        self.assertTrue(self.finished)
+
+
     @gen_test
     def test_no_ref(self):
         # In this usage, there is no direct hard reference to the