Issue #28720: Add collections.abc.AsyncGenerator.

This commit is contained in:
Yury Selivanov 2016-11-16 18:25:04 -05:00
parent 41782e4970
commit 22214ab0af
5 changed files with 155 additions and 2 deletions

View File

@ -92,6 +92,7 @@ ABC Inherits from Abstract Methods Mixin
:class:`Coroutine` :class:`Awaitable` ``send``, ``throw`` ``close``
:class:`AsyncIterable` ``__aiter__``
:class:`AsyncIterator` :class:`AsyncIterable` ``__anext__`` ``__aiter__``
:class:`AsyncGenerator` :class:`AsyncIterator` ``asend``, ``athrow`` ``aclose``, ``__aiter__``, ``__anext__``
========================== ====================== ======================= ====================================================
@ -222,6 +223,13 @@ ABC Inherits from Abstract Methods Mixin
.. versionadded:: 3.5
.. class:: Generator
ABC for asynchronous generator classes that implement the protocol
defined in :pep:`525` and :pep:`492`.
.. versionadded:: 3.6
These ABCs allow us to ask classes or instances if they provide
particular functionality, for example::

View File

@ -912,6 +912,10 @@ The new :class:`~collections.abc.Reversible` abstract base class represents
iterable classes that also provide the :meth:`__reversed__`.
(Contributed by Ivan Levkivskyi in :issue:`25987`.)
The new :class:`~collections.abc.AsyncGenerator` abstract base class represents
asynchronous generators.
(Contributed by Yury Selivanov in :issue:`28720`.)
The :func:`~collections.namedtuple` function now accepts an optional
keyword argument *module*, which, when specified, is used for
the ``__module__`` attribute of the returned named tuple class.

View File

@ -9,7 +9,8 @@ Unit tests are in test_collections.
from abc import ABCMeta, abstractmethod
import sys
__all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator",
__all__ = ["Awaitable", "Coroutine",
"AsyncIterable", "AsyncIterator", "AsyncGenerator",
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
"Sized", "Container", "Callable", "Collection",
"Set", "MutableSet",
@ -59,6 +60,11 @@ _coro = _coro()
coroutine = type(_coro)
_coro.close() # Prevent ResourceWarning
del _coro
## asynchronous generator ##
async def _ag(): yield
_ag = _ag()
async_generator = type(_ag)
del _ag
### ONE-TRICK PONIES ###
@ -183,6 +189,57 @@ class AsyncIterator(AsyncIterable):
return NotImplemented
class AsyncGenerator(AsyncIterator):
__slots__ = ()
async def __anext__(self):
"""Return the next item from the asynchronous generator.
When exhausted, raise StopAsyncIteration.
"""
return await self.asend(None)
@abstractmethod
async def asend(self, value):
"""Send a value into the asynchronous generator.
Return next yielded value or raise StopAsyncIteration.
"""
raise StopAsyncIteration
@abstractmethod
async def athrow(self, typ, val=None, tb=None):
"""Raise an exception in the asynchronous generator.
Return next yielded value or raise StopAsyncIteration.
"""
if val is None:
if tb is None:
raise typ
val = typ()
if tb is not None:
val = val.with_traceback(tb)
raise val
async def aclose(self):
"""Raise GeneratorExit inside coroutine.
"""
try:
await self.athrow(GeneratorExit)
except (GeneratorExit, StopAsyncIteration):
pass
else:
raise RuntimeError("asynchronous generator ignored GeneratorExit")
@classmethod
def __subclasshook__(cls, C):
if cls is AsyncGenerator:
return _check_methods(C, '__aiter__', '__anext__',
'asend', 'athrow', 'aclose')
return NotImplemented
AsyncGenerator.register(async_generator)
class Iterable(metaclass=ABCMeta):
__slots__ = ()

View File

@ -19,7 +19,8 @@ from collections import namedtuple, Counter, OrderedDict, _count_elements
from collections import UserDict, UserString, UserList
from collections import ChainMap
from collections import deque
from collections.abc import Awaitable, Coroutine, AsyncIterator, AsyncIterable
from collections.abc import Awaitable, Coroutine
from collections.abc import AsyncIterator, AsyncIterable, AsyncGenerator
from collections.abc import Hashable, Iterable, Iterator, Generator, Reversible
from collections.abc import Sized, Container, Callable, Collection
from collections.abc import Set, MutableSet
@ -959,6 +960,87 @@ class TestOneTrickPonyABCs(ABCTestCase):
self.assertRaises(RuntimeError, IgnoreGeneratorExit().close)
def test_AsyncGenerator(self):
class NonAGen1:
def __aiter__(self): return self
def __anext__(self): return None
def aclose(self): pass
def athrow(self, typ, val=None, tb=None): pass
class NonAGen2:
def __aiter__(self): return self
def __anext__(self): return None
def aclose(self): pass
def asend(self, value): return value
class NonAGen3:
def aclose(self): pass
def asend(self, value): return value
def athrow(self, typ, val=None, tb=None): pass
non_samples = [
None, 42, 3.14, 1j, b"", "", (), [], {}, set(),
iter(()), iter([]), NonAGen1(), NonAGen2(), NonAGen3()]
for x in non_samples:
self.assertNotIsInstance(x, AsyncGenerator)
self.assertFalse(issubclass(type(x), AsyncGenerator), repr(type(x)))
class Gen:
def __aiter__(self): return self
async def __anext__(self): return None
async def aclose(self): pass
async def asend(self, value): return value
async def athrow(self, typ, val=None, tb=None): pass
class MinimalAGen(AsyncGenerator):
async def asend(self, value):
return value
async def athrow(self, typ, val=None, tb=None):
await super().athrow(typ, val, tb)
async def gen():
yield 1
samples = [gen(), Gen(), MinimalAGen()]
for x in samples:
self.assertIsInstance(x, AsyncIterator)
self.assertIsInstance(x, AsyncGenerator)
self.assertTrue(issubclass(type(x), AsyncGenerator), repr(type(x)))
self.validate_abstract_methods(AsyncGenerator, 'asend', 'athrow')
def run_async(coro):
result = None
while True:
try:
coro.send(None)
except StopIteration as ex:
result = ex.args[0] if ex.args else None
break
return result
# mixin tests
mgen = MinimalAGen()
self.assertIs(mgen, mgen.__aiter__())
self.assertIs(run_async(mgen.asend(None)), run_async(mgen.__anext__()))
self.assertEqual(2, run_async(mgen.asend(2)))
self.assertIsNone(run_async(mgen.aclose()))
with self.assertRaises(ValueError):
run_async(mgen.athrow(ValueError))
class FailOnClose(AsyncGenerator):
async def asend(self, value): return value
async def athrow(self, *args): raise ValueError
with self.assertRaises(ValueError):
run_async(FailOnClose().aclose())
class IgnoreGeneratorExit(AsyncGenerator):
async def asend(self, value): return value
async def athrow(self, *args): pass
with self.assertRaises(RuntimeError):
run_async(IgnoreGeneratorExit().aclose())
def test_Sized(self):
non_samples = [None, 42, 3.14, 1j,
_test_gen(),

View File

@ -69,6 +69,8 @@ Library
- Issue #28704: Fix create_unix_server to support Path-like objects
(PEP 519).
- Issue #28720: Add collections.abc.AsyncGenerator.
Documentation
-------------