Skip to content

Commit 22214ab

Browse files
author
Yury Selivanov
committed
Issue #28720: Add collections.abc.AsyncGenerator.
1 parent 41782e4 commit 22214ab

5 files changed

Lines changed: 155 additions & 2 deletions

File tree

Doc/library/collections.abc.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ ABC Inherits from Abstract Methods Mixin
9292
:class:`Coroutine` :class:`Awaitable` ``send``, ``throw`` ``close``
9393
:class:`AsyncIterable` ``__aiter__``
9494
:class:`AsyncIterator` :class:`AsyncIterable` ``__anext__`` ``__aiter__``
95+
:class:`AsyncGenerator` :class:`AsyncIterator` ``asend``, ``athrow`` ``aclose``, ``__aiter__``, ``__anext__``
9596
========================== ====================== ======================= ====================================================
9697

9798

@@ -222,6 +223,13 @@ ABC Inherits from Abstract Methods Mixin
222223

223224
.. versionadded:: 3.5
224225

226+
.. class:: Generator
227+
228+
ABC for asynchronous generator classes that implement the protocol
229+
defined in :pep:`525` and :pep:`492`.
230+
231+
.. versionadded:: 3.6
232+
225233

226234
These ABCs allow us to ask classes or instances if they provide
227235
particular functionality, for example::

Doc/whatsnew/3.6.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,10 @@ The new :class:`~collections.abc.Reversible` abstract base class represents
912912
iterable classes that also provide the :meth:`__reversed__`.
913913
(Contributed by Ivan Levkivskyi in :issue:`25987`.)
914914

915+
The new :class:`~collections.abc.AsyncGenerator` abstract base class represents
916+
asynchronous generators.
917+
(Contributed by Yury Selivanov in :issue:`28720`.)
918+
915919
The :func:`~collections.namedtuple` function now accepts an optional
916920
keyword argument *module*, which, when specified, is used for
917921
the ``__module__`` attribute of the returned named tuple class.

Lib/_collections_abc.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from abc import ABCMeta, abstractmethod
1010
import sys
1111

