Issue #28720: Add collections.abc.AsyncGenerator.
This commit is contained in:
parent
41782e4970
commit
22214ab0af
|
@ -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::
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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__ = ()
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in New Issue