import asyncio import sys from types import TracebackType from typing import ( Any, Awaitable, Callable, Coroutine, Generator, Generic, Optional, Type, TypeVar, Union, ) if sys.version_info >= (3, 7, 0): __get_running_loop = asyncio.get_running_loop else: def __get_running_loop() -> asyncio.AbstractEventLoop: loop = asyncio.get_event_loop() if not loop.is_running(): raise RuntimeError("no running event loop") return loop def get_running_loop() -> asyncio.AbstractEventLoop: return __get_running_loop() def create_completed_future( loop: asyncio.AbstractEventLoop, ) -> "asyncio.Future[Any]": future = loop.create_future() future.set_result(None) return future _TObj = TypeVar("_TObj") _Release = Callable[[_TObj], Awaitable[None]] class _ContextManager(Coroutine[Any, None, _TObj], Generic[_TObj]): __slots__ = ("_coro", "_obj", "_release", "_release_on_exception") def __init__( self, coro: Coroutine[Any, None, _TObj], release: _Release[_TObj], release_on_exception: Optional[_Release[_TObj]] = None, ): self._coro = coro self._obj: Optional[_TObj] = None self._release = release self._release_on_exception = ( release if release_on_exception is None else release_on_exception ) def send(self, value: Any) -> "Any": return self._coro.send(value) def throw( # type: ignore self, typ: Type[BaseException], val: Optional[Union[BaseException, object]] = None, tb: Optional[TracebackType] = None, ) -> Any: if val is None: return self._coro.throw(typ) if tb is None: return self._coro.throw(typ, val) return self._coro.throw(typ, val, tb) def close(self) -> None: self._coro.close() def __await__(self) -> Generator[Any, None, _TObj]: return self._coro.__await__() async def __aenter__(self) -> _TObj: self._obj = await self._coro assert self._obj return self._obj async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc: Optional[BaseException], tb: Optional[TracebackType], ) -> None: if self._obj is None: return try: if exc_type is not None: await self._release_on_exception(self._obj) else: await self._release(self._obj) finally: self._obj = None class _IterableContextManager(_ContextManager[_TObj]): __slots__ = () def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) def __aiter__(self) -> "_IterableContextManager[_TObj]": return self async def __anext__(self) -> _TObj: if self._obj is None: self._obj = await self._coro try: return await self._obj.__anext__() # type: ignore except StopAsyncIteration: try: await self._release(self._obj) finally: self._obj = None raise class ClosableQueue: """ Proxy object for an asyncio.Queue that is "closable" When the ClosableQueue is closed, with an exception object as parameter, subsequent or ongoing attempts to read from the queue will result in that exception being result in that exception being raised. Note: closing a queue with exception will still allow to read any items pending in the queue. The close exception is raised only once all items are consumed. """ __slots__ = ("_loop", "_queue", "_close_event") def __init__( self, queue: asyncio.Queue, # type: ignore loop: asyncio.AbstractEventLoop, ): self._loop = loop self._queue = queue self._close_event = loop.create_future() # suppress Future exception was never retrieved self._close_event.add_done_callback(lambda f: f.exception()) def close(self, exception: Exception) -> None: if self._close_event.done(): return self._close_event.set_exception(exception) async def get(self) -> Any: if self._close_event.done(): try: return self._queue.get_nowait() except asyncio.QueueEmpty: return self._close_event.result() get = asyncio.ensure_future(self._queue.get(), loop=self._loop) try: await asyncio.wait( [get, self._close_event], return_when=asyncio.FIRST_COMPLETED ) except asyncio.CancelledError: get.cancel() raise if get.done(): return get.result() try: return self._close_event.result() finally: get.cancel() def empty(self) -> bool: return self._queue.empty() def qsize(self) -> int: return self._queue.qsize() def get_nowait(self) -> Any: if self._close_event.done(): try: return self._queue.get_nowait() except asyncio.QueueEmpty: return self._close_event.result() return self._queue.get_nowait()