12-
__all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator",
12+
__all__ = ["Awaitable", "Coroutine",
13+
"AsyncIterable", "AsyncIterator", "AsyncGenerator",
1314
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
1415
"Sized", "Container", "Callable", "Collection",
1516
"Set", "MutableSet",
@@ -59,6 +60,11 @@ async def _coro(): pass
5960
coroutine = type(_coro)
6061
_coro.close() # Prevent ResourceWarning
6162
del _coro
63+
## asynchronous generator ##
64+
async def _ag(): yield
65+
_ag = _ag()
66+
async_generator = type(_ag)
67+
del _ag
6268

6369

6470
### ONE-TRICK PONIES ###
@@ -183,6 +189,57 @@ def __subclasshook__(cls, C):
183189
return NotImplemented
184190

185191

192+
class AsyncGenerator(AsyncIterator):
193+
194+
__slots__ = ()
195+
196+
async def __anext__(self):
197+
"""Return the next item from the asynchronous generator.
198+
When exhausted, raise StopAsyncIteration.
199+
"""
200+
return await self.asend(None)
201+
202+
@abstractmethod
203+
async def asend(self, value):
204+
"""Send a value into the asynchronous generator.
205+
Return next yielded value or raise StopAsyncIteration.
206+
"""
207+
raise StopAsyncIteration
208+
209+
@abstractmethod
210+
async def athrow(self, typ, val=None, tb=None):
211+
"""Raise an exception in the asynchronous generator.
212+
Return next yielded value or raise StopAsyncIteration.
213+
"""
214+
if val is None:
215+
if tb is None:
216+
raise typ
217+
val = typ()
218+
if tb is not None:
219+
val = val.with_traceback(tb)
220+
raise val
221+
222+
async def aclose(self):
223+
"""Raise GeneratorExit inside coroutine.
224+
"""
225+
try:
226+
await self.athrow(GeneratorExit)
227+
except (GeneratorExit, StopAsyncIteration):
228+
pass
229+
else:
230+
raise RuntimeError("asynchronous generator ignored GeneratorExit")
231+
232+
@classmethod
233+
def __subclasshook__(cls, C):
234+
if cls is AsyncGenerator:
235+
return _check_methods(C, '__aiter__', '__anext__',
236+
'asend', 'athrow', 'aclose')
237+
return NotImplemented
238+
239+
240+
AsyncGenerator.register(async_generator)
241+
242+
186243
class Iterable(metaclass=ABCMeta):
187244

188245
__slots__ = ()

Lib/test/test_collections.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
from collections import UserDict, UserString, UserList
2020
from collections import ChainMap
2121
from collections import deque
22-
from collections.abc import Awaitable, Coroutine, AsyncIterator, AsyncIterable
22+
from collections.abc import Awaitable, Coroutine
23+
from collections.abc import AsyncIterator, AsyncIterable, AsyncGenerator
2324
from collections.abc import Hashable, Iterable, Iterator, Generator, Reversible
2425
from collections.abc import Sized, Container, Callable, Collection
2526
from collections.abc import Set, MutableSet
@@ -959,6 +960,87 @@ def throw(self, *args): pass
959960

960961
self.assertRaises(RuntimeError, IgnoreGeneratorExit().close)
961962

963+
def test_AsyncGenerator(self):
964+
class NonAGen1:
965+
def __aiter__(self): return self
966+
def __anext__(self): return None
967+
def aclose(self): pass
968+
def athrow(self, typ, val=None, tb=None): pass
969+
970+
class NonAGen2:
971+
def __aiter__(self): return self
972+
def __anext__(self): return None
973+
def aclose(self): pass
974+
def asend(self, value): return value
975+
976+
class NonAGen3:
977+
def aclose(self): pass
978+
def asend(self, value): return value
979+
def athrow(self, typ, val=None, tb=None): pass
980+
981+
non_samples = [
982+
None, 42, 3.14, 1j, b"", "", (), [], {}, set(),
983+
iter(()), iter([]), NonAGen1(), NonAGen2(), NonAGen3()]
984+
for x in non_samples:
985+
self.assertNotIsInstance(x, AsyncGenerator)
986+
self.assertFalse(issubclass(type(x), AsyncGenerator), repr(type(x)))
987+
988+
class Gen:
989+
def __aiter__(self): return self
990+
async def __anext__(self): return None
991+
async def aclose(self): pass
992+
async def asend(self, value): return value
993+
async def athrow(self, typ, val=None, tb=None): pass
994+
995+
class MinimalAGen(AsyncGenerator):
996+
async def asend(self, value):
997+
return value
998+
async def athrow(self, typ, val=None, tb=None):
999+
await super().athrow(typ, val, tb)
1000+
1001+
async def gen():
1002+
yield 1
1003+
1004+
samples = [gen(), Gen(), MinimalAGen()]
1005+
for x in samples:
1006+
self.assertIsInstance(x, AsyncIterator)
1007+
self.assertIsInstance(x, AsyncGenerator)
1008+
self.assertTrue(issubclass(type(x), AsyncGenerator), repr(type(x)))
1009+
self.validate_abstract_methods(AsyncGenerator, 'asend', 'athrow')
1010+
1011+
def run_async(coro):
1012+
result = None
1013+
while True:
1014+
try:
1015+
coro.send(None)
1016+
except StopIteration as ex:
1017+
result = ex.args[0] if ex.args else None
1018+
break
1019+
return result
1020+
1021+
# mixin tests
1022+
mgen = MinimalAGen()
1023+
self.assertIs(mgen, mgen.__aiter__())
1024+
self.assertIs(run_async(mgen.asend(None)), run_async(mgen.__anext__()))
1025+
self.assertEqual(2, run_async(mgen.asend(2)))
1026+
self.assertIsNone(run_async(mgen.aclose()))
1027+
with self.assertRaises(ValueError):
1028+
run_async(mgen.athrow(ValueError))
1029+
1030+
class FailOnClose(AsyncGenerator):
1031+
async def asend(self, value): return value
1032+
async def athrow(self, *args): raise ValueError
1033+
1034+
with self.assertRaises(ValueError):
1035+
run_async(FailOnClose().aclose())
1036+
1037+
class IgnoreGeneratorExit(AsyncGenerator):
1038+
async def asend(self, value): return value
1039+
async def athrow(self, *args): pass
1040+
1041+
with self.assertRaises(RuntimeError):
1042+
run_async(IgnoreGeneratorExit().aclose())
1043+
9621044
def test_Sized(self):
9631045
non_samples = [None, 42, 3.14, 1j,
9641046
_test_gen(),

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ Library
6969
- Issue #28704: Fix create_unix_server to support Path-like objects
7070
(PEP 519).
7171

72+
- Issue #28720: Add collections.abc.AsyncGenerator.
73+
7274
Documentation
7375
-------------
7476

0 commit comments

Comments
 (0)