From: Ben Darnell Date: Mon, 3 Aug 2015 02:03:20 +0000 (-0400) Subject: Implement async iteration in WaitIterator. X-Git-Tag: v4.3.0b1~63^2~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f90afbed19f51c79baff491f7b5b51ae44a1ce95;p=thirdparty%2Ftornado.git Implement async iteration in WaitIterator. --- diff --git a/tornado/gen.py b/tornado/gen.py index 9d4a59dc5..c11c1bd9b 100644 --- a/tornado/gen.py +++ b/tornado/gen.py @@ -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. diff --git a/tornado/test/gen_test.py b/tornado/test/gen_test.py index ecb972bec..d37e09365 100644 --- a/tornado/test/gen_test.py +++ b/tornado/test/gen_test.py @@ -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